【Android】自定义换肤框架03之自定义LayoutInflaterFactory

AppCompatActivity是如何创建View的
  • Activity通过LayoutInflater解析出XmlLayout相关信息
  • LayoutInflater内部维护了一个InflaterFactory对象
  • InflaterFactory接口包含了一个onCreateView方法,用于创建View
  • 将解析出的Xml信息转为AttributeSet,交给InflaterFactory来createView
  • AppCompatActivity中维护了一个AppCompatDelegate对象
  • 这个对象既用于处理兼容性工作,也实现了InflaterFactory接口
  • 在Activity执行onCreate方法时,会调用installViewFactory,将delegate设置为LayoutInflater的Factory2
LayoutInflater.Factory2和LayoutInflater.Factory
  • Factory2是新版本的Factory接口,Factory是旧接口
  • 当Factory2存在时,会忽略Factory,反之则使用Factory来创建View
  • Factory2是为了兼容旧版本代码和而引入的,通过delegate和factory轻松实现了两套逻辑的切换
自定义LayoutInflaterFactory

在上一章,我们实现了自定义AssetManager和Resources,但不知道在哪里去应用它们

现在我们知道,View是通过InflaterFactory创建的

如果我们能让Factory使用自定义Resources,那么基本就实现了换肤的功能

先上代码,让大家心里有个底

package com.android.app

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity

class HomeActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        layoutInflater.factory2 = SkinnerInflaterFactory(this)
        super.onCreate(savedInstanceState)
        val root = layoutInflater.inflate(R.layout.activity_home, null)
        setContentView(root)
    }
}
package com.android.app

import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.widget.ImageView
import androidx.appcompat.app.AppCompatActivity
import com.android.library.skinner.SkinnerAssetManager

typealias androidStyleableRes = androidx.appcompat.R.styleable

class SkinnerInflaterFactory(private val activity: AppCompatActivity) : LayoutInflater.Factory2 {

    override fun onCreateView(parent: View?, name: String, context: Context, attrs: AttributeSet): View? {
        val view = activity.delegate.createView(parent, name, context, attrs)
        if (view is ImageView) {
            skinImageView(view, attrs)
        }
        return view
    }

    override fun onCreateView(name: String, context: Context, attrs: AttributeSet) = null

    private fun skinImageView(view: ImageView, attrs: AttributeSet) {
        val typedArray = activity.obtainStyledAttributes(attrs, androidStyleableRes.AppCompatImageView)
        if (typedArray.hasValue(androidStyleableRes.AppCompatImageView_android_src)) {
            val srcDrawableId = typedArray.getResourceId(androidStyleableRes.AppCompatImageView_android_src, 0)
            val skinDrawable = SkinnerAssetManager.skinDrawable(srcDrawableId)
            view.setImageDrawable(skinDrawable)
        }
    }
}

代码其实非常简单,如果是自己实现的话,以下点需要注意

  • InflaterFactory一旦创建,不可再被修改,除非通过反射强制去修改
  • InflaterFactory默认是在onCreate方法里创建的,如果我们想使用自定义的,则需在onCreate之前设置
  • 由于InflaterFactory是在onCreate方法中设置的,意味着如果想中途换肤,则必须重启Activity甚至Application才会生效
  • 如果想让新的InflaterFactory立刻生效,只能通过反射去强制修改,然后再调用setContentView重新加载布局
  • InflaterFactory是从零开始创建完整的View,这意味着我们可以去做任何事情,只要不嫌麻烦
  • 比如读到name=TextView时,我们可以创建一个Button返回,完成控件替换
  • 比如读到name=TextView时,我们可以创建一个AppCompatTextView返回,完成旧控件自动升级
  • 当然,创建一个完整的View,而且是Xml中可能出现的所有View,工作量是非常庞大的
  • 我们要的只是更换皮肤,即修改部分属性对应的资源,没必要去自己去创建View
  • 我们可以调用默认的Factory去创建View,然后再修改我们想要的属性值即可
  • 上面代码中用到的activity.delegate.createView即是AppCompatActivity的默认Factory
  • 如果我们没有自定义Factory的话,activity.delegate就会成为默认的LayoutInflater.factory2
使用自定义皮肤资源

上面已经给出了自定义皮肤资源的代码

