2024年Android最新Android-skin-support 换肤原理全面解析(4),小米面试题库

最后

我坚信,坚持学习,每天进步一点,滴水穿石,我们离成功都很近!
以下是总结出来的字节经典面试题目,包含:计算机网络,Kotlin,数据结构与算法,Framework源码,微信小程序,NDK音视频开发,计算机网络等。

字节高级Android经典面试题和答案


网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

}

其实里面我觉得就只有2句关键代码,就是去根据xml写的东西去构建View嘛.rInflateChildren()最后还是会去调用createViewFromTag()方法,这里是为了先创建出rootView,然后将子View添加进rootView.

来看看createViewFromTag()的实现

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,

boolean ignoreThemeAttr) {

try {

View view;

if (mFactory2 != null) {

view = mFactory2.onCreateView(parent, name, context, attrs);

} else if (mFactory != null) {

view = mFactory.onCreateView(name, context, attrs);

} else {

view = null;

}

if (view == null && mPrivateFactory != null) {

view = mPrivateFactory.onCreateView(parent, name, context, attrs);

}

if (view == null) {

final Object lastContext = mConstructorArgs[0];

mConstructorArgs[0] = context;

try {

if (-1 == name.indexOf(‘.’)) {

view = onCreateView(parent, name, attrs);

} else {

view = createView(name, null, attrs);

}

} finally {

mConstructorArgs[0] = lastContext;

}

}

return view;

}

可以看到如果mFactory2不为空的话,那么就会调用mFactory2去创建View(mFactory2.onCreateView(parent, name, context, attrs)) . 这句结论很重要.前面的答案已揭晓.如果设置了mFactory2就会用mFactory2去创建View.而mFactory2在上面的AppCompatDelegateImplV9installViewFactory()中已设置好了的,其实mFactory2就是AppCompatDelegateImplV9.

来看看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()去创建View.我感觉AppCompatViewInflater就是专门用来创建View的,面向对象的五大原则之一–单一职责原则.

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) {

View view = null;

// We 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;

}

if (view == null && originalContext != context) {

// If the original context does not equal our themed context, then we need to manually

// inflate it using the name so that android:theme takes effect.

view = createViewFromTag(context, name, attrs);

}

if (view != null) {

// If we have created a view, check its android:onClick

checkOnClickListener(view, attrs);

}

return view;

}

可以看到如果在xml中写了一个TextView控件,其实是通过我们写的控件名称判断是什么控件,然后去new的方式创建出来的,并且new的不是TextView,而是AppCompatTextView.其他的一些系统控件也是这么new出来的.

但是,有个问题,如果我在xml布局中不是写的这些控件(比如RecyclerView,自定义控件等),那么怎么创建view呢?注意到代码中如果执行完switch块之后view为空(说明不是上面列的那些控件),调用了createViewFromTag()方法.来看看实现

private static final String[] sClassPrefixList = {

“android.widget.”,

“android.view.”,

“android.webkit.”

};

private View createViewFromTag(Context context, String name, AttributeSet attrs) {

if (name.equals(“view”)) {

name = attrs.getAttributeValue(null, “class”);

}

try {

mConstructorArgs[0] = context;

mConstructorArgs[1] = attrs;

//这里判断一下name(即在xml中写的控件名称)中是否含有’.’

//如果没有那么肯定就是系统控件(比如ProgressBar,在布局中是不需要加ProgressBar的具体包名的)

//如果有那么就是自定义控件,或者是系统的控件(比如android.support.v7.widget.SwitchCompat)

if (-1 == name.indexOf(‘.’)) {

for (int i = 0; i < sClassPrefixList.length; i++) {

final View view = createView(context, name, sClassPrefixList[i]);

if (view != null) {

return view;

}

}

return null;

} else {

return createView(context, name, null);

}

} catch (Exception e) {

// We do not want to catch these, lets return null and let the actual LayoutInflater

// try

return null;

} finally {

// Don’t retain references on context.

mConstructorArgs[0] = null;

mConstructorArgs[1] = null;

}

}

