风味特定代码

Flavor specific code

考虑一个 Activity MainActivity 和片段 MainFragment。该片段具有一些复杂的布局层次结构和一个来自库 com.framer:frame_me:1.1.

的视图组 Frame

如果我有 2 种口味 foobar,并且我希望这个 Frame 只出现在 bar 口味中,而不出现在 foo 中、XML 元素 java 代码 和依赖关系 。我应该怎么做?

我可以使用

编译依赖
barCompile 'com.framer:frame_me:1.1'

但是片段及其 XML 呢?我不想在两种风格的片段中编写 2 个变体,因为我不想在 2 个地方维护相同的代码。


在我看来,一个可能的想法(可能是一个坏想法)是:

  1. 将 XML 元素移动到 bar 源集中的单独文件中。在 foo 源集中添加同名的 ViewStub 元素。现在在片段 XML
  2. 中使用 include 包含此 XML 文件
  3. 添加一个接口来处理 main 源集中的 Frame 视图。在 foo 源集中添加一个空实现,在 bar 源集中添加一个。这样,所有逻辑都可以保留在 bar 中,而所有通用逻辑都可以保留在 main 源集中。

这一切听起来只是为了编写特定于风味的代码和 xml。

做了大量的工作

如何用 FrameLayout 容器替换 XML 中的 Frame 标签?

然后在 bar 风格的源代码中你可以实例化 Frame 并说 container.addView(frame)foo flavor 不会引用 Frame class 并且会忽略容器。

这与您的第一种方法类似,但无需维护单独的资源集。这似乎是合理的,无论如何你都会有一些特定于风味的 java 代码。

build.gradle sourceSets 选项是什么? 您可以将 Fragment 和 XML 放在 bar 文件夹中,然后设置:

android {
    productFlavors {
         ...
    }
    sourceSets {
         bar.java.srcDirs = ['src/bar/java']
         bar.res.srcDirs = ['src/bar/res']
    }
}

你只需要抽象。由于资源是使用 R class 中的整数索引来标识的,您可以使用 int 变量作为布局文件的占位符,并且考虑到在活动布局中搜索布局元素 ID 的事实,您可以回收公共元素.首先,创建一个公共片段 class,其中包含所有公共元素:

public abstract class BaseFlavorFragment extends Fragment {

/*Define an interface for whatever code the fragment may need from the outside and a member for keeping reference of that. You can also use the host activity, this is just for flexibility*/
    public interface whateverThisDoes{
        void do();
    }
/*All the common fragment members go here, as protected so you can reach them from every subclass*/
    protected TextView title;
    protected Button mainButton;
    protected whateverThisDoes listener;

    public void setWhateverThisDoes(whateverThisDoes listener){
        this.listener = listener;
    }
/*Finally, create a int variable that will hold the reference to the layout file you need to use. you will set this in every flavor using the setContainer method.*/
    protected int layout = 0;

    /*this will allow you to select which XML to use
layout = R.layout.flavorlayout*/
    public abstract setContainer();

    /*Use this method to inflate any flavor members, like the Frame you mentioned*/
    public abstract void inflateComponents();
    /*Use this to set listeners, data, or anything the flavor controls do*/
    public abstract void setBehaviors();

    /*Set here anything the common controls do*/
    protected void setCommonBehaviors(){
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //whatever                
            }
        });
        setBehaviors();
    }

    @Override
    public void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        setContainer();
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
       super.onCreateView(inflater, container, savedInstanceState);
       View view = inflater.inflate(layout, container, false);
       /*Inflate common components*/
       title =  (TextView) root.findViewById(R.id.title);
       button =  (Button) root.findViewById(R.id.button);
       /*inflate flavor components, if there's any*/
       inflateComponents(); 
       /*assign data, listeners, whatever the flavor controls do*/
       setBehaviors();          
       return view;
    }
}

现在,您只需为 Foo 和 Bar 创建一个实现即可。如果唯一的区别是布局文件,将所有内容放入基础 class,并使用 setContainer() 设置布局文件。如果您有更多差异,则只需将它们处理到每个抽象方法中即可。基础 class 可以存在于公共代码、实现、每个风格中。如果您不需要从外部设置任何行为代码,则可以删除该接口。