我一直在使用流和砂漿作爲Android應用的替代架構進行試驗。我一直在研究一款應用程序,該應用程序當前只有一種電話佈局,但我想知道如果您想爲平板電腦設置不同的佈局,流程和迫擊炮架構如何工作。主細節可能是最簡單的例子,但顯然還有其他例子。Square Flow + Mortar平板電腦示例
我有一些想法如何工作,但我想知道廣場開發人員可能已經考慮過這個話題。
我一直在使用流和砂漿作爲Android應用的替代架構進行試驗。我一直在研究一款應用程序,該應用程序當前只有一種電話佈局,但我想知道如果您想爲平板電腦設置不同的佈局,流程和迫擊炮架構如何工作。主細節可能是最簡單的例子,但顯然還有其他例子。Square Flow + Mortar平板電腦示例
我有一些想法如何工作,但我想知道廣場開發人員可能已經考慮過這個話題。
我們仍在爲此做出規範的回答,但基本思路是讓資源系統更改您在哪種情況下顯示的視圖。因此,您的活動將其內容視圖設置爲R.layout.root_view
。該佈局的平板電腦版本(我們將其放在res/layout-sw600dp
中)可以綁定到不同的視圖,這可能會注入不同的演示者,等等。
很多情況下你需要做一個決定運行的情況下,定義values/bools .xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<bool name="show_tablet_ui">false</bool>
</resources>
和values-sw600dp/bools.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<bool name="show_tablet_ui">true</bool>
</resources>
一個布爾資源將其暴露在通過匕首應用程序的其餘部分。使用此綁定註釋:
/**
* Whether we should show a tablet UI.
*/
@Retention(RUNTIME) @Qualifier
public @interface ShowTabletUi {
int ID = R.bool.show_tablet_ui;
}
和提供方法,如:
/**
* Singleton because there's no reason to read it from resources again,
* it won't change.
*/
@Provides @ShowTabletUi @Singleton boolean showTabletUi(Resources resources) {
return resources.getBoolean(ShowTabletUi.ID);
}
別急還有更多!假設你想要一個單一的屏幕/藍圖定義,爲不同的外形因素製造不同的模塊。我們已經開始使用註解方案來簡化這種事情。我們已經開始使用一些註釋來聲明它們的接口類,而不是讓我們的屏幕類全部實現BluePrint
。在這個世界裏,屏幕如何有選擇地選擇用於平板電腦或手機的模塊。
@Layout(R.layout.some_view) @WithModuleFactory(SomeScreen.ModuleFactory.class)
public class SomeScreen {
public static class ModuleFactory extends ResponsiveModuleFactory<HomeScreen> {
@Override protected Object createTabletModule(HomeScreen screen) {
return new TabletModule();
}
@Override protected Object createMobileModule(HomeScreen screen) {
return new MobileModule();
}
}
魔法吧?這是幕後的內容。首先,一個ModuleFactory
是一些靜態類,可以訪問屏幕和資源,並吐出一個匕首模塊。
public abstract class ModuleFactory<T> {
final Blueprint createBlueprint(final Resources resources, final MortarScreen screen) {
return new Blueprint() {
@Override public String getMortarScopeName() {
return screen.getName();
}
@Override public Object getDaggerModule() {
return ModuleFactory.this.createDaggerModule(resources, (T) screen);
}
};
}
protected abstract Object createDaggerModule(Resources resources, T screen);
}
我們的trixie ResponsiveModuleFactory
子類看起來像這樣。 (記住怎樣ShowTabletUi.java
定義的資源ID作爲常數?這是爲什麼。)
public abstract class ResponsiveModuleFactory<T> extends ModuleFactory<T> {
@Override protected final Object createDaggerModule(Resources resources, T screen) {
boolean showTabletUi = resources.getBoolean(ShowTabletUi.ID);
return showTabletUi ? createTabletModule(screen) : createMobileModule(screen);
}
protected abstract Object createTabletModule(T screen);
protected abstract Object createMobileModule(T screen);
}
爲了讓這一切都走了,我們有一個ScreenScoper類(如下圖)。在Mortar示例代碼中,您將使ScreenConductor使用其中的一個來創建和銷燬示波器。遲早(很快我希望)迫擊炮和/或其樣本將被更新以包含這些東西。
package mortar;
import android.content.Context;
import android.content.res.Resources;
import com.squareup.util.Objects;
import dagger.Module;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.LinkedHashMap;
import java.util.Map;
import static java.lang.String.format;
/**
* Creates {@link MortarScope}s for screens that may be annotated with {@link WithModuleFactory},
* {@link WithModule} or {@link Module}.
*/
public class ScreenScoper {
private static final ModuleFactory NO_FACTORY = new ModuleFactory() {
@Override protected Object createDaggerModule(Resources resources, Object screen) {
throw new UnsupportedOperationException();
}
};
private final Map<Class, ModuleFactory> moduleFactoryCache = new LinkedHashMap<>();
public MortarScope getScreenScope(Context context, final MortarScreen screen) {
MortarScope parentScope = Mortar.getScope(context);
return getScreenScope(context.getResources(), parentScope, screen);
}
/**
* Finds or creates the scope for the given screen, honoring its optoinal {@link
* WithModuleFactory} or {@link WithModule} annotation. Note the scopes are also created
* for unannotated screens.
*/
public MortarScope getScreenScope(Resources resources, MortarScope parentScope,
final MortarScreen screen) {
ModuleFactory moduleFactory = getModuleFactory(screen);
MortarScope childScope;
if (moduleFactory != NO_FACTORY) {
Blueprint blueprint = moduleFactory.createBlueprint(resources, screen);
childScope = parentScope.requireChild(blueprint);
} else {
// We need every screen to have a scope, so that anything it injects is scoped. We need
// this even if the screen doesn't declare a module, because Dagger allows injection of
// objects that are annotated even if they don't appear in a module.
Blueprint blueprint = new Blueprint() {
@Override public String getMortarScopeName() {
return screen.getName();
}
@Override public Object getDaggerModule() {
return null;
}
};
childScope = parentScope.requireChild(blueprint);
}
return childScope;
}
private ModuleFactory getModuleFactory(MortarScreen screen) {
Class<?> screenType = Objects.getClass(screen);
ModuleFactory moduleFactory = moduleFactoryCache.get(screenType);
if (moduleFactory != null) return moduleFactory;
WithModule withModule = screenType.getAnnotation(WithModule.class);
if (withModule != null) {
Class<?> moduleClass = withModule.value();
Constructor<?>[] constructors = moduleClass.getDeclaredConstructors();
if (constructors.length != 1) {
throw new IllegalArgumentException(
format("Module %s for screen %s should have exactly one public constructor",
moduleClass.getName(), screen.getName()));
}
Constructor constructor = constructors[0];
Class[] parameters = constructor.getParameterTypes();
if (parameters.length > 1) {
throw new IllegalArgumentException(
format("Module %s for screen %s should have 0 or 1 parameter", moduleClass.getName(),
screen.getName()));
}
Class screenParameter;
if (parameters.length == 1) {
screenParameter = parameters[0];
if (!screenParameter.isInstance(screen)) {
throw new IllegalArgumentException(format("Module %s for screen %s should have a "
+ "constructor parameter that is a super class of %s", moduleClass.getName(),
screen.getName(), screen.getClass().getName()));
}
} else {
screenParameter = null;
}
try {
if (screenParameter == null) {
moduleFactory = new NoArgsFactory(constructor);
} else {
moduleFactory = new SingleArgFactory(constructor);
}
} catch (Exception e) {
throw new RuntimeException(
format("Failed to instantiate module %s for screen %s", moduleClass.getName(),
screen.getName()), e);
}
}
if (moduleFactory == null) {
WithModuleFactory withModuleFactory = screenType.getAnnotation(WithModuleFactory.class);
if (withModuleFactory != null) {
Class<? extends ModuleFactory> mfClass = withModuleFactory.value();
try {
moduleFactory = mfClass.newInstance();
} catch (Exception e) {
throw new RuntimeException(format("Failed to instantiate module factory %s for screen %s",
withModuleFactory.value().getName(), screen.getName()), e);
}
}
}
if (moduleFactory == null) moduleFactory = NO_FACTORY;
moduleFactoryCache.put(screenType, moduleFactory);
return moduleFactory;
}
private static class NoArgsFactory extends ModuleFactory<Object> {
final Constructor moduleConstructor;
private NoArgsFactory(Constructor moduleConstructor) {
this.moduleConstructor = moduleConstructor;
}
@Override protected Object createDaggerModule(Resources resources, Object ignored) {
try {
return moduleConstructor.newInstance();
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
}
}
private static class SingleArgFactory extends ModuleFactory {
final Constructor moduleConstructor;
public SingleArgFactory(Constructor moduleConstructor) {
this.moduleConstructor = moduleConstructor;
}
@Override protected Object createDaggerModule(Resources resources, Object screen) {
try {
return moduleConstructor.newInstance(screen);
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
}
}
}
我昨天第一次發佈它的ScreenScoper有一個緩存錯誤。我剛剛修復它。 – rjrjr
這是超好玩!什麼是MortarScreen類?所有Screens都是從哪個空類中擴展而來的?與Blueprint有什麼不同? –
@NelsonOsacky它是一個定義String getName()方法的接口。 – rjrjr