这里比较有意思,首先是判断一下是否是系统控件,怎么判断呢?通过判断控件名称中是否包含’.‘来判断.系统控件在xml布局中声明时是不需要加具体包名的,比如ProgressBar,所以没有’.‘的肯定是系统控件.那么有’.'的就是自定义控件或者一些特殊的系统控件了(比如android.support.v7.widget.SwitchCompat).

有个小疑问?为什么系统控件可以在布局中声明时不加包名,而自定义控件必须要加包名呢?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XveKLAOT-1600323763198)(http://olg7c0d2n.bkt.clouddn.com/2018%E5%B9%B47%E6%9C%8829%E6%97%A5143048.jpg)]

其实是系统的控件大多放在sClassPrefixList定义的这些包名下,所以待会儿可以通过拼接的方式将控件的位置找到.随便举个例子,我们来看看哪些系统控件在android.widget.包下面

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dXLAZogd-1600323763200)(http://olg7c0d2n.bkt.clouddn.com/18-7-27/92621264.jpg)]

源码中创建系统控件和非系统控件分开去创建.其实方法都是同一个,只是一个传了前缀,一个没有传前缀.来看看创建方法实现

private static final Class<?>[] sConstructorSignature = new Class[]{

Context.class, AttributeSet.class};

private static final Map<String, Constructor<? extends View>> sConstructorMap

= new ArrayMap<>();

private View createView(Context context, String name, String prefix)

throws ClassNotFoundException, InflateException {

//这里的sConstructorMap是用来做缓存的,如果之前已经创建,则会将构造方法缓存起来,下次直接用

Constructor<? extends View> constructor = sConstructorMap.get(name);

try {

if (constructor == null) {

// Class not found in the cache, see if it’s real, and try to add it

//通过classLoader去寻找该class,这里的classLoader其实是PathClassLoader

//看到没? (prefix + name)这种直接将前缀与名称拼接的方式就可以将View的位置拼接出来

//然后其他的全类名的View就不需要拼接前缀

Class<? extends View> clazz = context.getClassLoader().loadClass(

prefix != null ? (prefix + name) : name).asSubclass(View.class);

//获取构造方法

constructor = clazz.getConstructor(sConstructorSignature);

//缓存构造方法

sConstructorMap.put(name, constructor);

}

//设置构造方法可访问

constructor.setAccessible(true);

//通过构造方法new一个View对象出来

return constructor.newInstance(mConstructorArgs);

} catch (Exception e) {

// We do not want to catch these, lets return null and let the actual LayoutInflater

// try

return null;

}

}

其实这个创建View就是利用ClassLoader去寻找这个类的class,然后获取其{ Context.class, AttributeSet.class}这个构造方法,然后通过反射将View创建出来.具体逻辑在代码中已标明注释.

至此,Android的控件加载方式已全部剖析完毕.

其中,有一个小细节,刚刚为了流程顺畅没有在上面说到,上面有一段构建View(根据控件名称创建AppCompatXX控件)的代码如下:

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;

}

我们来随便看一下控件的源码,比如AppCompatTextView,其他的AppCompatXX控件实现都是差不多的.

public class AppCompatTextView extends TextView implements TintableBackgroundView,

AutoSizeableTextView {

//这2个是关键类

private final AppCompatBackgroundHelper mBackgroundTintHelper;

private final AppCompatTextHelper mTextHelper;

public AppCompatTextView(Context context) {

this(context, null);

}

public AppCompatTextView(Context context, AttributeSet attrs) {

this(context, attrs, android.R.attr.textViewStyle);

}

public AppCompatTextView(Context context, AttributeSet attrs, int defStyleAttr) {

super(TintContextWrapper.wrap(context), attrs, defStyleAttr);

mBackgroundTintHelper = new AppCompatBackgroundHelper(this);

mBackgroundTintHelper.loadFromAttributes(attrs, defStyleAttr);

mTextHelper = AppCompatTextHelper.create(this);

mTextHelper.loadFromAttributes(attrs, defStyleAttr);

mTextHelper.applyCompoundDrawablesTints();

}

class AppCompatBackgroundHelper {

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)) {

//获取android:background 背景的资源id

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)) {

//获取android:backgroundTint

ViewCompat.setBackgroundTintList(mView,

a.getColorStateList(R.styleable.ViewBackgroundHelper_backgroundTint));

}

