【移動應(yīng)用開發(fā)技術(shù)】Android換膚的原理是什么_第1頁
【移動應(yīng)用開發(fā)技術(shù)】Android換膚的原理是什么_第2頁
【移動應(yīng)用開發(fā)技術(shù)】Android換膚的原理是什么_第3頁
【移動應(yīng)用開發(fā)技術(shù)】Android換膚的原理是什么_第4頁
【移動應(yīng)用開發(fā)技術(shù)】Android換膚的原理是什么_第5頁
已閱讀5頁,還剩5頁未讀, 繼續(xù)免費閱讀

付費下載

下載本文檔

版權(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)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

最新文檔

評論

0/150

提交評論