版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領(lǐng)
文檔簡介
【移動應(yīng)用開發(fā)技術(shù)】Android換膚的原理是什么
Android換膚的原理是什么,針對這個問題,這篇文章詳細介紹了相對應(yīng)的分析和解答,希望可以幫助更多想解決這個問題的小伙伴找到更簡單易行的方法。換膚介紹換膚本質(zhì)上是對資源的一中替換包括、字體、顏色、背景、圖片、大小等等。當(dāng)然這些我們都有成熟的api可以通過控制代碼邏輯做到。比如View的修改背景顏色
setBackgroundColor,TextView的setTextSize
修改字體等等。但是作為程序員我們怎么能忍受對每個頁面的每個元素一個行行代碼做換膚處理呢?我們需要用最少的代碼實現(xiàn)最容易維護和使用效果***(動態(tài)切換,及時生效)的換膚框架。換膚方式一:切換使用主題Theme使用相同的資源id,但在不同的Theme下邊自定義不同的資源。我們通過主動切換到不同的Theme從而切換界面元素創(chuàng)建時使用的資源。這種方案的代碼量不多發(fā),而且有個很明顯的缺點不支持已經(jīng)創(chuàng)建界面的換膚,必須重新加載界面元素。
GitHubDemo換膚方式二:加載資源包加載資源包是各種應(yīng)用程序都在使用的換膚方法,例如我們最常用的輸入法皮膚、瀏覽器皮膚等等。我們可以將皮膚的資源文件放入安裝包內(nèi)部,也可以進行下載緩存到磁盤上。Android的應(yīng)用程序可以使用這種方式進行換膚。GitHub上面有一個start非常高的換膚框架
Android-Skin-Loader就是通過加載資源包對app進行換膚。對這個框架的分析這個也是這篇文章主要的講述內(nèi)容。對比一下發(fā)現(xiàn)切換Theme可以進行小幅度的換膚設(shè)置(比如某個自定義組件的主題),而如果我們想要對整個app做主題切換那么通過加載資源包的這種方式目前應(yīng)該說是比較好的了。Android換膚知識點換膚相應(yīng)的API我們先來看一下Android提供的一些基本的api,通過使用這些api可以在App內(nèi)部進行資源對象的替換。public
class
Resources{
public
String
getString(int
id)throws
NotFoundException
{
CharSequence
res
=
mAssets.getResourceText(id);
if
(res
!=
null)
{
return
res;
}
throw
new
NotFoundException("String
resource
ID
#0x"
+
Integer.toHexString(id));
}
public
Drawable
getDrawable(int
id)throws
NotFoundException
{
/********部分代碼省略*******/
}
public
int
getColor(int
id)throws
NotFoundException
{{
/********部分代碼省略*******/
}
/********部分代碼省略*******/
}這個是我們常用的Resources類的api,我們通??梢允褂迷谫Y源文件中定義的@+id
String類型,然后在編譯出的R.java中對應(yīng)的資源文件生產(chǎn)的id(int類型),從而通過這個id(int類型)調(diào)用Resources提供的這些api獲取到對應(yīng)的資源對象。這個在同一個app下沒有任何問題,但是在皮膚包中我們怎么獲取這個id值呢。public
class
Resources{
/********部分代碼省略*******/
/**
*
通過給的資源名稱返回一個資源的標(biāo)識id。
*@paramname
描述資源的名稱
*@paramdefType
資源的類型
*@paramdefPackage
包名
*
*@return返回資源id,0標(biāo)識未找到該資源
*/
public
int
getIdentifier(String
name,
String
defType,
String
defPackage){
if
(name
==
null)
{
throw
new
NullPointerException("name
is
null");
}
try
{
return
Integer.parseInt(name);
}
catch
(Exception
e)
{
//
Ignore
}
return
mAssets.getResourceIdentifier(name,
defType,
defPackage);
}
}Resources提供了可以通過@+id
、Type、PackageName這三個參數(shù)就可以在AssetManager中尋找相應(yīng)的PackageName中有沒有Type類型并且id值都能與參數(shù)對應(yīng)上的id,進行返回。然后我們可以通過這個id再調(diào)用Resource的獲取資源的api就可以得到相應(yīng)的資源。這里我們需要注意的一點是getIdentifier(Stringname,StringdefType,StringdefPackage)
方法和getString(intid)
方法所調(diào)用Resources對象的mAssets對象必須是同一個,并且包含有PackageName這個資源包。AssetManager構(gòu)造怎么構(gòu)造一個包含特定packageName資源的AssetManager對象實例呢?public
final
class
AssetManagerimplements
AutoCloseable{
/********部分代碼省略*******/
/**
*
Create
a
new
AssetManager
containing
only
the
basic
system
assets.
*
Applications
will
not
generally
use
this
method,
instead
retrieving
the
*
appropriate
asset
manager
with
{@linkResources#getAssets}.
Not
for
*
use
by
applications.
*
{@hide}
*/
public
AssetManager(){
synchronized
(this)
{
if
(DEBUG_REFS)
{
mNumRefs
=
0;
incRefsLocked(this.hashCode());
}
init(false);
if
(localLOGV)
Log.v(TAG,
"New
asset
manager:
"
+
this);
ensureSystemAssets();
}
}從AssetManager的構(gòu)造函數(shù)來看有{@hide}
的朱姐,所以在其他類里面是直接創(chuàng)建AssetManager實例。但是不要忘記Java中還有反射機制可以創(chuàng)建類對象。AssetManager
assetManager
=
AssetManager.class.newInstance();讓創(chuàng)建的assetManager包含特定的PackageName的資源信息,怎么辦?我們在AssetManager中找到相應(yīng)的api可以調(diào)用。public
final
class
AssetManagerimplements
AutoCloseable{
/********部分代碼省略*******/
/**
*
Add
an
additional
set
of
assets
to
the
asset
manager.
This
can
be
*
either
a
directory
or
ZIP
file.
Not
for
use
by
applications.
Returns
*
the
cookie
of
the
added
asset,
or
0
on
failure.
*
{@hide}
*/
public
final
int
addAssetPath(String
path){
synchronized
(this)
{
int
res
=
addAssetPathNative(path);
if
(mStringBlocks
!=
null)
{
makeStringBlocks(mStringBlocks);
}
return
res;
}
}
}同樣改方法也不支持外部調(diào)用,我們只能通過反射的方法來調(diào)用。/**
*
apk路徑
*/
String
apkPath
=
Environment.getExternalStorageDirectory()+"/skin.apk";
AssetManager
assetManager
=
null;
try
{
AssetManager
assetManager
=
AssetManager.class.newInstance();
AssetManager.class.getDeclaredMethod("addAssetPath",
String.class).invoke(assetManager,
apkPath);
}
catch
(Throwable
th)
{
th.printStackTrace();
}至此我們可以構(gòu)造屬于自己換膚的Resources了。換膚Resources構(gòu)造public
Resources
getSkinResources(Context
context){
/**
*
插件apk路徑
*/
String
apkPath
=
Environment.getExternalStorageDirectory()+"/skin.apk";
AssetManager
assetManager
=
null;
try
{
AssetManager
assetManager
=
AssetManager.class.newInstance();
AssetManager.class.getDeclaredMethod("addAssetPath",
String.class).invoke(assetManager,
apkPath);
}
catch
(Throwable
th)
{
th.printStackTrace();
}
return
new
Resources(assetManager,
context.getResources().getDisplayMetrics(),
context.getResources().getConfiguration());
}使用資源包中的資源換膚我們將上述所有的代碼組合在一起就可以實現(xiàn),使用資源包中的資源對app進行換膚。public
Resources
getSkinResources(Context
context){
/**
*
插件apk路徑
*/
String
apkPath
=
Environment.getExternalStorageDirectory()+"/skin.apk";
AssetManager
assetManager
=
null;
try
{
AssetManager
assetManager
=
AssetManager.class.newInstance();
AssetManager.class.getDeclaredMethod("addAssetPath",
String.class).invoke(assetManager,
apkPath);
}
catch
(Throwable
th)
{
th.printStackTrace();
}
return
new
Resources(assetManager,
context.getResources().getDisplayMetrics(),
context.getResources().getConfiguration());
}
@Override
protected
void
onCreate(Bundle
savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ImageView
imageView
=
(ImageView)
findViewById(R.id.imageView);
TextView
textView
=
(TextView)
findViewById(R.id.text);
/**
*
插件資源對象
*/
Resources
resources
=
getSkinResources(this);
/**
*
獲取圖片資源
*/
Drawable
drawable
=
resources.getDrawable(resources.getIdentifier("night_icon",
"drawable","com.tzx.skin"));
/**
*
獲取文本資源
*/
int
color
=
resources.getColor(resources.getIdentifier("night_color","color","com.tzx.skin"));
imageView.setImageDrawable(drawable);
textView.setText(text);
}通過上述介紹,我們可以簡單的對當(dāng)前頁面進行換膚了。但是想要做出一個一個成熟換膚框架那么僅僅這些還是不夠的,提高一下我們的思維高度,如果我們在View創(chuàng)建的時候就直接使用皮膚資源包中的資源文件,那么這無疑就使換膚更加的簡單已維護。LayoutInflater.Factory看過我前一篇遇見LayoutInflater&Factory文章的這部分可以省略掉.很幸運Android給我們在View生產(chǎn)的時候做修改提供了法門。public
abstract
class
LayoutInflater{
/***部分代碼省略****/
public
interface
Factory{
public
View
onCreateView(String
name,
Context
context,
AttributeSet
attrs);
}
public
interface
Factory2extends
Factory{
public
View
onCreateView(View
parent,
String
name,
Context
context,
AttributeSet
attrs);
}
/***部分代碼省略****/
}我們可以給當(dāng)前的頁面的Window對象在創(chuàng)建的時候設(shè)置Factory,那么在Window中的View進行創(chuàng)建的時候就會先通過自己設(shè)置的Factory進行創(chuàng)建。Factory使用方式和相關(guān)注意事項請移位到
遇見LayoutInflater&Factory,關(guān)于Factory的相關(guān)知識點盡在其中。Android-Skin-Loader解析初始化初始化換膚框架,導(dǎo)入需要換膚的資源包(當(dāng)前為一個apk文件,其中只有資源文件)。public
class
SkinApplicationextends
Application{
public
void
onCreate(){
super.onCreate();
initSkinLoader();
}
/**
*
Must
call
init
first
*/
private
void
initSkinLoader(){
SkinManager.getInstance().init(this);
SkinManager.getInstance().load();
}
}構(gòu)造換膚對象導(dǎo)入需要換膚的資源包,并構(gòu)造換膚的Resources實例。/**
*
Load
resources
from
apk
in
asyc
task
*@paramskinPackagePath
path
of
skin
apk
*@paramcallback
callback
to
notify
user
*/
public
void
load(String
skinPackagePath,final
ILoaderListener
callback){
new
AsyncTask<String,
Void,
Resources>()
{
protected
void
onPreExecute(){
if
(callback
!=
null)
{
callback.onStart();
}
};
@Override
protected
Resources
doInBackground(String...
params){
try
{
if
(params.length
==
1)
{
String
skinPkgPath
=
params[0];
File
file
=
new
File(skinPkgPath);
if(file
==
null
||
!file.exists()){
return
null;
}
PackageManager
mPm
=
context.getPackageManager();
//檢索程序外的一個安裝包文件
PackageInfo
mInfo
=
mPm.getPackageArchiveInfo(skinPkgPath,
PackageManager.GET_ACTIVITIES);
//獲取安裝包報名
skinPackageName
=
mInfo.packageName;
//構(gòu)建換膚的AssetManager實例
AssetManager
assetManager
=
AssetManager.class.newInstance();
Method
addAssetPath
=
assetManager.getClass().getMethod("addAssetPath",
String.class);
addAssetPath.invoke(assetManager,
skinPkgPath);
//構(gòu)建換膚的Resources實例
Resources
superRes
=
context.getResources();
Resources
skinResource
=
new
Resources(assetManager,superRes.getDisplayMetrics(),superRes.getConfiguration());
//存儲當(dāng)前皮膚路徑
SkinConfig.saveSkinPath(context,
skinPkgPath);
skinPath
=
skinPkgPath;
isDefaultSkin
=
false;
return
skinResource;
}
return
null;
}
catch
(Exception
e)
{
e.printStackTrace();
return
null;
}
};
protected
void
onPostExecute(Resources
result){
mResources
=
result;
if
(mResources
!=
null)
{
if
(callback
!=
null)
callback.onSuccess();
//更新多有可換膚的界面
notifySkinUpdate();
}else{
isDefaultSkin
=
true;
if
(callback
!=
null)
callback.onFailed();
}
};
}.execute(skinPackagePath);
}定義基類換膚頁面的基類的通用代碼實現(xiàn)基本換膚功能。public
class
BaseFragmentActivityextends
FragmentActivityimplements
ISkinUpdate,IDynamicNewView{
/***部分代碼省略****/
//自定義LayoutInflater.Factory
private
SkinInflaterFactory
mSkinInflaterFactory;
@Override
protected
void
onCreate(Bundle
savedInstanceState){
super.onCreate(savedInstanceState);
try
{
//設(shè)置LayoutInflater的mFactorySet為true,表示還未設(shè)置mFactory,否則會拋出異常。
Field
field
=
LayoutInflater.class.getDeclaredField("mFactorySet");
field.setAccessible(true);
field.setBoolean(getLayoutInflater(),
false);
//設(shè)置LayoutInflater的MFactory
mSkinInflaterFactory
=
new
SkinInflaterFactory();
getLayoutInflater().setFactory(mSkinInflaterFactory);
}
catch
(NoSuchFieldException
e)
{
e.printStackTrace();
}
catch
(IllegalArgumentException
e)
{
e.printStackTrace();
}
catch
(IllegalAccessException
e)
{
e.printStackTrace();
}
}
@Override
protected
void
onResume(){
super.onResume();
//注冊皮膚管理對象
SkinManager.getInstance().attach(this);
}
@Override
protected
void
onDestroy(){
super.onDestroy();
//反注冊皮膚管理對象
SkinManager.getInstance().detach(this);
}
/***部分代碼省略****/
}SkinInflaterFactorySkinInflaterFactory進行View的創(chuàng)建并對View進行換膚。構(gòu)造Viewpublic
class
SkinInflaterFactoryimplements
Factory{
/***部分代碼省略****/
public
View
onCreateView(String
name,
Context
context,
AttributeSet
attrs){
//讀取View的skin:enable屬性,false為不需要換膚
//
if
this
is
NOT
enable
to
be
skined
,
simplly
skip
it
boolean
isSkinEnable
=
attrs.getAttributeBooleanValue(SkinConfig.NAMESPACE,
SkinConfig.ATTR_SKIN_ENABLE,
false);
if
(!isSkinEnable){
return
null;
}
//創(chuàng)建View
View
view
=
createView(context,
name,
attrs);
if
(view
==
null){
return
null;
}
//如果View創(chuàng)建成功,對View進行換膚
parseSkinAttr(context,
attrs,
view);
return
view;
}
//創(chuàng)建View,類比可以查看LayoutInflater的createViewFromTag方法
private
View
createView(Context
context,
String
name,
AttributeSet
attrs){
View
view
=
null;
try
{
if
(-1
==
name.indexOf('.')){
if
("View".equals(name))
{
view
=
LayoutInflater.from(context).createView(name,
"android.view.",
attrs);
}
if
(view
==
null)
{
view
=
LayoutInflater.from(context).createView(name,
"android.widget.",
attrs);
}
if
(view
==
null)
{
view
=
LayoutInflater.from(context).createView(name,
"android.webkit.",
attrs);
}
}else
{
view
=
LayoutInflater.from(context).createView(name,
null,
attrs);
}
L.i("about
to
create
"
+
name);
}
catch
(Exception
e)
{
L.e("error
while
create
【"
+
name
+
"】
:
"
+
e.getMessage());
view
=
null;
}
return
view;
}
}對生產(chǎn)的View進行換膚public
class
SkinInflaterFactoryimplements
Factory{
//存儲當(dāng)前Activity中的需要換膚的View
private
List<SkinItem>
mSkinItems
=
new
ArrayList<SkinItem>();
/***部分代碼省略****/
private
void
parseSkinAttr(Context
context,
AttributeSet
attrs,
View
view){
//當(dāng)前View的所有屬性標(biāo)簽
List<SkinAttr>
viewAttrs
=
new
ArrayList<SkinAttr>();
for
(int
i
=
0;
i
<
attrs.getAttributeCount();
i++){
String
attrName
=
attrs.getAttributeName(i);
String
attrValue
=
attrs.getAttributeValue(i);
if(!AttrFactory.isSupportedAttr(attrName)){
continue;
}
//過濾view屬性標(biāo)簽中屬性的value的值為引用類型
if(attrValue.startsWith("@")){
try
{
int
id
=
Integer.parseInt(attrValue.substring(1));
String
entryName
=
context.getResources().getResourceEntryName(id);
String
typeName
=
context.getResources().getResourceTypeName(id);
//構(gòu)造SkinAttr實例,attrname,id,entryName,typeName
//屬性的名稱(background)、屬性的id值(int類型),屬性的id值(@+id,string類型),屬性的值類型(color)
SkinAttr
mSkinAttr
=
AttrFactory.get(attrName,
id,
entryName,
typeName);
if
(mSkinAttr
!=
null)
{
viewAttrs.add(mSk
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 2025年中職人工智能工程技術(shù)(AI基礎(chǔ)應(yīng)用)試題及答案
- 2025年高職(農(nóng)產(chǎn)品加工與質(zhì)量檢測)微生物檢測基礎(chǔ)試題及答案
- 2026年寫字樓服務(wù)(會議組織流程)試題及答案
- 2025年高職教育技術(shù)學(xué)(多媒體教學(xué)資源制作)試題及答案
- 2025年中職播音與主持藝術(shù)(播音與主持教學(xué)法)試題及答案
- 2025年中職旅游服務(wù)與管理(景區(qū)講解技巧)試題及答案
- 2025年大學(xué)大一(播音與主持藝術(shù))節(jié)目策劃與制作綜合測試題及答案
- 2025年中職會計(稅務(wù)申報基礎(chǔ))試題及答案
- 2025年大學(xué)第一學(xué)年(材料成型及控制工程)焊接材料學(xué)試題及答案
- 2025年中職(會計基礎(chǔ))賬務(wù)核算階段測試試題及答案
- 電力設(shè)施的綠色設(shè)計與可持續(xù)發(fā)展
- 小型農(nóng)場研學(xué)課課程設(shè)計
- GB/T 3487-2024乘用車輪輞規(guī)格系列
- 第四單元“小說天地”(主題閱讀)-2024-2025學(xué)年六年級語文上冊閱讀理解(統(tǒng)編版)
- 蔣詩萌小品《誰殺死了周日》臺詞完整版
- 中醫(yī)培訓(xùn)課件:《中藥熱奄包技術(shù)》
- 2024年全國初中數(shù)學(xué)聯(lián)合競賽試題參考答案及評分標(biāo)準(zhǔn)
- 七年級上信息科技期末測試卷
- 起重機械的安全圍擋與隔離區(qū)域
- 車輛運用管理工作-認識車輛部門組織機構(gòu)(鐵道車輛管理)
- 22S803 圓形鋼筋混凝土蓄水池
評論
0/150
提交評論