if (a.hasValue(R.styleable.ViewBackgroundHelper_backgroundTintMode)) {

//获取android:backgroundTintMode

ViewCompat.setBackgroundTintMode(mView,

DrawableUtils.parseTintMode(

a.getInt(R.styleable.ViewBackgroundHelper_backgroundTintMode, -1),

null));

}

} finally {

a.recycle();

}

}

}

class AppCompatTextHelper {

@SuppressLint(“NewApi”)

void loadFromAttributes(AttributeSet attrs, int defStyleAttr) {

final Context context = mView.getContext();

final AppCompatDrawableManager drawableManager = AppCompatDrawableManager.get();

// First read the TextAppearance style id

TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs,

R.styleable.AppCompatTextHelper, defStyleAttr, 0);

final int ap = a.getResourceId(R.styleable.AppCompatTextHelper_android_textAppearance, -1);

// Now read the compound drawable and grab any tints

if (a.hasValue(R.styleable.AppCompatTextHelper_android_drawableLeft)) {

mDrawableLeftTint = createTintInfo(context, drawableManager,

a.getResourceId(R.styleable.AppCompatTextHelper_android_drawableLeft, 0));

}

if (a.hasValue(R.styleable.AppCompatTextHelper_android_drawableTop)) {

mDrawableTopTint = createTintInfo(context, drawableManager,

a.getResourceId(R.styleable.AppCompatTextHelper_android_drawableTop, 0));

}

if (a.hasValue(R.styleable.AppCompatTextHelper_android_drawableRight)) {

mDrawableRightTint = createTintInfo(context, drawableManager,

a.getResourceId(R.styleable.AppCompatTextHelper_android_drawableRight, 0));

}

if (a.hasValue(R.styleable.AppCompatTextHelper_android_drawableBottom)) {

mDrawableBottomTint = createTintInfo(context, drawableManager,

a.getResourceId(R.styleable.AppCompatTextHelper_android_drawableBottom, 0));

}

a.recycle();

// PasswordTransformationMethod wipes out all other TransformationMethod instances

// in TextView’s constructor, so we should only set a new transformation method

// if we don’t have a PasswordTransformationMethod currently…

final boolean hasPwdTm =

mView.getTransformationMethod() instanceof PasswordTransformationMethod;

boolean allCaps = false;

boolean allCapsSet = false;

ColorStateList textColor = null;

ColorStateList textColorHint = null;

ColorStateList textColorLink = null;

// First check TextAppearance’s textAllCaps value

if (ap != -1) {

a = TintTypedArray.obtainStyledAttributes(context, ap, R.styleable.TextAppearance);

if (!hasPwdTm && a.hasValue(R.styleable.TextAppearance_textAllCaps)) {

allCapsSet = true;

allCaps = a.getBoolean(R.styleable.TextAppearance_textAllCaps, false);

}

updateTypefaceAndStyle(context, a);

if (Build.VERSION.SDK_INT < 23) {

// If we’re running on < API 23, the text color may contain theme references

// so let’s re-set using our own inflater

if (a.hasValue(R.styleable.TextAppearance_android_textColor)) {

textColor = a.getColorStateList(R.styleable.TextAppearance_android_textColor);

}

if (a.hasValue(R.styleable.TextAppearance_android_textColorHint)) {

textColorHint = a.getColorStateList(

R.styleable.TextAppearance_android_textColorHint);

}

if (a.hasValue(R.styleable.TextAppearance_android_textColorLink)) {

textColorLink = a.getColorStateList(

R.styleable.TextAppearance_android_textColorLink);

}

}

a.recycle();

}

// Now read the style’s values

a = TintTypedArray.obtainStyledAttributes(context, attrs, R.styleable.TextAppearance,

defStyleAttr, 0);

if (!hasPwdTm && a.hasValue(R.styleable.TextAppearance_textAllCaps)) {

allCapsSet = true;

allCaps = a.getBoolean(R.styleable.TextAppearance_textAllCaps, false);

}

if (Build.VERSION.SDK_INT < 23) {

// If we’re running on < API 23, the text color may contain theme references

// so let’s re-set using our own inflater

if (a.hasValue(R.styleable.TextAppearance_android_textColor)) {

textColor = a.getColorStateList(R.styleable.TextAppearance_android_textColor);

}

if (a.hasValue(R.styleable.TextAppearance_android_textColorHint)) {

textColorHint = a.getColorStateList(

R.styleable.TextAppearance_android_textColorHint);

}

if (a.hasValue(R.styleable.TextAppearance_android_textColorLink)) {

textColorLink = a.getColorStateList(

R.styleable.TextAppearance_android_textColorLink);

}

}

updateTypefaceAndStyle(context, a);

a.recycle();

if (textColor != null) {

mView.setTextColor(textColor);

}

if (textColorHint != null) {

mView.setHintTextColor(textColorHint);

}

if (textColorLink != null) {

mView.setLinkTextColor(textColorLink);

}

if (!hasPwdTm && allCapsSet) {

setAllCaps(allCaps);

}

if (mFontTypeface != null) {

mView.setTypeface(mFontTypeface, mStyle);

}

mAutoSizeTextHelper.loadFromAttributes(attrs, defStyleAttr);

if (PLATFORM_SUPPORTS_AUTOSIZE) {

// Delegate auto-size functionality to the framework implementation.

if (mAutoSizeTextHelper.getAutoSizeTextType()

!= TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE) {

final int[] autoSizeTextSizesInPx =

mAutoSizeTextHelper.getAutoSizeTextAvailableSizes();

if (autoSizeTextSizesInPx.length > 0) {

if (mView.getAutoSizeStepGranularity() != AppCompatTextViewAutoSizeHelper

.UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) {

// Configured with granularity, preserve details.

mView.setAutoSizeTextTypeUniformWithConfiguration(

mAutoSizeTextHelper.getAutoSizeMinTextSize(),

mAutoSizeTextHelper.getAutoSizeMaxTextSize(),

mAutoSizeTextHelper.getAutoSizeStepGranularity(),

TypedValue.COMPLEX_UNIT_PX);

} else {

mView.setAutoSizeTextTypeUniformWithPresetSizes(

autoSizeTextSizesInPx, TypedValue.COMPLEX_UNIT_PX);

}

}

}

}

}