typealias androidStyleableRes = androidx.appcompat.R.styleable
private fun skinImageView(view: ImageView, attrs: AttributeSet) {
    val typedArray = activity.obtainStyledAttributes(attrs, androidStyleableRes.AppCompatImageView)
    if (typedArray.hasValue(androidStyleableRes.AppCompatImageView_android_src)) {
        val srcDrawableId = typedArray.getResourceId(androidStyleableRes.AppCompatImageView_android_src, 0)
        val skinDrawable = SkinnerAssetManager.skinDrawable(srcDrawableId)
        view.setImageDrawable(skinDrawable)
    }
}

在这段代码里,我们做了以下工作

  • 判断控件类型是不是我们想要修改的
  • 找到改控件对应的样式空间,即styleable.namespace
  • 找到自己想要修改的属性,即styleable.namespace_attr
  • 皮肤包中如果存在该资源,则使用皮肤包中的资源,否则使用安装包中的默认资源
  • 以上动态加载资源的过程,是通过SkinnerAssetManager去实现的
十万个为什么

如果只是一个Demo的话,到此为止已经完美实现功能了

但是在实际应用中,我们可能需要支持任意控件,任意属性的修改

这意味着,上一节的代码,可能需要上百段雷同的代码,才能满足所有的要求

并且,哪些属性需要适配换肤功能,Factory也是不知道的,需要我们想办法去指定

理想的情况是,所有资源通过Resources加载,然后根据资源名称对Resources进行Hook

遗憾的是,安卓并未支持以上机制,所以目前已有的皮肤适配方案,都一定程度上依赖手动去配置

下一章,我们将讲解,如何支持全控件全属性适配,并且能够适当简化编码

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/777249.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

软连接迁移 Docker 的默认安装(存储)目录

前言 经常我们会拿到一些别人装好的服务器,需要在这些系统上启动我们的docker服务。 但是这些“专业人员”呢,有时候就会有非常不专业的操作,比如他把根目录/只划分50GB,/home却有51TB。这个时候就会导致我们的服务器还有很多空间…

万界星空科技机械加工行业MES解决方案

机械加工行业作为制造业的重要组成部分,面临着生产效率、成本控制和产品质量提升等多重挑战。为了应对这些挑战,引入并实施制造执行系统(MES)成为了行业的必然选择。本文将详细介绍一种针对机械加工行业的MES解决方案,…

STM32-HAL-FATFS(文件系统)(没做完,stm32f103zet6(有大佬的可以在评论区说一下次板子为什么挂载失败了))

