插件化换肤(1)

如何实现插件化换肤

1.拿到App中要替换的view以及该view在App中的resId
2.通过该view的resId它拿到在App中的属性名字和类型等信息,然后通过这些信息拿到皮肤中的id,暂命名为skinId
3.把skinId设置给要替换的view

来看一个例子,我们新建一个TestActivity继承自AppCompatActivity :

public class TestActivity extends AppCompatActivity {

    private static final String TAG = TestActivity.class.getSimpleName();

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);

        TextView mTvTest = findViewById(R.id.tv_test);
        Log.d(TAG, "The instance is:" + mTvTest);

        TextView textView = new TextView(this);
        Log.d(TAG, "The instance is:" + textView);
    }
}

布局文件如下:




    


Manifest.xml里面的theme要继承Theme.AppCompat.Light.NoActionBar,运行到手机上后打印信息如下:
The instance is:androidx.appcompat.widget.AppCompatTextView
The instance is:android.widget.TextView

发现了吗?我们自己new出来的TextView与布局文件里的TextView不一样,说明sdk对布局文件里的TextView进行了改造。sdk里面具体是怎么实现的呢,我们来看看AppCompatActivity的onCreate()方法:

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        final AppCompatDelegate delegate = getDelegate();
        delegate.installViewFactory();
        delegate.onCreate(savedInstanceState);
        super.onCreate(savedInstanceState);
    }

其中第4行的delegate.installViewFactory()会调用AppCompatDelegateImpl的installViewFactory()方法,代码如下:

    @Override
    public void installViewFactory() {
        LayoutInflater layoutInflater = LayoutInflater.from(mContext);
        if (layoutInflater.getFactory() == null) {
            LayoutInflaterCompat.setFactory2(layoutInflater, this);
        } else {
            if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImpl)) {
                Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"
                        + " so we can not install AppCompat's");
            }
        }
    }

在这里设置了setFactory2为this,通过上一篇文章LayoutInflater.inflate参数配置,我们知道inflate()方法会通过createViewFromTag()来根据节点名创建View对象,如果mFactory2 != null,会调用mFactory2.onCreateView(parent, name, context, attrs),所以这里会调用AppCompatDelegateImpl的onCreateView(View parent, String name, Context context, AttributeSet attrs)方法,最终调用AppCompatViewInflater的createView(),在这里我们可以看到把TextView改造成AppCompatTextView的代码,这就是继承AppCompatActivity打印的布局的实例是AppCompatTextView的原因。

我们也可以模仿这种方法通过onCreateView()拿到view以及resId等信息。AppCompatActivity 的布局是通过setContentView()解析的,所以我们必须在setContentView()之前执行setFactory2()方法,同时我们还需要注意LayoutInflater中的setFactory2(Factory2 factory)代码如下:

    public void setFactory2(Factory2 factory) {
        if (mFactorySet) {
            throw new IllegalStateException("A factory has already been set on this LayoutInflater");
        }
        if (factory == null) {
            throw new NullPointerException("Given factory can not be null");
        }
        mFactorySet = true;
        if (mFactory == null) {
            mFactory = mFactory2 = factory;
        } else {
            mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2);
        }
    }

如果mFactorySet为true,会抛出异常,我们还需要提前把mFactorySet的值改为false,再执行setFactory2()方法。

你可能感兴趣的:(插件化换肤(1))