主目錄見:Android高級進階知識(這是總目錄索引)
?終于迎來我們的換膚框架最終章了梢睛,前面我們也學了support v7的源碼了召娜,那么今天我們這里就輕松很多闸与,廢話不多說洋满,直接開始講解剩晴。想要源碼的直接[點擊下載],這老哥穩(wěn)B嘀洹!赞弥!大家向他學習毅整。
一.目標
看過前面幾篇的問題都知道,文章前面會有目標說明绽左,今天不例外悼嫉。
1.復習《Support v7庫》的知識點。
2.說下framework怎么加載資源拼窥。
3.同時說明下Application的registerActivityLifecycleCallbacks方法戏蔑。
二.源碼分析
1.基礎使用
在Application的onCreate中初始化:
SkinCompatManager.withoutActivity(this) // 基礎控件換膚初始化
.addStrategy(new CustomSDCardLoader()) // 自定義加載策略,指定SDCard路徑[可選]
.addHookInflater(new SkinHookAutoLayoutViewInflater()) // hongyangAndroid/AndroidAutoLayout[可選]
.addInflater(new SkinMaterialViewInflater()) // material design 控件換膚初始化[可選]
.addInflater(new SkinConstraintViewInflater()) // ConstraintLayout 控件換膚初始化[可選]
.addInflater(new SkinCardViewInflater()) // CardView v7 控件換膚初始化[可選]
.addInflater(new SkinCircleImageViewInflater()) // hdodenhof/CircleImageView[可選]
.addInflater(new SkinFlycoTabLayoutInflater()) // H07000223/FlycoTabLayout[可選]
.setSkinStatusBarColorEnable(false) // 關(guān)閉狀態(tài)欄換膚鲁纠,默認打開[可選]
.setSkinWindowBackgroundEnable(false) // 關(guān)閉windowBackground換膚总棵,默認打開[可選]
.loadSkin();
加載插件皮膚庫:
// 指定皮膚插件
SkinCompatManager.getInstance().loadSkin("new.skin"[, SkinLoaderListener], int strategy);
// 恢復應用默認皮膚
SkinCompatManager.getInstance().restoreDefaultTheme();
本來以前庫是要繼承SkinCompatActivity的,但是現(xiàn)在用了registerActivityLifecycleCallbacks監(jiān)聽了Activity的生命周期改含,這樣可以燥起來情龄,就不用繼承了,等會會說明捍壤。
2.SkinCompatManager withoutActivity
這個地方遵循看源碼的一貫步驟骤视,我們來看下withoutActivity方法到底是干了啥?方法如下:
public static SkinCompatManager withoutActivity(Application application) {
init(application);
SkinActivityLifecycle.init(application);
return sInstance;
}
這個方法總共就兩個方法鹃觉,我們順序來看init方法干了啥:
public static SkinCompatManager init(Context context) {
if (sInstance == null) {
synchronized (SkinCompatManager.class) {
if (sInstance == null) {
sInstance = new SkinCompatManager(context);
}
}
}
return sInstance;
}
這個方法其實就是創(chuàng)建一個SkinCompatManager這個單例對象专酗,那我們就看這個構(gòu)造函數(shù)里面做了些啥:
private SkinCompatManager(Context context) {
mAppContext = context.getApplicationContext();
SkinPreference.init(mAppContext);
SkinCompatResources.init(mAppContext);
initLoaderStrategy();
}
這個方法主要是做一些初始化工作,第一個類SkinPreference其實就是對SharePreference的封裝盗扇,SkinCompatResources其實就是存放包名祷肯,皮膚名,加載策略(就是從什么途徑加載)等等疗隶,同時有一些加載資源的方法躬柬。最后initLoaderStrategy()方法是加載默認的皮膚包加載策略。
?看完第一個方法抽减,我們現(xiàn)在看第二個方法SkinActivityLifecycle.init(application)允青,這個方法非常關(guān)鍵,我們先看看init做了啥?
public static SkinActivityLifecycle init(Application application) {
if (sInstance == null) {
synchronized (SkinActivityLifecycle.class) {
if (sInstance == null) {
sInstance = new SkinActivityLifecycle(application);
}
}
}
return sInstance;
}
首先也是初始化一個SkinActivityLifecycle單例類颠锉,我們也直接跟進構(gòu)造函數(shù)里面:
private SkinActivityLifecycle(Application application) {
application.registerActivityLifecycleCallbacks(this);
}
這個地方就是一句話法牲,但是非常關(guān)鍵,記得在LeakCanary源碼里面也有用到這個方法琼掠。這個方法是攔截Acitivity的生命周期方法來進行統(tǒng)一處理拒垃,這樣的話我們不用讓我們的Acitivity繼承SkinCompatActivity(所以現(xiàn)在這個類被標記為廢棄),就可以在每個Acitivity創(chuàng)建的時候做點動作了,非常管用瓷蛙。具體的Activity的生命周期里面做了哪些動作悼瓮,我們下面會重點講解。
3.SkinCompatManager loadSkin
這個方法是用來加載皮膚包的艰猬,我們這里先看loadSkin做了些什么東西横堡。
public AsyncTask loadSkin() {
String skin = SkinPreference.getInstance().getSkinName();
int strategy = SkinPreference.getInstance().getSkinStrategy();
if (TextUtils.isEmpty(skin) || strategy == SKIN_LOADER_STRATEGY_NONE) {
return null;
}
return loadSkin(skin, null, strategy);
}
我們看到這個地方,首先獲取本地是否有皮膚包名稱和皮膚包策略的記錄冠桃,如果有則取出來進行加載命贴。
public AsyncTask loadSkin(String skinName, SkinLoaderListener listener, int strategy) {
return new SkinLoadTask(listener, mStrategyMap.get(strategy)).execute(skinName);
}
這個地方用到了SkinLoadTask來進行加載,SkinLoadTask是一個AsyncTask對象食听,所以我們先看onPreExecute()方法:
protected void onPreExecute() {
if (mListener != null) {
mListener.onStart();
}
}
這個方法沒有做啥胸蛛,就是調(diào)用接口的onStart方法,但是我們前面創(chuàng)建來的Listener是null樱报,所以這個地方?jīng)]有調(diào)用葬项。接下來我們看到doInBackground()方法:
@Override
protected String doInBackground(String... params) {
synchronized (mLock) {
while (mLoading) {
try {
mLock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
mLoading = true;
}
try {
if (params.length == 1) {
if (TextUtils.isEmpty(params[0])) {
SkinCompatResources.getInstance().reset();
return params[0];
}
if (!TextUtils.isEmpty(
mStrategy.loadSkinInBackground(mAppContext, params[0]))) {
return params[0];
}
}
} catch (Exception e) {
e.printStackTrace();
}
SkinCompatResources.getInstance().reset();
return null;
}
這個方法前面加了一個同步代碼塊進行同步操作,然后我們看到調(diào)用了SkinCompatResources.getInstance().reset();將這個對象之前存進去的皮膚包名稱迹蛤,策略等信息重置民珍。接著調(diào)用mStrategy.loadSkinInBackground(mAppContext, params[0])方法,這個地方mStrategy就是加載策略笤受,內(nèi)置了三個策略分別為:SkinAssetsLoader穷缤,SkinBuildInLoader敌蜂,SkinSDCardLoader箩兽,第一個就是從Assets中加載皮膚包,第二個就是從本應用中加載皮膚包章喉,第三個是從SD卡中加載皮膚包汗贫。這個地方我們用從SD卡中加載皮膚包為例子,因為這個場景用到還比較多秸脱。
4.SkinSDCardLoader loadSkinInBackground
這個類是個抽象類落包,為什么設置為抽象類,是因為SD卡的目錄作者希望留給用戶自己設置摊唇。我們直接進入到這個類的loadSkinInBackground方法:
public String loadSkinInBackground(Context context, String skinName) {
String skinPkgPath = getSkinPath(context, skinName);
if (SkinFileUtils.isFileExists(skinPkgPath)) {
String pkgName = SkinCompatManager.getInstance().getSkinPackageName(skinPkgPath);
Resources resources = SkinCompatManager.getInstance().getSkinResources(skinPkgPath);
if (resources != null && !TextUtils.isEmpty(pkgName)) {
SkinCompatResources.getInstance().setupSkin(
resources,
pkgName,
skinName,
this);
return skinName;
}
}
return null;
}
我們看到第一句就是獲取皮膚包的路徑咐蝇,這個方法由用戶自己指定,我們可以集成這個類進行重寫巷查。然后程序判斷目錄是否存在有序,如果存在則獲取皮膚包的包名:
public String getSkinPackageName(String skinPkgPath) {
PackageManager mPm = mAppContext.getPackageManager();
PackageInfo info = mPm.getPackageArchiveInfo(skinPkgPath, PackageManager.GET_ACTIVITIES);
return info.packageName;
}
然后獲取Resources對象:
@Nullable
public Resources getSkinResources(String skinPkgPath) {
try {
AssetManager assetManager = AssetManager.class.newInstance();
Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
addAssetPath.invoke(assetManager, skinPkgPath);
Resources superRes = mAppContext.getResources();
return new Resources(assetManager, superRes.getDisplayMetrics(), superRes.getConfiguration());
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
這個地方有個知識點:就是系統(tǒng)是怎么加載資源的抹腿。要詳細可以看Android應用程序資源管理器(Asset Manager)的創(chuàng)建過程分析 和Android應用程序資源的查找過程分析 。這個地方我們簡單歸納就是會調(diào)用AssetManager里面的final方法addAssetPath()旭寿,所以我們傳進皮膚包的路徑警绩,反射調(diào)用這個方法進行添加即可。這樣我們的外部皮膚包就被加進去了盅称。
5.SkinLoadTask onPostExecute
看完onPreExecute()方法和doInBackground()方法我們就來看onPostExecute方法了:
protected void onPostExecute(String skinName) {
SkinLog.e("skinName = " + skinName);
synchronized (mLock) {
// skinName 為""時肩祥,恢復默認皮膚
if (skinName != null) {
SkinPreference.getInstance().setSkinName(skinName).setSkinStrategy(mStrategy.getType()).commitEditor();
notifyUpdateSkin();
if (mListener != null) mListener.onSuccess();
} else {
SkinPreference.getInstance().setSkinName("").setSkinStrategy(SKIN_LOADER_STRATEGY_NONE).commitEditor();
if (mListener != null) mListener.onFailed("皮膚資源獲取失敗");
}
mLoading = false;
mLock.notifyAll();
}
}
這里首先是將我們的策略類型保存起來,然后調(diào)用notifyUpdateSkin(),這個方法是做什么呢缩膝?因為這邊加載完皮膚會通知Acitivity里面的視圖控件跟著皮膚進行變化混狠,這里用到了觀察者設計模式,在每個Activity onResume的時候會將Activity添加為觀察者逞盆,所以這個地方notifyUpdateSkin就是調(diào)用到
observer = new SkinObserver() {
@Override
public void updateSkin(SkinObservable observable, Object o) {
updateStatusBarColor(activity);
updateWindowBackground(activity);
getSkinDelegate((AppCompatActivity) activity).applySkin();
}
};
刷新狀態(tài)欄檀蹋,刷新背景,然后通知每個控件進行重新設置皮膚云芦。跟之前套路不一樣俯逾,我們將要進入主要知識講解了。為了蹭iphone發(fā)布會熱點舅逸,決定輕松一刻:
6.SkinActivityLifecycle onActivityCreated
我們知道之前有一句話:
private SkinActivityLifecycle(Application application) {
application.registerActivityLifecycleCallbacks(this);
}
這句話已經(jīng)將SkinActivityLifecycle 設置為生命周期攔截器了桌肴,這樣我們就可以攔截到Acitivity的每個生命周期,我們先來看我們Activity創(chuàng)建的生命周期即onActivityCreated:
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
if (activity instanceof AppCompatActivity) {
LayoutInflater layoutInflater = activity.getLayoutInflater();
try {
Field field = LayoutInflater.class.getDeclaredField("mFactorySet");
field.setAccessible(true);
field.setBoolean(layoutInflater, false);
LayoutInflaterCompat.setFactory(activity.getLayoutInflater(),
getSkinDelegate((AppCompatActivity) activity));
} catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
}
updateStatusBarColor(activity);
updateWindowBackground(activity);
}
}
看到這個方法我們應該有種似曾相識的感覺琉历,沒錯坠七!這里其實跟support V7的源碼是一樣的。重點強調(diào):LayoutInflaterCompat.setFactory(activity.getLayoutInflater(),getSkinDelegate((AppCompatActivity) activity));這個地方就是自定義Factory攔截View的創(chuàng)建過程旗笔。那我們還是那個套路看下getSkinDelegate()這個方法做了啥:
private SkinCompatDelegate getSkinDelegate(AppCompatActivity activity) {
if (mSkinDelegateMap == null) {
mSkinDelegateMap = new WeakHashMap<>();
}
SkinCompatDelegate mSkinDelegate = mSkinDelegateMap.get(activity);
if (mSkinDelegate == null) {
mSkinDelegate = SkinCompatDelegate.create(activity);
}
mSkinDelegateMap.put(activity, mSkinDelegate);
return mSkinDelegate;
}
這個地方還有緩存彪置,作者不錯,首先看HashMap(這里要用WeakHashMap主要是因為持有了Activity的實例蝇恶,為了防止內(nèi)存泄漏所以用了弱引用的HashMap)里面有沒有SkinCompatDelegate對象拳魁,沒有則創(chuàng)建。SkinCompatDelegate是個LayoutInflaterFactory即Factory對象撮弧,所以我們xml里面View創(chuàng)建的時候會調(diào)用Factory里面的onCreateView()方法潘懊。
7.SkinCompatDelegate onCreateView
因為這個方法攔截了view的創(chuàng)建過程,所以我們就可以看到這個方法做了啥贿衍,其實我們已經(jīng)很熟悉這個方法了:
@Override
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
View view = createView(parent, name, context, attrs);
if (view == null) {
return null;
}
if (view instanceof SkinCompatSupportable) {
//這個主要是添加進mSkinHelpers內(nèi)授舟,到時調(diào)用notifyUpdateSkin的時候會調(diào)用刷新
mSkinHelpers.add(new WeakReference<SkinCompatSupportable>((SkinCompatSupportable) view));
}
return view;
}
我們知道我們調(diào)用createView方法,那么創(chuàng)建view的任務就是這個方法了:
public View createView(View parent, final String name, @NonNull Context context,
@NonNull AttributeSet attrs) {
final boolean isPre21 = Build.VERSION.SDK_INT < 21;
if (mSkinCompatViewInflater == null) {
mSkinCompatViewInflater = new SkinCompatViewInflater();
}
// We only want the View to inherit its context if we're running pre-v21
final boolean inheritContext = isPre21 && shouldInheritContext((ViewParent) parent);
return mSkinCompatViewInflater.createView(parent, name, context, attrs, inheritContext,
isPre21, /* Only read android:theme pre-L (L+ handles this anyway) */
true, /* Read read app:theme as a fallback at all times for legacy reasons */
VectorEnabledTintResources.shouldBeUsed() /* Only tint wrap the context if enabled */
);
}
前面初始化了mSkinCompatViewInflater對象然后調(diào)用它的createView方法贸辈,我們繼續(xù)跟進去:
public final View createView(View parent, final String name, @NonNull Context context,
@NonNull AttributeSet attrs, boolean inheritContext,
boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
final Context originalContext = context;
// We can emulate Lollipop's android:theme attribute propagating down the view hierarchy
// by using the parent's context
if (inheritContext && parent != null) {
context = parent.getContext();
}
if (readAndroidTheme || readAppTheme) {
// We then apply the theme on the context, if specified
context = themifyContext(context, attrs, readAndroidTheme, readAppTheme);
}
if (wrapContext) {
context = TintContextWrapper.wrap(context);
}
View view = createViewFromHackInflater(context, name, attrs);
// We need to 'inject' our tint aware Views in place of the standard framework versions
if (view == null) {
view = createViewFromFV(context, name, attrs);
}
if (view == null) {
view = createViewFromV7(context, name, attrs);
}
if (view == null) {
view = createViewFromInflater(context, name, attrs);
}
if (view == null) {
view = createViewFromTag(context, name, attrs);
}
if (view != null) {
// If we have created a view, check it's android:onClick
checkOnClickListener(view, attrs);
}
return view;
}
前面跟我們support v7源碼是一樣的释树,我們直接看到createViewFromHackInflater方法,這個方法我們看到嘗試創(chuàng)建了view對象:
private View createViewFromHackInflater(Context context, String name, AttributeSet attrs) {
View view = null;
for (SkinLayoutInflater inflater : SkinCompatManager.getInstance().getHookInflaters()) {
view = inflater.createView(context, name, attrs);
if (view == null) {
continue;
} else {
break;
}
}
return view;
}
我們看到代碼應該很熟悉呀,這個方法其實就是讓用戶可以設置SkinLayoutInflater奢啥,然后來得到優(yōu)先攔截創(chuàng)建view的能力署浩。我們假裝用戶沒有設置,那么返回的view就會為null扫尺,那么就會進入下一個createViewFromFV()方法:
private View createViewFromFV(Context context, String name, AttributeSet attrs) {
View view = null;
if (name.contains(".")) {
return null;
}
switch (name) {
case "View":
view = new SkinCompatView(context, attrs);
break;
case "LinearLayout":
view = new SkinCompatLinearLayout(context, attrs);
break;
case "RelativeLayout":
view = new SkinCompatRelativeLayout(context, attrs);
break;
case "FrameLayout":
view = new SkinCompatFrameLayout(context, attrs);
break;
case "TextView":
view = new SkinCompatTextView(context, attrs);
break;
case "ImageView":
view = new SkinCompatImageView(context, attrs);
break;
case "Button":
view = new SkinCompatButton(context, attrs);
break;
case "EditText":
view = new SkinCompatEditText(context, attrs);
break;
case "Spinner":
view = new SkinCompatSpinner(context, attrs);
break;
case "ImageButton":
view = new SkinCompatImageButton(context, attrs);
break;
case "CheckBox":
view = new SkinCompatCheckBox(context, attrs);
break;
case "RadioButton":
view = new SkinCompatRadioButton(context, attrs);
break;
case "RadioGroup":
view = new SkinCompatRadioGroup(context, attrs);
break;
case "CheckedTextView":
view = new SkinCompatCheckedTextView(context, attrs);
break;
case "AutoCompleteTextView":
view = new SkinCompatAutoCompleteTextView(context, attrs);
break;
case "MultiAutoCompleteTextView":
view = new SkinCompatMultiAutoCompleteTextView(context, attrs);
break;
case "RatingBar":
view = new SkinCompatRatingBar(context, attrs);
break;
case "SeekBar":
view = new SkinCompatSeekBar(context, attrs);
break;
case "ProgressBar":
view = new SkinCompatProgressBar(context, attrs);
break;
case "ScrollView":
view = new SkinCompatScrollView(context, attrs);
break;
}
return view;
}
我們可以看到這里攔截好多控件的創(chuàng)建過程筋栋。為了不使我們的文章冗長,這邊就挑一個控件來說明正驻,這里我們挑講解support v7時候同樣的控件TextView控件來講解弊攘。
8.SkinCompatTextView
這是個自定義的TextView(如果自己要往這個庫加入什么新的控件,套路也是一樣的)姑曙,又因為到時通知更新要更新控件襟交,所以每個控件必須實現(xiàn)SkinCompatSupportable接口:
public class SkinCompatTextView extends AppCompatTextView implements SkinCompatSupportable {
}
我們接下來看到構(gòu)造函數(shù):
public SkinCompatTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mBackgroundTintHelper = new SkinCompatBackgroundHelper(this);
mBackgroundTintHelper.loadFromAttributes(attrs, defStyleAttr);
mTextHelper = SkinCompatTextHelper.create(this);
mTextHelper.loadFromAttributes(attrs, defStyleAttr);
}
這里主要有兩個類SkinCompatBackgroundHelper和SkinCompatTextHelper,很明顯第一個是用來設置背景的伤靠,第二個類是設置Text相關(guān)外形的捣域。我們先來看SkinCompatBackgroundHelper的loadFromAttributes方法:
public void loadFromAttributes(AttributeSet attrs, int defStyleAttr) {
TypedArray a = mView.getContext().obtainStyledAttributes(attrs, R.styleable.SkinBackgroundHelper, defStyleAttr, 0);
try {
if (a.hasValue(R.styleable.SkinBackgroundHelper_android_background)) {
mBackgroundResId = a.getResourceId(
R.styleable.SkinBackgroundHelper_android_background, INVALID_ID);
}
} finally {
a.recycle();
}
applySkin();
}
這里清晰明了,對老司機來說這都不是事宴合,獲取自定義屬性backgroud焕梅,但是這里有個小技巧我們來看下SkinBackgroundHelper_android_background對應的屬性是啥:
<declare-styleable name="SkinBackgroundHelper">
<attr name="android:background" />
</declare-styleable>
看到?jīng)]有!X郧ⅰ贞言!看到?jīng)]有!7У佟该窗!其實他對應的就是系統(tǒng)的background,為什么這么做呢蚤霞?就是為了我們在寫background的時候不需要麻煩再去自定義酗失,只要寫上background即可,是不是處處有干貨昧绣,這干貨很干刊咳。
接下來我們看下SkinCompatTextHelper的loadFromAttributes:
public void loadFromAttributes(AttributeSet attrs, int defStyleAttr) {
final Context context = mView.getContext();
// First read the TextAppearance style id
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SkinCompatTextHelper, defStyleAttr, 0);
final int ap = a.getResourceId(R.styleable.SkinCompatTextHelper_android_textAppearance, INVALID_ID);
SkinLog.d(TAG, "ap = " + ap);
if (a.hasValue(R.styleable.AppCompatTextHelper_android_drawableLeft)) {
mDrawableLeftResId = a.getResourceId(R.styleable.AppCompatTextHelper_android_drawableLeft, INVALID_ID);
}
if (a.hasValue(R.styleable.AppCompatTextHelper_android_drawableTop)) {
mDrawableTopResId = a.getResourceId(R.styleable.AppCompatTextHelper_android_drawableTop, INVALID_ID);
}
if (a.hasValue(R.styleable.AppCompatTextHelper_android_drawableRight)) {
mDrawableRightResId = a.getResourceId(R.styleable.AppCompatTextHelper_android_drawableRight, INVALID_ID);
}
if (a.hasValue(R.styleable.AppCompatTextHelper_android_drawableBottom)) {
mDrawableBottomResId = a.getResourceId(R.styleable.AppCompatTextHelper_android_drawableBottom, INVALID_ID);
}
a.recycle();
if (ap != INVALID_ID) {
a = context.obtainStyledAttributes(ap, R.styleable.SkinTextAppearance);
if (a.hasValue(R.styleable.SkinTextAppearance_android_textColor)) {
mTextColorResId = a.getResourceId(R.styleable.SkinTextAppearance_android_textColor, INVALID_ID);
SkinLog.d(TAG, "mTextColorResId = " + mTextColorResId);
}
if (a.hasValue(R.styleable.SkinTextAppearance_android_textColorHint)) {
mTextColorHintResId = a.getResourceId(
R.styleable.SkinTextAppearance_android_textColorHint, INVALID_ID);
SkinLog.d(TAG, "mTextColorHintResId = " + mTextColorHintResId);
}
a.recycle();
}
// Now read the style's values
a = context.obtainStyledAttributes(attrs, R.styleable.SkinTextAppearance, defStyleAttr, 0);
if (a.hasValue(R.styleable.SkinTextAppearance_android_textColor)) {
mTextColorResId = a.getResourceId(R.styleable.SkinTextAppearance_android_textColor, INVALID_ID);
SkinLog.d(TAG, "mTextColorResId = " + mTextColorResId);
}
if (a.hasValue(R.styleable.SkinTextAppearance_android_textColorHint)) {
mTextColorHintResId = a.getResourceId(
R.styleable.SkinTextAppearance_android_textColorHint, INVALID_ID);
SkinLog.d(TAG, "mTextColorHintResId = " + mTextColorHintResId);
}
a.recycle();
applySkin();
}
看著這么多代碼不要被嚇到扔涧,其實非常簡單廷雅,我們看下style:
<declare-styleable name="SkinCompatTextHelper">
<attr name="android:drawableLeft" />
<attr name="android:drawableTop" />
<attr name="android:drawableRight" />
<attr name="android:drawableBottom" />
<attr name="android:drawableStart" />
<attr name="android:drawableEnd" />
<attr name="android:textAppearance" />
</declare-styleable>
其實就是獲取這些屬性值壳鹤。我去鉴嗤。斩启。。醉锅。上面代碼白貼了兔簇。最后會調(diào)用applySkin()方法,就是去設置這些屬性:
public void applySkin() {
applyTextColorResource();
applyTextColorHintResource();
applyCompoundDrawablesResource();
}
從方法名就可以很清晰地看出來。就不過多糾結(jié)了垄琐,這個方法applySkin是SkinCompatSupportable 接口里面的边酒,在觀察者模式提示更新的時候也會調(diào)用到這個方法,所以我們自定義的換膚控件都必須實現(xiàn)這個接口狸窘,這是個約定墩朦。
到這里我們的講解就完成,這篇真的是干貨和技能MAX翻擒,媽媽再也不用擔心我求干貨了氓涣。
總結(jié):這個換膚框架是比較綜合的一個support v7知識應用,同時包含了許多的小技巧陋气,都是自定義控件或者其他地方能用到的劳吠,是一個解決方案。希望大家有所收獲巩趁,謝謝堅持看完痒玩,說明大哥你是閑人中的戰(zhàn)斗機!R槲俊蠢古!