这里的这里,不得不说Android源码真是让在下佩服的五体投地,又一次体现了单一职责原则.你问我在哪里? 系统将背景相关的交给AppCompatBackgroundHelper去处理,将文字相关的交给AppCompatTextHelper处理.

AppCompatBackgroundHelper和AppCompatTextHelper拿到了xml中定义的属性的值之后,将其值赋值给控件.就是这么简单.

看到了这里,预备知识就介绍得差不多了,看了半天,你说的这些乱七八糟的东西与我的换肤有个毛关系啊?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V0PhJfle-1600323763201)(http://olg7c0d2n.bkt.clouddn.com/2018%E5%B9%B47%E6%9C%8829%E6%97%A5155555.jpg)]

请各位看官放下手中的砖头,且听贫道细细道来.

五.换肤原理详细解析


1.上文预备知识与换肤的关系

源码中可以通过拦截View创建过程, 替换一些基础的组件(比如TextView -> AppCompatTextView), 然后对一些特殊的属性(比如:background, textColor) 做处理, 那我们为什么不能将这种思想拿到换肤框架中来使用呢?我擦,一语惊醒梦中人啊,老哥.我们也可以搞一个委托啊,我们也可以搞一个类似于AppCompatViewInflater的控件加载器啊,我们也可以设置mFactory2啊,相当于创建View的过程由我们接手.既然我们接手了,那岂不是对所有控件都可以为所欲为???那是当然啦. 既然都可以为所欲为了,那换个肤算什么,so easy.

2.源码一,创建控件全过程

SkinCompatManager.withoutActivity(application)

.addInflater(new SkinAppCompatViewInflater());

首先我们从库的初始化处着手,这里将Application传入,又添加了一个SkinAppCompatViewInflater,SkinAppCompatViewInflater其实就是用来创建View的,和系统的AppCompatViewInflater差不多.我们来看看withoutActivity(application)做了什么.

//SkinCompatManager.java

public static SkinCompatManager withoutActivity(Application application) {

init(application);

SkinActivityLifecycle.init(application);

return sInstance;

}

//SkinActivityLifecycle.java

public static SkinActivityLifecycle init(Application application) {

if (sInstance == null) {

synchronized (SkinActivityLifecycle.class) {

if (sInstance == null) {

sInstance = new SkinActivityLifecycle(application);

}

}

}

return sInstance;

}

private SkinActivityLifecycle(Application application) {

//就是这里,注册了ActivityLifecycleCallbacks,可以监听所有Activity的生命周期

application.registerActivityLifecycleCallbacks(this);

//这个方法稍后看

installLayoutFactory(application);

SkinCompatManager.getInstance().addObserver(getObserver(application));

}

可以看到,初始化时在SkinActivityLifecycle中其实就注册了ActivityLifecycleCallbacks,现在我们可以监听app所有Activity的生命周期.

来看看SkinActivityLifecycle中监听到Activity的onCreate()方法时干了什么

@Override

public void onActivityCreated(Activity activity, Bundle savedInstanceState) {

//判断是否需要换肤 这个可以外部初始化时控制

if (isContextSkinEnable(activity)) {

//在Activity创建的时候,直接将Factory设置成三方库里面的

installLayoutFactory(activity);

//更新状态栏颜色

updateStatusBarColor(activity);

//更新window背景颜色

updateWindowBackground(activity);

if (activity instanceof SkinCompatSupportable) {

((SkinCompatSupportable) activity).applySkin();

}

}

}

/**

  • 设置Factory(创建View的工厂)

*/

private void installLayoutFactory(Context context) {

LayoutInflater layoutInflater = LayoutInflater.from(context);

try {

//setFactory只能调用一次,用于设置Factory(创建View), 设置了Factory了mFactorySet就会是true

//如果需要重新设置Factory,则需要先将mFactorySet设置为false,不然系统判断到mFactorySet是true则会抛异常.

//这里使用自己构建的Factory去创建View,在创建View时当然也就可以控制它的背景或者文字颜色.

//(在这里之前需要知道哪些控件需要换肤,其中一部分是继承自三方库的控件,这些控件是实现了SkinCompatSupportable接口的,可以很方便的控制.

// 还有一部分是系统的控件,在创建时直接创建三方库中的控件(比如View就创建SkinCompatView).

// 在设置系统控件的背景颜色和文字颜色时,直接从三方库缓存颜色中取值,然后进行设置.)

Field field = LayoutInflater.class.getDeclaredField(“mFactorySet”);

field.setAccessible(true);

field.setBoolean(layoutInflater, false);

LayoutInflaterCompat.setFactory(layoutInflater, getSkinDelegate(context));

} catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException e) {

e.printStackTrace();

}

}

