【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.appimport android.os.Bundle
import androidx.appcompat.app.AppCompatActivityclass 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.appimport 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.SkinnerAssetManagertypealias androidStyleableRes = androidx.appcompat.R.styleableclass 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) = nullprivate 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.mzph.cn/web/41017.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

安全测试之使用Docker搭建SQL注入安全测试平台sqli-labs

1 搜索镜像 docker search sqli-labs 2 拉取镜像 docker pull acgpiano/sqli-labs 3 创建docker容器 docker run -d --name sqli-labs -p 10012:80 acgpiano/sqli-labs 4 访问测试平台网站 若直接使用虚拟机,则直接通过ip端口号访问若通过配置域名&#xff0…

PyQt多线程详解

PyQt多线程是在PyQt框架中利用多线程技术来提高应用程序的响应性和性能的一种方法。下面将详细说明PyQt多线程的基本概念、应用场景以及实现方式。 一、PyQt多线程的基本概念 在PyQt中,多线程指的是在单个程序实例内同时运行多个线程。每个线程都可以执行不同的任…

第十五章 Nest Pipe(内置及自定义)

NestJS的Pipe是一个用于数据转换和验证的特殊装饰器。Pipe可以应用于控制器(Controller)的处理方法(Handler)和中间件(Middleware),用于处理传入的数据。它可以用来转换和验证数据,确…

【Linux进阶】文件系统5——ext2文件系统(inode)

1.再谈inode (1) 理解inode,要从文件储存说起。 文件储存在硬盘上,硬盘的最小存储单位叫做"扇区"(Sector)。每个扇区储存512字节(相当于0.5KB)。操作系统读取硬盘的时候,不会一个个…

记录excel表生成一列按七天一个周期的方法

使用excel生成每七天一个周期的列。如下图所示: 针对第一列的生成办法,使用如下函数: TEXT(DATE(2024,1,1)(ROW()-2)*7,"yyyy/m/d")&" - "&TEXT(DATE(2024,1,1)(ROW()-1)*7-1,"yyyy/m/d") 特此记录。…

charles使用教程

安装与配置 下载链接:https://www.charlesproxy.com/download/ 进行移动端抓包: 电脑端配置: 关闭防火墙 Proxy–>勾选 macOS Proxy Proxy–>Proxy Setting–>填入代理端口8888–>勾选Enable transparent http proxying 安装c…

俄罗斯方块的python实现

俄罗斯方块游戏是一种经典的拼图游戏,玩家需要将不同形状的方块拼接在一起,使得每一行都被完全填满,从而清除这一行并获得积分。以下是该游戏的算法描述: 1. 初始化 初始化游戏界面,设置屏幕大小、方块大小、网格大小…

昇思25天学习打卡营第1天|初识MindSpore

# 打卡 day1 目录 # 打卡 day1 初识MindSpore 昇思 MindSpore 是什么? 昇思 MindSpore 优势|特点 昇思 MindSpore 不足 官方生态学习地址 初识MindSpore 昇思 MindSpore 是什么? 昇思MindSpore 是全场景深度学习架构,为开发者提供了全…

女生学计算机好不好?感觉计算机分有点高……?

众所周知,在国内的高校里,计算机专业的女生是非常少的,很多小班30人左右,但是每个班女生人数只有个位数。这就给很多人一个感觉,是不是女生天生就不适合学这个东西呢?女生是不是也应该放弃呢?当…

ubuntu 进入命令行

在Ubuntu中,有几种方法可以进入命令行界面: 启动时选择命令行模式: 在计算机启动时,如果安装了GRUB引导加载器,可以通过GRUB菜单选择进入命令行模式。这通常涉及到在启动时按下Shift键或其他指定键来显示GRUB菜单&…

常见算法和Lambda

常见算法和Lambda 文章目录 常见算法和Lambda常见算法查找算法基本查找(顺序查找)二分查找/折半查找插值查找斐波那契查找分块查找扩展的分块查找(无规律的数据) 常见排序算法冒泡排序选择排序插入排序快速排序递归快速排序 Array…

SpringBoot新手快速入门系列教程二:MySql5.7.44的免安装版本下载和配置,以及简单的Mysql生存指令指南。

我们要如何选择MySql 目前主流的Mysql有5.0、8.0、9.0 主要区别 MySQL 5.0 发布年份:2005年特性: 基础事务支持存储过程、触发器、视图基础存储引擎(如MyISAM、InnoDB)外键支持基本的全文搜索性能和扩展性: 相对较…

2024年江苏省研究生数学建模竞赛B题火箭烟幕弹运用策略优化论文和代码分析

经过不懈的努力, 2024年江苏省研究生数学建模竞赛B题火箭烟幕弹运用策略优化论文和代码已完成,代码为B题全部问题的代码,论文包括摘要、问题重述、问题分析、模型假设、符号说明、模型的建立和求解(问题1模型的建立和求解、问题2模…

[学习笔记]SQL学习笔记(连载中。。。)

学习视频:【数据库】SQL 3小时快速入门 #数据库教程 #SQL教程 #MySQL教程 #database#Python连接数据库 目录 1.SQL的基础知识1.1.表(table)和键(key)1.2.外键、联合主键 2.MySQL安装(略,请自行参考视频)3.基本的MySQL语法3.1.规…

进程控制-fork函数

一个进程,包括代码、数据和分配给进程的资源。 fork ()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同…

DatawhaleAI夏令营2024 Task2

#AI夏令营 #Datawhale #夏令营 赛题解析一、Baseline详解1.1 环境配置1.2 数据处理任务理解2.3 prompt设计2.4 数据抽取 二、完整代码总结 赛题解析 赛事背景 在数字化时代,企业积累了大量对话数据,这些数据不仅是交流记录,还隐藏着宝贵的信…

【鸿蒙学习笔记】@Link装饰器:父子双向同步

官方文档:Link装饰器:父子双向同步 目录标题 [Q&A] Link装饰器作用 [Q&A] Link装饰器特点样例:简单类型样例:数组类型样例:Map类型样例:Set类型样例:联合类型 [Q&A] Link装饰器作用…

信号与系统-实验6-离散时间系统的 Z 域分析

一、实验目的 1、掌握 z 变换及其性质;了解常用序列的 z 变换、逆 z 变换; 2、掌握利用 MATLAB 的符号运算实现 z 变换; 3、掌握利用 MATLAB 绘制离散系统零、极点图的方法; 4、掌握利用 MATLAB 分析离散系统零、极点的方法&a…

字符串中的注意事项

在比较早的C/C版本中,经常可以看到推荐使用gets函数来进行整行字符串的输入,就像下面这样的简单写法即可输入一整行: C gets(str);但是当输入的字符串长度超过数组长度上限MAX_LEN时,gets函数会把超出的部分也一并读进来&#x…

MySQL基础篇(二)字符集以及校验规则

在MySQL基础篇(一)中,我们知道了如何创建数据库,这篇文章带大家了解创建的一些细节。 红色框:可省略,作用如果存在相同的数据库名称,就不会再创建,反之,创建。 蓝色框&…