【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.pswp.cn/web/41017.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/41017.shtml
英文地址,請注明出處:http://en.pswp.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基礎篇(一)中,我們知道了如何創建數據庫,這篇文章帶大家了解創建的一些細節。 紅色框:可省略,作用如果存在相同的數據庫名稱,就不會再創建,反之,創建。 藍色框&…