在我们的Activity创建的时候,首先判断一下是否需要换肤,需要换肤才去搞.

我们重点看看installLayoutFactory()方法,在上面的预备知识中说了mFactory只能设置一次,不然就要抛异常,所以需要先利用发射将mFactorySet的值设置为false才不会抛异常.然后才能setFactory().

下面我们来看看setFactory()的第二个参数创建过程,第二个参数其实是一个创建View的工厂.

//SkinActivityLifecycle.java

private SkinCompatDelegate getSkinDelegate(Context context) {

if (mSkinDelegateMap == null) {

mSkinDelegateMap = new WeakHashMap<>();

}

SkinCompatDelegate mSkinDelegate = mSkinDelegateMap.get(context);

if (mSkinDelegate == null) {

mSkinDelegate = SkinCompatDelegate.create(context);

mSkinDelegateMap.put(context, mSkinDelegate);

}

return mSkinDelegate;

}

//SkinCompatDelegate.java

public class SkinCompatDelegate implements LayoutInflaterFactory {

private final Context mContext;

//主角 在这里 在这里!!!

private SkinCompatViewInflater mSkinCompatViewInflater;

private List<WeakReference> mSkinHelpers = new ArrayList<>();

private SkinCompatDelegate(Context context) {

mContext = context;

}

@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.add(new WeakReference<>((SkinCompatSupportable) view));

}

