动态换肤一(前期预备知识)

  动态换肤框架是仿照网易云音乐来换肤的,换肤的方式就是通过解压 apk 文件从中获取到皮肤包的资源,然后替换我们主包中的资源。

创建项目

  首先我们新建一个项目,再在这个项目里面新建一个 module 模拟第三方框架引入。


image.png

image.png

模拟使用者使用

  ,假如我们是调用者,我们使用这个框架的时候,当然是希望越简单越好,如果我是使用者,我可能会希望这样使用这个框架。

public class MainActivity extends AppCompatActivity {

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

        findViewById(R.id.tv_click).setOnClickListener(v -> changeSkin());
    }

    private void changeSkin() {
        /**
         * 这里我希望传入一个皮肤包的路径,然后框架帮我换肤,
         * 如果我传入的是一个空的字符串,框架帮我换到主项目的原始皮肤
         */
        XXX.load("xxxxx");
    }
}

思考的问题

  我们这个换肤,其实就是给每个控件的某个属性换一个不同的值,比如:换肤前,TextView 的 android:textColor="@color/white",那么我们点击换肤按钮后, TextView 的 android:textColor="@color/black",大体就是这个意思。
  那么我们首先要思考下面几个问题:

  • 1.我们如何拿到 View 来进行换肤,框架层,肯定不能用 findViewById
  • 2.拿到 View 后,我们如何拿到皮肤包中的资源文件
      也就两个问题,我们将这些问题逐个进行解决。

问题1:如何拿到 View

  该问题应该拆分成两步,第一步是拿到该 Activity 或者说这个 Activity 的布局文件中的所有控件;第二部是从这些全部的控件中筛选出我们需要换肤的 View,因为并不是所有的 View 都需要换肤。

拿到每个 Activity 的布局文件中的所有的 View

  首先,我们在 Activity 的 onCreate() 方法里面,可以直接通过 findViewById() 方法拿到对应的控件,说明我们所有的 View 都已经创建完毕并且加载到当前的 Activity 里面了,那么我们的换肤框架也想要拿到所有的 View ,怎么办?观察 Activity ,发现 setContentView() 方法有蹊跷。

注意:我的项目中 Activity 继承自 AppCompatActivity,(API level = 26)

  查看 setContentView() 源码:


AppCompatActivity&setContentView()

  这个 getDelegate() 方法返回的是一个 AppCompatDelegate,点进去发现,实际上调用的是 AppCompatDelegate.create() 方法,一路跟踪下去:


AppCompatDelegate&create()

  我们发现,这就做了一些兼容性处理,我们随便选择一个,全局搜索 setContentVIew() 方法,我最终在 AppCompatDelegateImplV9 类里面找到了setContentView() 方法的具体实现。
setContentView 源码

  找到这,那么我们就要看看 LayoutInflater 的 inflate() 方法干了什么。


inflate()

  在这个方法里面,首先获取了 Resources 对象,然后通过 getLayout() 方法获取了一个 XML 解析器(这里用的是 Pull 解析),最后调用 inflate() 的另一个重载方法,将生成的 View 返回。
  重载的 inflate() 代码有点长,就不全部截取了。
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            try {
                // Look for the root node.
                int type;
//              你看,Pull 解析
                while ((type = parser.next()) != XmlPullParser.START_TAG &&
                        type != XmlPullParser.END_DOCUMENT) {
                    // Empty
                }

                if (TAG_MERGE.equals(name)) {
                   ...
                    }
                    rInflate(parser, root, inflaterContext, attrs, false);
                } else {
                    // Temp is the root view that was found in the xml
//                  获取到 temp
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);
                    ...
//                  将 temp 赋值给 result
                    if (root == null || !attachToRoot) {
                        result = temp;
                    }
                }

            } catch (XmlPullParserException e) {
              ...
            } finally {
               ...
            }
//          最终返回的是 result
            return result;
        }
    }

  我们发现,最后 return 的是 result ,而这个 result 在前面又被 temp 赋值了,而这个 temp 是通过 createViewFromTag() 方法返回的。我们继续看 createViewFromTag() 方法。

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
            boolean ignoreThemeAttr) {
        ...
//      注意下面代码
        try {
            View view;
            if (mFactory2 != null) {
                view = mFactory2.onCreateView(parent, name, context, attrs);
            } else if (mFactory != null) {
                view = mFactory.onCreateView(name, context, attrs);
            } 
            ...
            if (view == null && mPrivateFactory != null) {
                view = mPrivateFactory.onCreateView(parent, name, context, attrs);
            }
            if (view == null) {
                final Object lastContext = mConstructorArgs[0];
                mConstructorArgs[0] = context;
                try {
                    if (-1 == name.indexOf('.')) {
                        view = onCreateView(parent, name, attrs);
                    } else {
                        view = createView(name, null, attrs);
                    }
                } 
                ...
            }
            return view;
        } catch (InflateException e) {
            ...
        } 
    }

  OK!看到这,我们大概明白了,是通过 mFactory2 或者 mFactory 或者 mPrivateFactory 的 onCreateView() 方法来创建的 View,如果上述方法都不行,则通过反射调用构造方法的方式来创建相应的 View 的。
  看到这里,有点想法,这个 mFactory2 的 onCreateView() 方法里面可以拿到 View 的 name,还有 attrs 属性,那通过反射的方式,就可以拿到对应的 View 了。正好这个 Factory2 是一个借口,我们给 LayoutInfalter 提供我们自定义的 Factory2,就会调用到我们的 onCreateView() 方法来创建 View 了。
  而且,很巧的是,LayoutInfalter 为我们提供了一个设置 Factory2 的方法。


设置 Factory2 的方法

下一篇文章地址:https://www.jianshu.com/p/1e180d8ed33b

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,163评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,301评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,089评论 0 352
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,093评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,110评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,079评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,005评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,840评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,278评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,497评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,394评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,980评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,628评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,649评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,548评论 2 352