2014-03-24 24 views
11

我一直在使用流和砂漿作爲Android應用的替代架構進行試驗。我一直在研究一款應用程序,該應用程序當前只有一種電話佈局,但我想知道如果您想爲平板電腦設置不同的佈局,流程和迫擊炮架構如何工作。主細節可能是最簡單的例子,但顯然還有其他例子。Square Flow + Mortar平板電腦示例

我有一些想法如何工作,但我想知道廣場開發人員可能已經考慮過這個話題。

回答

16

我們仍在爲此做出規範的回答,但基本思路是讓資源系統更改您在哪種情況下顯示的視圖。因此,您的活動將其內容視圖設置爲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); 
     } 
    } 
    } 
} 
+0

我昨天第一次發佈它的ScreenScoper有一個緩存錯誤。我剛剛修復它。 – rjrjr

+0

這是超好玩!什麼是MortarScreen類?所有Screens都是從哪個空類中擴展而來的?與Blueprint有什麼不同? –

+0

@NelsonOsacky它是一個定義String getName()方法的接口。 – rjrjr