1STM32Cube配置 1-1配置时钟 1-2配置调试端口 1-3配置uart 1-4配置SDIO(注意参数)(其中他的初始化的异常函数给注释,SD卡文件写了) 配置了还要打开中断和DMA可在我的其他文章中看一样的 1-5配置FatFs (只改了图选中…

【Kubernetes】Pod 资源调度之亲和性调度

Pod 资源调度之亲和性调度 1.Node 亲和性调度1.1 Node 硬亲和性1.2 Node 软亲和性 2.Pod 亲和性调度2.1 Pod 硬亲和2.2 Pod 软亲和2.3 Pod 反亲和 Kubernetes 的 默认调度器 以 预选、优选、选定机制 完成将每个新的 Pod 资源绑定至为其选出的目标节点上,不过&#…

Javase-异常

文章目录 1. 异常概述2. 异常的继承结构3. 自定义异常4. 异常的处理5. 异常的使用6. finally语句块7. 方法覆盖与异常 1. 异常概述 什么是异常 ①什么是异常?有什么用? 1.Java中的异常是指程序运行时出现了错误或异常情况,导致程序无法继续正常执行的现象。例如&…

【CG】计算机图形学(Computer Graphics)基础(其壹)

0 学习视频 B站GAMES101-现代计算机图形学入门-闫令琪 1 什么是计算机图形学 1.1 什么是好的画面? 画面足够亮。如果全局光照做的好,整个画面就会亮,看起来很舒服。 1.2 计算机图形学涉及到的领域 数学(透视)投影…

java基础:面向对象(一)

一、概念 物以类聚,分类的思维模式,思考问题首先会解决问题需要哪些分类,然后对这些分类进行单独思考。最后,才对某个分类下的细节进行面向过程的思索。面向对象适合处理复杂的问题,适合处理需要多人协作的问题!对于描…

vulhub靶场之DEVGURU:1

1 信息收集 1.1 主机发现 arp-scan -l 发现主机IP地址为“192.168.1.11 1.2 端口发现 nmap -sS -sV -A -T5 -p- 192.168.1.11 发现端口为:22,80,8585 1.3 目录扫描 dirsearch -u 192.168.1.11 发现存在git泄露 2 文件和端口访问 2…

idea中没有显示‘‘Spring‘‘一栏 (已解决)

第一步: 随便找一个Bean(即直接或者间接使用Component的类) 第二步: 找到左边的图标, 右键这个图标, 然后选择如下选项: 第三步: 成功 然后就成功了, 可以看到具体的bean了以及其bean的关系图等.

MySQL的Geometry数据处理之WKB方案

MySQL的Geometry数据处理之WKT方案:https://blog.csdn.net/qq_42402854/article/details/140134357 MySQL的Geometry数据处理之WKT方案中,介绍WTK方案的优点,也感受到它的繁琐和缺陷。比如: 需要借助 ST_GeomFromText和 ST_AsTex…

主从复制原理及操作

主从复制的概念 主从复制是一种在数据库系统中常用的数据备份和读取扩展技术,通过将一个数据库服务器(主服务器)上的数据变更自动同步到一个或多个数据库服务器(从服务器)上,以此来实现数据的冗余备份、读…

数据库之SQL(二)

目录 一、简述SQL中如何将“行”转换为“列” 二、简述SQL注入 三、如何将一张表的部分数据更新到另一张表 四、WHERE和HAVING的区别 一、简述SQL中如何将“行”转换为“列” 我们以MySQL数据库为例,来说明行转列的实现方式。 首先,假设我们有一张分…

WAIC 2024:科技界的摇滚狂欢,你错过了什么?

大数据产业创新服务媒体 ——聚焦数据 改变商业 2024年7月5日,WAIC 2024举办的第二天。数据猿作为受邀媒体,在今天继续亲历这一场关于未来的盛会。在这片汇聚了全球顶尖科技力量的舞台上,见证了人工智能领域的最新成果,感受到了科…

Midjourney对图片细微调整和下载保存

点击v2是对第二图片细微调整。 点击u3对第3张图片进行放大。 保存图片: 对点击u3放大的图片,双击 , 右键保存图片

hdu物联网硬件实验3 按键和中断

学院 班级 学号 姓名 日期 成绩 实验题目 按键和中断 实验目的 实现闪灯功能转换 硬件原理 无 关键代码及注释 /* Button Turns on and off a light emitting diode(LED) connected to digital pin 13, when pressing a pushbutton attached…

招聘一个1-3年经验的Java工程师:企业视角的技能与素质要求

个人名片 🎓作者简介:java领域优质创作者 🌐个人主页:码农阿豪 📞工作室:新空间代码工作室(提供各种软件服务) 💌个人邮箱:[2435024119qq.com] &#x1f4f1…

Spring的核心基础:感受一下对象工厂

“欢迎来到Spring!”的小项目 (1)写一个HelloSpring的类,采用setter方法注入userName,写一个简单的show方法。 package com.itzhoutao; public class HelloSpring{private String userName;public void setUserName…

Spring源码十一:事件驱动

上一篇Spring源码十:BeanPostProcess中,我们介绍了BeanPostProcessor是Spring框架提供的一个强大工具,它允许我们开发者在Bean的生命周期中的特定点进行自定义操作。通过实现BeanPostProcessor接口,开发者可以插入自己的逻辑&…

核心实验:基于Web前端的性能测试分析!

实验简介 本实验主要利用IE和Chrome的F12开发人员工具结合Web前端测试分析相关知识,对常见网站进行基于前端的性能测试分析,本实验将不会使用到测试开发相关技术,而是纯粹意义上的手工测试,但却是很容易找到系统前端性能及设计问…

AI行业的非零和博弈:解读Mustafa Suleyman的观点

引言 在人工智能(AI)领域,微软AI公司的CEO Mustafa Suleyman最近在阿斯彭思想节上的访谈引起了广泛关注。与CNBC记者Andrew Ross Sorkin的对话中,Suleyman不仅分享了他对OpenAI人事变动的看法,还深入探讨了AI行业的现…