return view;

}

public View createView(View parent, final String name, @NonNull Context context,

@NonNull AttributeSet attrs) {

if (mSkinCompatViewInflater == null) {

mSkinCompatViewInflater = new SkinCompatViewInflater();

}

List wrapperList = SkinCompatManager.getInstance().getWrappers();

for (SkinWrapper wrapper : wrapperList) {

Context wrappedContext = wrapper.wrapContext(mContext, parent, attrs);

if (wrappedContext != null) {

context = wrappedContext;

}

}

return mSkinCompatViewInflater.createView(parent, name, context, attrs);

}

public static SkinCompatDelegate create(Context context) {

return new SkinCompatDelegate(context);

}

public void applySkin() {

if (mSkinHelpers != null && !mSkinHelpers.isEmpty()) {

for (WeakReference ref : mSkinHelpers) {

if (ref != null && ref.get() != null) {

((SkinCompatSupportable) ref.get()).applySkin();

}

}

}

}

}

可以看到SkinCompatDelegate是一个SkinCompatViewInflater的委托.这里其实和系统的AppCompatDelegateImplV9很类似.

当系统需要创建View的时候,就会回调SkinCompatDelegate的@Override public View onCreateView(View parent, String name, Context context, AttributeSet attrs)方法,因为前面设置了LayoutInflater的Factory为SkinCompatDelegate. 然后SkinCompatDelegate将创建View的工作交给SkinCompatViewInflater去处理(也是和系统一模一样).

来看看SkinCompatViewInflater是如何创建View的

public final View createView(View parent, final String name, @NonNull Context context, @NonNull AttributeSet attrs) {

View view = createViewFromHackInflater(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;

}

private View createViewFromInflater(Context context, String name, AttributeSet attrs) {

View view = null;

//这里的SkinLayoutInflater(我理解为控件创建器)就是我们之前在初始化时设置的SkinAppCompatViewInflater

//当然,SkinLayoutInflater可以有多个

for (SkinLayoutInflater inflater : SkinCompatManager.getInstance().getInflaters()) {

view = inflater.createView(context, name, attrs);

if (view == null) {

continue;

} else {

break;

}

}

return view;

}

//这个方法和系统的完全一模一样嘛,so easy

public View createViewFromTag(Context context, String name, AttributeSet attrs) {

if (“view”.equals(name)) {

name = attrs.getAttributeValue(null, “class”);

}

try {

mConstructorArgs[0] = context;

mConstructorArgs[1] = attrs;

//自定义控件

if (-1 == name.indexOf(‘.’)) {

for (int i = 0; i < sClassPrefixList.length; i++) {

final View view = createView(context, name, sClassPrefixList[i]);

if (view != null) {

return view;

}

}

return null;

} else {

return createView(context, name, null);

}

} catch (Exception e) {

// We do not want to catch these, lets return null and let the actual LayoutInflater

// try

return null;

} finally {

// Don’t retain references on context.

mConstructorArgs[0] = null;

mConstructorArgs[1] = null;

}

}

可以看到,这些实现其实是和系统的实现是差不多的.原理已在上面的预备知识中给出.

这里也有不同的地方,createViewFromInflater()方法中利用了我们在初始化库时设置的SkinLayoutInflater(我觉得是控件创造器)去创建view.

为什么要在SkinCompatViewInflater还要细化,还需要交由更细的SkinLayoutInflater来处理呢?我觉得是因为方便扩展,库中给出了几个SkinLayoutInflater,有SkinAppCompatViewInflater(基础控件构建器)、SkinMaterialViewInflater(material design控件构造器)、SkinConstraintViewInflater(ConstraintLayout构建器)、SkinCardViewInflater(CardView v7构建器)。

由于初始化时我们设置的是SkinAppCompatViewInflater,其他的构建器都是类似的原理.我们就来看看

//SkinAppCompatViewInflater.java

@Override

public View createView(Context context, String name, AttributeSet attrs) {

View view = createViewFromFV(context, name, attrs);

if (view == null) {

view = createViewFromV7(context, name, attrs);

}

return view;

}

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;

default:

break;

}

