Android-skin-support的原理
来源:互联网 发布:开眼角增生知乎 编辑:程序博客网 时间:2024/05/29 09:19
前言
上一篇阐述了皮肤包的创建和配置使用Android-skin-support生成换肤包,那么这一篇文章我们就来阐述一下Android-skin-support的原理。
正文
让我们先看看v7包中的AppCompatActivity是如何实现加载布局资源的。
AppCompatActivity的是生命周期是委托给AppCompateDelegate的子类AppCompatDelegateImplV9来实现。在AppCompatDelegateImplV9中我们看到了LayoutInflater 这个类。
@Override public void installViewFactory() { LayoutInflater layoutInflater = LayoutInflater.from(mContext); if (layoutInflater.getFactory() == null) { LayoutInflaterCompat.setFactory(layoutInflater, this); } else { if (!(LayoutInflaterCompat.getFactory(layoutInflater) instanceof AppCompatDelegateImplV9)) { Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed" + " so we can not install AppCompat's"); } }
还是在AppCompatDelegateImplV9的类中,有个onCreateView()方法,这个方法是LayoutInflaterFactory接口的方法(AppCompatDelegateImplV9实现了LayoutInflaterFactory的接口)这方法的View又是通过createview()方法交给AppCompatViewInflater处理的。
/** * From {@link android.support.v4.view.LayoutInflaterFactory} */ @Override public final View onCreateView(View parent, String name, Context context, AttributeSet attrs) { // First let the Activity's Factory try and inflate the view final View view = callActivityOnCreateView(parent, name, context, attrs); if (view != null) { return view; } // If the Factory didn't handle it, let our createView() method try return createView(parent, name, context, attrs); }
我们注意createView方法的mAppCompatViewInflater.createView()这一行代码,此代码跳到就是跳到AppCompatViewInflater.createView中的createView()方法。
@Override public View createView(View parent, final String name, @NonNull Context context, @NonNull AttributeSet attrs) { if (mAppCompatViewInflater == null) { mAppCompatViewInflater = new AppCompatViewInflater(); } boolean inheritContext = false; if (IS_PRE_LOLLIPOP) { inheritContext = (attrs instanceof XmlPullParser) // If we have a XmlPullParser, we can detect where we are in the layout ? ((XmlPullParser) attrs).getDepth() > 1 // Otherwise we have to use the old heuristic : shouldInheritContext((ViewParent) parent); } return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext, IS_PRE_LOLLIPOP, /* 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 */ ); }
查看AppCompatViewInflater.createView中的createView()的方法
public final View createView(View parent, final String name, @NonNull Context context, @NonNull AttributeSet attrs, boolean inheritContext, boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) { need to 'inject' our tint aware Views in place of the standard framework versions switch (name) { case "TextView": view = new AppCompatTextView(context, attrs); break; case "ImageView": view = new AppCompatImageView(context, attrs); break; case "Button": view = new AppCompatButton(context, attrs); break; case "EditText": view = new AppCompatEditText(context, attrs); break; case "Spinner": view = new AppCompatSpinner(context, attrs); break; case "ImageButton": view = new AppCompatImageButton(context, attrs); break; case "CheckBox": view = new AppCompatCheckBox(context, attrs); break; case "RadioButton": view = new AppCompatRadioButton(context, attrs); break; case "CheckedTextView": view = new AppCompatCheckedTextView(context, attrs); break; case "AutoCompleteTextView": view = new AppCompatAutoCompleteTextView(context, attrs); break; case "MultiAutoCompleteTextView": view = new AppCompatMultiAutoCompleteTextView(context, attrs); break; case "RatingBar": view = new AppCompatRatingBar(context, attrs); break; case "SeekBar": view = new AppCompatSeekBar(context, attrs); break; } return view; }
我们看到有很多控件的分类,在这些控件中(比如AppCompatImageView)借助AppCompatBackgroundHelper完成了相关子控件背景的设置。
AppCompatImageView中的构造函数
public AppCompatImageView(Context context, AttributeSet attrs, int defStyleAttr) { super(TintContextWrapper.wrap(context), attrs, defStyleAttr); mBackgroundTintHelper = new AppCompatBackgroundHelper(this); mBackgroundTintHelper.loadFromAttributes(attrs, defStyleAttr); mImageHelper = new AppCompatImageHelper(this); mImageHelper.loadFromAttributes(attrs, defStyleAttr); }
在AppCompatBackgroundHelper中判断attr中是否为此控件的android_background设置了背景,如果设置了就取出
void loadFromAttributes(AttributeSet attrs, int defStyleAttr) { TintTypedArray a = TintTypedArray.obtainStyledAttributes(mView.getContext(), attrs, R.styleable.ViewBackgroundHelper, defStyleAttr, 0); try { if (a.hasValue(R.styleable.ViewBackgroundHelper_android_background)) { mBackgroundResId = a.getResourceId( R.styleable.ViewBackgroundHelper_android_background, -1); ColorStateList tint = mDrawableManager .getTintList(mView.getContext(), mBackgroundResId); if (tint != null) { setInternalBackgroundTint(tint); } } if (a.hasValue(R.styleable.ViewBackgroundHelper_backgroundTint)) { ViewCompat.setBackgroundTintList(mView, a.getColorStateList(R.styleable.ViewBackgroundHelper_backgroundTint)); } if (a.hasValue(R.styleable.ViewBackgroundHelper_backgroundTintMode)) { ViewCompat.setBackgroundTintMode(mView, DrawableUtils.parseTintMode( a.getInt(R.styleable.ViewBackgroundHelper_backgroundTintMode, -1), null)); } } finally { a.recycle(); } }
前言
上一篇阐述了皮肤包的创建和配置使用Android-skin-support生成换肤包,那么这一篇文章我们就来阐述一下Android-skin-support的原理。
正文
第一部分
让我们先看看v7包中的AppCompatActivity是如何实现加载布局的。
AppCompatActivity的是生命周期是委托给AppCompateDelegate的子类AppCompatDelegateImplV9来实现。在AppCompatDelegateImplV9中我们看到了LayoutInflater 这个类。
@Override public void installViewFactory() { LayoutInflater layoutInflater = LayoutInflater.from(mContext); if (layoutInflater.getFactory() == null) { LayoutInflaterCompat.setFactory(layoutInflater, this); } else { if (!(LayoutInflaterCompat.getFactory(layoutInflater) instanceof AppCompatDelegateImplV9)) { Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed" + " so we can not install AppCompat's"); } }
还是在AppCompatDelegateImplV9的类中,有个onCreateView()方法,这个方法是LayoutInflaterFactory接口的方法(AppCompatDelegateImplV9实现了LayoutInflaterFactory的接口)这方法的View又是通过createview()方法交给AppCompatViewInflater处理的。
/** * From {@link android.support.v4.view.LayoutInflaterFactory} */ @Override public final View onCreateView(View parent, String name, Context context, AttributeSet attrs) { // First let the Activity's Factory try and inflate the view final View view = callActivityOnCreateView(parent, name, context, attrs); if (view != null) { return view; } // If the Factory didn't handle it, let our createView() method try return createView(parent, name, context, attrs); }
我们注意createView方法的mAppCompatViewInflater.createView()这一行代码,此代码跳到就是跳到AppCompatViewInflater.createView中的createView()方法。
@Override public View createView(View parent, final String name, @NonNull Context context, @NonNull AttributeSet attrs) { if (mAppCompatViewInflater == null) { mAppCompatViewInflater = new AppCompatViewInflater(); } boolean inheritContext = false; if (IS_PRE_LOLLIPOP) { inheritContext = (attrs instanceof XmlPullParser) // If we have a XmlPullParser, we can detect where we are in the layout ? ((XmlPullParser) attrs).getDepth() > 1 // Otherwise we have to use the old heuristic : shouldInheritContext((ViewParent) parent); } return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext, IS_PRE_LOLLIPOP, /* 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 */ ); }
查看AppCompatViewInflater中的createView()的方法
public final View createView(View parent, final String name, @NonNull Context context, @NonNull AttributeSet attrs, boolean inheritContext, boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) { need to 'inject' our tint aware Views in place of the standard framework versions switch (name) { case "TextView": view = new AppCompatTextView(context, attrs); break; case "ImageView": view = new AppCompatImageView(context, attrs); break; case "Button": view = new AppCompatButton(context, attrs); break; case "EditText": view = new AppCompatEditText(context, attrs); break; case "Spinner": view = new AppCompatSpinner(context, attrs); break; case "ImageButton": view = new AppCompatImageButton(context, attrs); break; case "CheckBox": view = new AppCompatCheckBox(context, attrs); break; case "RadioButton": view = new AppCompatRadioButton(context, attrs); break; case "CheckedTextView": view = new AppCompatCheckedTextView(context, attrs); break; case "AutoCompleteTextView": view = new AppCompatAutoCompleteTextView(context, attrs); break; case "MultiAutoCompleteTextView": view = new AppCompatMultiAutoCompleteTextView(context, attrs); break; case "RatingBar": view = new AppCompatRatingBar(context, attrs); break; case "SeekBar": view = new AppCompatSeekBar(context, attrs); break; } return view; }
我们看到有很多控件的分类,在这些控件中(比如AppCompatImageView)借助AppCompatBackgroundHelper和AppCompatImageHelper完成了相关子控件背景的设置。
AppCompatImageView中的构造函数
public AppCompatImageView(Context context, AttributeSet attrs, int defStyleAttr) { super(TintContextWrapper.wrap(context), attrs, defStyleAttr); mBackgroundTintHelper = new AppCompatBackgroundHelper(this); mBackgroundTintHelper.loadFromAttributes(attrs, defStyleAttr); mImageHelper = new AppCompatImageHelper(this); mImageHelper.loadFromAttributes(attrs, defStyleAttr); }
在ImageHelper中解析attr中为此控件的AppCompatImageView_srcCompat设置的图片资源和android_background设置的背景资源后保存。如果设置了就取出设置drawable,通过BackgroundTintHelper设置背景,并drawable.invalidateSelf()。
public void loadFromAttributes(AttributeSet attrs, int defStyleAttr) { TintTypedArray a = null; try { Drawable drawable = mView.getDrawable(); if (drawable == null) { a = TintTypedArray.obtainStyledAttributes(mView.getContext(), attrs, R.styleable.AppCompatImageView, defStyleAttr, 0); // If the view doesn't already have a drawable (from android:src), try loading // it from srcCompat final int id = a.getResourceId(R.styleable.AppCompatImageView_srcCompat, -1); if (id != -1) { drawable = AppCompatResources.getDrawable(mView.getContext(), id); if (drawable != null) { mView.setImageDrawable(drawable); } } } if (drawable != null) { DrawableUtils.fixDrawable(drawable); } } finally { if (a != null) { a.recycle(); } } }
void applySupportBackgroundTint() { final Drawable background = mView.getBackground(); if (mBackgroundTint != null) { AppCompatDrawableManager.tintDrawable(background, mBackgroundTint, mView.getDrawableState()); } else if (mInternalBackgroundTint != null) { AppCompatDrawableManager.tintDrawable(background, mInternalBackgroundTint, mView.getDrawableState()); } }
第二部分
基本原理
通过第一部分的原理我们可知,可以通过复写SkinCompatViewInflater代替AppCompatViewInflater,并将LayoutInflateFactor中的View的创建过程交给SkinCompatViewInflater类来实现。在此类中的createViewFromFV()方法中实现自定义的控件(比如SkinCompatView),再从写setBackgroundResource方法实现换肤。
1.所有的控件都要实现SkinCompatSupportable这个接口,这个接口中只有一个方法applySkin()。每次skinCompatManager.loadSkin()切换皮肤的时候就会调用applySkin()方法。
public interface SkinCompatSupportable { void applySkin();}
下面的就是实现SkinCompatSupportable的SkinCompatImageView
public class SkinCompatImageView extends AppCompatImageView implements SkinCompatSupportable { private SkinCompatBackgroundHelper mBackgroundTintHelper; private SkinCompatImageHelper mImageHelper; public SkinCompatImageView(Context context) { this(context, (AttributeSet)null); } public SkinCompatImageView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public SkinCompatImageView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.mBackgroundTintHelper = new SkinCompatBackgroundHelper(this); this.mBackgroundTintHelper.loadFromAttributes(attrs, defStyleAttr); this.mImageHelper = new SkinCompatImageHelper(this); this.mImageHelper.loadFromAttributes(attrs, defStyleAttr); } public void setBackgroundResource(@DrawableRes int resId) { super.setBackgroundResource(resId); if(this.mBackgroundTintHelper != null) { this.mBackgroundTintHelper.onSetBackgroundResource(resId); } } public void setImageResource(@DrawableRes int resId) { if(this.mImageHelper != null) { this.mImageHelper.setImageResource(resId); } } public void applySkin() { if(this.mBackgroundTintHelper != null) { this.mBackgroundTintHelper.applySkin(); } if(this.mImageHelper != null) { this.mImageHelper.applySkin(); } }}
在构造方法中通过在构造方法中通过SkinCompatBackgroundHelper和SkinCompatImageHelper通过loadFromAttributes分别解析出background, drawbale并保存,然后调用applySkin()方法完成图片的更换或者背景的设置。
public void loadFromAttributes(AttributeSet attrs, int defStyleAttr) { TypedArray a = null; try { a = this.mView.getContext().obtainStyledAttributes(attrs, styleable.SkinCompatImageView, defStyleAttr, 0); this.mSrcResId = a.getResourceId(styleable.SkinCompatImageView_android_src, 0); int srcCompatResId = a.getResourceId(styleable.SkinCompatImageView_srcCompat, 0); srcCompatResId = checkResourceId(srcCompatResId); if(srcCompatResId != 0) { this.mSrcResId = srcCompatResId; } } finally { if(a != null) { a.recycle(); } } this.applySkin(); }
上面的就是在SkinCompatImageView初始化的时候通过过SkinCompatBackgroundHelper和SkinCompatImageHelper 调用applySkin()完成换肤的。那么如果初始化完成后通过点击按钮用skinCompatManager.loadSkin()实现换肤该怎么办呢,其实我们前面说过SkinCompatImageView都继承了SkinCompatSupportable,当加载切换不同的皮肤包后SkinCompatSupportable接口中的applySkin()的方法就被会执行,方法会调用SkinCompatBackgroundHelper和SkinCompatImageHelper 的applySkin()方法完成换肤。
public void applySkin() { if(this.mBackgroundTintHelper != null) { this.mBackgroundTintHelper.applySkin(); } if(this.mImageHelper != null) { this.mImageHelper.applySkin(); } }
调用applySkin完成图片的更换和背景的设置
public void applySkin() { this.mSrcResId = checkResourceId(this.mSrcResId); SkinLog.d(TAG, "mSrcResId = " + this.mSrcResId); if(this.mSrcResId != 0) { String typeName = this.mView.getResources().getResourceTypeName(this.mSrcResId); if("color".equals(typeName)) { Drawable drawable1; if(VERSION.SDK_INT < 21) { int drawable = SkinCompatResources.getInstance().getColor(this.mSrcResId); drawable1 = this.mView.getDrawable(); if(drawable1 != null && drawable1 instanceof ColorDrawable) { ((ColorDrawable)drawable1.mutate()).setColor(drawable); } else { this.mView.setImageDrawable(new ColorDrawable(drawable)); } } else { ColorStateList drawable2 = SkinCompatResources.getInstance().getColorStateList(this.mSrcResId); drawable1 = this.mView.getDrawable(); if(drawable1 != null) { DrawableCompat.setTintList(drawable1, drawable2); this.mView.setImageDrawable(drawable1); } else { ColorDrawable colorDrawable = new ColorDrawable(); colorDrawable.setTintList(drawable2); this.mView.setImageDrawable(colorDrawable); } } } else { Drawable drawable3; if("drawable".equals(typeName)) { drawable3 = SkinCompatResources.getInstance().getDrawable(this.mSrcResId); this.mView.setImageDrawable(drawable3); } else if("mipmap".equals(typeName)) { drawable3 = SkinCompatResources.getInstance().getMipmap(this.mSrcResId); this.mView.setImageDrawable(drawable3); } }
其实Android-skin-support还是有部分缺陷的,有些皮肤或者高度之类的变换无法实现,那么下一篇我们就来看看Android-skin-support的缺陷。
Android-skin-support的缺陷
- Android-skin-support的原理
- Android-skin-support: 实现原理
- Android-skin-support的缺陷
- Android换肤之Android-skin-support
- 使用Android-skin-support生成换肤包
- Android-skin-support 一款用心去做的Android 换肤框架
- Android换肤原理和Android-Skin-Loader框架解析
- Android Support Multidex原理分析
- android的Support Library
- 定制Android模拟器skin
- 定制Android模拟器skin
- Android自定义模拟器skin
- Android模拟器skin
- 定制Android模拟器skin
- 基于Android-Skin-Loader的换肤效果
- skin的初级应用
- skin++的使用
- SKIN++皮肤的使用
- 开源项目综合案例
- 工程计算2——非线性方程求根
- Python解析json文件报错:'utf8' codec can't decode byte 0xbb in position 0: invalid start byte
- 乘积量化(PQ)
- Pytorch学习笔记(1)---Tensors
- Android-skin-support的原理
- div 动态显示滚动条
- python中的矩阵运算
- Python-第三方库requests详解
- kotlin发展迅速
- JS基础学习第十天:BOM对象及BOM操作
- Java基础:IO 流中的 flush
- JavaScript:获取url的querystring参数
- 【Android布局】布局优化之ViewStub、include、merge使用与源码分析