Android理解(一)自定义控件皮肤的原理

参考了https://blog.csdn.net/jzman/article/details/108612469

以及开源的https://github.com/fengjundev/Android-Skin-Loader

首先,理解一下默认的创建View方式之前做一次拦截,就是接口Factory,Factory2的重写,里面写入自己的创建View的逻辑,

可以直接将创建的View返回,这样就是自定义View创建

并且也可以在某些条件判断下,选择默认的创建方法,也就是部分自定义View

还可以用来对View的属性集做一些事情

// LayoutInflater.java
public interface Factory {
    /**
     * @param name Tag名称,如TextView
     * @param context 上下文环境
     * @param attrs Xml属性
     *
     * @return View 新创建的View,如果返回null,则Hook无效
     */
    public View onCreateView(String name, Context context, AttributeSet attrs);
}

public interface Factory2 extends Factory {
    // since API 11,多添加了一个参数parent
    public View onCreateView(View parent, String name, Context context, AttributeSet attrs);
}
//过滤后,一部分View使用默认创建
LayoutInflaterCompat.setFactory2(layoutInflater, new LayoutInflater.Factory2() {
    @Override
    public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {

        if (name.equals("TextView")){
            Button button = new Button(MainActivity.this);
            button.setText("我被替换成了Button");
            return button;
        }

        AppCompatDelegate compatDelegate = getDelegate();
        View view = compatDelegate.createView(parent,name,context,attrs);
        return view;
    }

    @Override
    public View onCreateView(String name, Context context, AttributeSet attrs) {
        return onCreateView(null,name,context,attrs);
    }
});

 有了这样的拦截已经可以做很多事情了,比如AndroidStudio中AppCompatActivity中对默认控件的替换等等。。。

当然这里说到的是自定义皮肤,而且以上的拦截有个不足就是只能在创建View也就是最初渲染的时候进行拦截,自定义皮肤不能一加载就整个界面重新绘制,所以还需要动态的修改属性

动态的修改View属性很简单,问题在于知道(1)哪些View需要自定义皮肤,(2)View的哪些属性需要自定义,(3)自定义属性的值可以哪里找

而anroid-skin-loader给出的解决方案是这样的:

问题一:定义一个自定义属性用来标记View是否需要皮肤,在最初的拦截时从View属性集中找到属性集进行解析

问题二:在问题一的基础上找特定的属性类型(支持自定义皮肤的),属性类型是写死的,我感觉如果想要定义再灵活一点的皮肤样式,这里也可以搞自定义属性

问题三:值可以在资源集Resources里面找,默认找的是当前应用的资源集。如何找自定义资源集呢?所以这里就涉及到一个皮肤包(自定义资源集)的概念,很简答,可以理解为一个apk包里面除了res文件夹之外什么都不写(因为用不着),manifest可以这么写

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="cn.feng.skinpackage">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

    </application>

</manifest>

res中把自定义的资源填上,对应布局文件中资源值的引用,然后打个包,然后涉及到Android有个PackageManager包解析器,还有个AssetManager资源管理者,下面这段代码

PackageManager mPm = context.getPackageManager();
PackageInfo mInfo = mPm.getPackageArchiveInfo(skinPkgPath, PackageManager.GET_ACTIVITIES);
skinPackageName = mInfo.packageName;

AssetManager assetManager = AssetManager.class.newInstance();
Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
addAssetPath.invoke(assetManager, skinPkgPath);

Resources superRes = context.getResources();
Resources skinResource = new Resources(assetManager,superRes.getDisplayMetrics(),superRes.getConfiguration());

包解析器解析出包名,是用于资源查找的。调用一个AssetManager反射方法(传参是包文件的路径),就加载了自定义资源集。上述代码得出的skinResource实际上是包括默认的资源集以及自定义的资源集。

那么自定义属性的值到底怎么查找呢?首先基于问题一二,确定了要自定义的属性,然后-》属性id-》属性值的引用,用这个引用和之前解析出的包名(哦,所以如果要多个皮肤,打包的时候注意包名不能重复)在资源集中查找属于资源包的资源值

 

回到最初的默认View创建前拦截上,这里要做的工作就是解析并保存问题一和问题二的答案,附带的创建View,如果资源是自定义资源集的还要去自定义中找。

问题来了,拦截时自定义资源集还没加载怎么办?所以为了保证加载这一步更快,要把它写在Application创建的时候,还有就是要保存一些配置啦,是否自定义,当前自定义皮肤包的路径了,方便一上来就加载自定义资源(好像是废话)

 

另外,这个框架也支持动态添加的View(非布局文件中的)的皮肤自定义,想想上文所述,无非是在”答案“中再添加一些数据。

 

自定义皮肤可以理解为View的动态属性值全套替换方案

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值