return view;

}

private View createViewFromV7(Context context, String name, AttributeSet attrs) {

View view = null;

switch (name) {

case “android.support.v7.widget.Toolbar”:

view = new SkinCompatToolbar(context, attrs);

break;

default:

break;

}

return view;

}

柳暗花明又一村?这不就是之前我们在Android源码中看过的代码吗?几乎是一模一样。我们在这里将View的创建拦截,然后创建自己的控件。既然是我们自己创建的控件,想干啥还不容易么?

我们看一下SkinCompatTextView的源码

//SkinCompatTextView.java

public class SkinCompatTextView extends AppCompatTextView implements SkinCompatSupportable {

private SkinCompatTextHelper mTextHelper;

private SkinCompatBackgroundHelper mBackgroundTintHelper;

public SkinCompatTextView(Context context) {

this(context, null);

}

public SkinCompatTextView(Context context, AttributeSet attrs) {

this(context, attrs, android.R.attr.textViewStyle);

}

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);

}

@Override

public void applySkin() {

if (mBackgroundTintHelper != null) {

mBackgroundTintHelper.applySkin();

}

if (mTextHelper != null) {

mTextHelper.applySkin();

}

最后

答应大伙的备战金三银四,大厂面试真题来啦!

这份资料我从春招开始,就会将各博客、论坛。网站上等优质的Android开发中高级面试题收集起来,然后全网寻找最优的解答方案。每一道面试题都是百分百的大厂面经真题+最优解答。包知识脉络 + 诸多细节。
节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

《960全网最全Android开发笔记》

《379页Android开发面试宝典》

包含了腾讯、百度、小米、阿里、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率。

如何使用它?
1.可以通过目录索引直接翻看需要的知识点,查漏补缺。
2.五角星数表示面试问到的频率,代表重要推荐指数

《507页Android开发相关源码解析》

只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。

真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。

腾讯、字节跳动、阿里、百度等BAT大厂 2020-2021面试真题解析

资料收集不易,如果大家喜欢这篇文章,或者对你有帮助不妨多多点赞转发关注哦。文章会持续更新的。绝对干货!!!

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

xt, AttributeSet attrs) {

this(context, attrs, android.R.attr.textViewStyle);

}

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);

}

@Override

public void applySkin() {

if (mBackgroundTintHelper != null) {

mBackgroundTintHelper.applySkin();

}

if (mTextHelper != null) {

mTextHelper.applySkin();

}

最后

答应大伙的备战金三银四,大厂面试真题来啦!

这份资料我从春招开始,就会将各博客、论坛。网站上等优质的Android开发中高级面试题收集起来,然后全网寻找最优的解答方案。每一道面试题都是百分百的大厂面经真题+最优解答。包知识脉络 + 诸多细节。
节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

《960全网最全Android开发笔记》

[外链图片转存中…(img-Mf9LCqWC-1715653593777)]

《379页Android开发面试宝典》

包含了腾讯、百度、小米、阿里、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率。

如何使用它?
1.可以通过目录索引直接翻看需要的知识点,查漏补缺。
2.五角星数表示面试问到的频率,代表重要推荐指数

[外链图片转存中…(img-5uiAKdkz-1715653593778)]

《507页Android开发相关源码解析》

只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。

真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。

[外链图片转存中…(img-VsZn8OS2-1715653593778)]

腾讯、字节跳动、阿里、百度等BAT大厂 2020-2021面试真题解析

[外链图片转存中…(img-QkmM8YfB-1715653593778)]

资料收集不易,如果大家喜欢这篇文章,或者对你有帮助不妨多多点赞转发关注哦。文章会持续更新的。绝对干货!!!

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值