红橙Darren视频笔记 换肤框架2 原理篇 view创建的拦截

2024-05-30 12:32

本文主要是介绍红橙Darren视频笔记 换肤框架2 原理篇 view创建的拦截,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1.实现换肤的要求与方案分析

要求
1.换肤后每一次打开应用都是新的皮肤
2.换肤后所有的activity里面的View都要换肤(即时刷新)
做法:
为所有Activity添加theme的监听 当theme发生变化的时候 通知所有Activity进行换肤 如果使用fragment,activity内部遍历自己的fragment 也进行换肤

换肤分为两步
1.找到皮肤包的位置
2.给所有的view换肤

1.1皮肤包的位置方案

皮肤包根据实现的方式 存储的地方有所不同
像之前的文章(https://blog.csdn.net/u011109881/article/details/115558620)皮肤包自然是与我们的应用分离的
Android7.0左右已经支持theme切换,我们可以利用Android的这种新机制实现换肤 即定义不同的theme 那么此时的皮肤包其实就在apk内部 我们可以创建不同的theme 里面定义的颜色不同 这样我们在皮肤切换时切换theme 就能自动替换颜色
例如
themeDay.xml

<?xml version="1.0" encoding="utf-8"?>
<resources><style name="DayTheme" parent="activityScoutThemeNoTitle"><item name="commonButtonBgColor">@color/day_button</item><item name="commonTextColor">@color/day_text_color</item><item name="commonBg">@drawable/commonDayBg</item></style>
</resources>

themeNight.xml

<?xml version="1.0" encoding="utf-8"?>
<resources><style name="NightTheme" parent="DayTheme"><item name="commonButtonBgColor">@color/night_button</item><item name="commonBg">@drawable/commonNightBg</item><item name="commonTextColor">@color/night_text_color</item></style>
</resources>

这样我们就可以定义在布局中使用attr了

        <TextViewandroid:id="@+id/titleNameText"android:layout_width="wrap_content"android:layout_height="wrap_content"android:minWidth="200dp"android:text="@string/addressHome"android:textColor="?attr/commonTextColor"android:tag="textColor:attr:commonTextColor"android:textSize="28sp" />

1.2.替换所有view方案

这里列举三种方案
1.每一个activity里面都把需要换肤的View使用findviewbyid给找出来,然后调用代码去换肤;
弊端:当新增view删除view的时候 需要频繁修改代码 不够灵活 容易出错
2.获取activity里面的根布局,然后通过不断的循环获取子View, 查看xml是否添加了tag;
例子

        <TextViewandroid:id="@+id/titleNameText"android:layout_width="wrap_content"android:layout_height="wrap_content"android:minWidth="200dp"android:text="@string/addressHome"android:textColor="?attr/commonTextColor"android:tag="textColor:attr:commonTextColor"android:textSize="28sp" />

tag用于标记该view是否需要换肤
3.拦截View的创建,这是我们想要使用的方案
灵感来源于Android源码 我们发现继承Activity 和AppCompatActivity button的式样有所不同(继承AppCompatActivity Button是蓝色的) 好像换了皮肤 为什么呢
我们发现如果我们写一个Activity直接继承自Activity 那么Button使用的是android.widget.Button
但是如果继承的是AppCompatActivity Button的实例是com.google.android.material.button.MaterialButton
这就是Android material design的实现方式 拦截view的创建 换一套自己的view 使用新的皮肤
下面我们分析 Android自己是如何“换肤”的

2.setContentView源码分析

关于Activity的setContentView源码 我之前分析过 参见(https://blog.csdn.net/u011109881/article/details/111085949)
下面分析继承AppCompatActivity 时setContentView的源码(我的demo项目是支持AndroidX的 和不持支AndroidX的项目可能不同)
从Activity的setContentView开始跟进

   	// AppCompatActivitypublic void setContentView(@LayoutRes int layoutResID) {getDelegate().setContentView(layoutResID);}// AppCompatDelegatepublic abstract void setContentView(@LayoutRes int resId);// 无法继续跟进 需要知道getDelegate的实例public AppCompatDelegate getDelegate() {if (mDelegate == null) {mDelegate = AppCompatDelegate.create(this, this);}return mDelegate;}// AppCompatDelegateImplpublic static AppCompatDelegate create(@NonNull Activity activity,@Nullable AppCompatCallback callback) {return new AppCompatDelegateImpl(activity, callback);// 看来getDelegate的实例是AppCompatDelegateImpl//跟进AppCompatDelegateImpl的setContentView方法}public void setContentView(int resId) {ensureSubDecor();// 初始化DecorView 跟进去发现和继承Activity中installDecor方法的做法类似ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);// 找到系统布局供开发使用的布局contentParent.removeAllViews();LayoutInflater.from(mContext).inflate(resId, contentParent);// 将setContentView的布局填充到android.R.id.content的地方mAppCompatWindowCallback.getWrapped().onContentChanged();}

无论是继承Activity还是AppCompatActivity 其代码相差无几 最终的布局格局都是如下:
在这里插入图片描述
到目前位置 我们还没看到AppCompatActivity 是如何将Button替换了的 我们继续跟进

3.view的填充 LayoutInflater inflate填充布局源码分析(继承AppCompatActivity API 27)

首先需要注意AppCompatActivity的创建

    @Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {final AppCompatDelegate delegate = getDelegate();delegate.installViewFactory();delegate.onCreate(savedInstanceState);super.onCreate(savedInstanceState);}//AppCompatDelegate的installViewFactory只是抽象方法public abstract void installViewFactory();//具体实现在AppCompatDelegateImpl//AppCompatDelegateImpl继承自Factory2@Overridepublic void installViewFactory() {LayoutInflater layoutInflater = LayoutInflater.from(mContext);if (layoutInflater.getFactory() == null) {//注意setFactory2被调用LayoutInflaterCompat.setFactory2(layoutInflater, this);} else {if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImpl)) {Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"+ " so we can not install AppCompat's");}}}

前期准备工作完成 继续往下看

	// setContentView来自AppCompatDelegateImpl 注意继承自Factory2// class AppCompatDelegateImpl extends AppCompatDelegate//    implements MenuBuilder.Callback, LayoutInflater.Factory2 public void setContentView(int resId) {ensureSubDecor();ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);contentParent.removeAllViews();LayoutInflater.from(mContext).inflate(resId, contentParent);//跟进mAppCompatWindowCallback.getWrapped().onContentChanged();}// LayoutInflater.javapublic View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {return inflate(resource, root, root != null);}public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {final Resources res = getContext().getResources();if (DEBUG) {Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("+ Integer.toHexString(resource) + ")");}View view = tryInflatePrecompiled(resource, res, root, attachToRoot);if (view != null) {return view;}XmlResourceParser parser = res.getLayout(resource);try {return inflate(parser, root, attachToRoot);// 跟进} finally {parser.close();}}public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {synchronized (mConstructorArgs) {Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");final Context inflaterContext = mContext;final AttributeSet attrs = Xml.asAttributeSet(parser);Context lastContext = (Context) mConstructorArgs[0];mConstructorArgs[0] = inflaterContext;View result = root;try {...// Inflate all children under temp against its context.rInflateChildren(parser, temp, attrs, true);// 关键if (DEBUG) {System.out.println("-----> done inflating children");}// We are supposed to attach all the views we found (int temp)// to root. Do that now.if (root != null && attachToRoot) {root.addView(temp, params);}// Decide whether to return the root that was passed in or the// top view found in xml.if (root == null || !attachToRoot) {result = temp;}}} ...finally {// Don't retain static reference on context.mConstructorArgs[0] = lastContext;mConstructorArgs[1] = null;Trace.traceEnd(Trace.TRACE_TAG_VIEW);}return result;}}final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,boolean finishInflate) throws XmlPullParserException, IOException {rInflate(parser, parent, parent.getContext(), attrs, finishInflate);}void rInflate(XmlPullParser parser, View parent, Context context,AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {final int depth = parser.getDepth();int type;boolean pendingRequestFocus = false;while (((type = parser.next()) != XmlPullParser.END_TAG ||parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {if (type != XmlPullParser.START_TAG) {continue;}final String name = parser.getName();// 重点 后面会使用这个变量进行判断if (TAG_REQUEST_FOCUS.equals(name)) {pendingRequestFocus = true;consumeChildElements(parser);} else if (TAG_TAG.equals(name)) {parseViewTag(parser, parent, attrs);} else if (TAG_INCLUDE.equals(name)) {if (parser.getDepth() == 0) {throw new InflateException("<include /> cannot be the root element");}parseInclude(parser, context, parent, attrs);} else if (TAG_MERGE.equals(name)) {throw new InflateException("<merge /> must be the root element");} else {final View view = createViewFromTag(parent, name, context, attrs);// 继续跟进final ViewGroup viewGroup = (ViewGroup) parent;final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);rInflateChildren(parser, view, attrs, true);// 第一次走的这里 为了但是代码重复了 我跳过这次调用viewGroup.addView(view, params);}}if (pendingRequestFocus) {parent.restoreDefaultFocus();}if (finishInflate) {parent.onFinishInflate();}}private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {return createViewFromTag(parent, name, context, attrs, false);}View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,boolean ignoreThemeAttr) {...try {View view;if (mFactory2 != null) {//还记得前面的准备条件么 这里就走这条case view = mFactory2.onCreateView(parent, name, context, attrs);// 跟进// AppCompatDelegateImpl继承自Factory2 因此走的AppCompatDelegateImpl onCreateView方法} 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;}...}public final View onCreateView(View parent, String name, Context context, AttributeSet attrs) {return createView(parent, name, context, attrs);}public View createView(View parent, final String name, @NonNull Context context,@NonNull AttributeSet attrs) {if (mAppCompatViewInflater == null) {TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);String viewInflaterClassName =a.getString(R.styleable.AppCompatTheme_viewInflaterClass);if (viewInflaterClassName == null) {// Set to null (the default in all AppCompat themes). Create the base inflater// (no reflection)mAppCompatViewInflater = new AppCompatViewInflater();} else {try {Class<?> viewInflaterClass = Class.forName(viewInflaterClassName);mAppCompatViewInflater =(AppCompatViewInflater) viewInflaterClass.getDeclaredConstructor().newInstance();} catch (Throwable t) {Log.i(TAG, "Failed to instantiate custom view inflater "+ viewInflaterClassName + ". Falling back to default.", t);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// 重点 将各种view重新创建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 contextif (inheritContext && parent != null) {context = parent.getContext();}if (readAndroidTheme || readAppTheme) {// We then apply the theme on the context, if specifiedcontext = themifyContext(context, attrs, readAndroidTheme, readAppTheme);}if (wrapContext) {context = TintContextWrapper.wrap(context);}View view = null;// We need to 'inject' our tint aware Views in place of the standard framework versionsswitch (name) {case "TextView":view = createTextView(context, attrs);verifyNotNull(view, name);break;case "ImageView":view = createImageView(context, attrs);verifyNotNull(view, name);break;case "Button":view = createButton(context, attrs);verifyNotNull(view, name);break;case "EditText":view = createEditText(context, attrs);verifyNotNull(view, name);break;case "Spinner":view = createSpinner(context, attrs);verifyNotNull(view, name);break;case "ImageButton":view = createImageButton(context, attrs);verifyNotNull(view, name);break;case "CheckBox":view = createCheckBox(context, attrs);verifyNotNull(view, name);break;case "RadioButton":view = createRadioButton(context, attrs);verifyNotNull(view, name);break;case "CheckedTextView":view = createCheckedTextView(context, attrs);verifyNotNull(view, name);break;case "AutoCompleteTextView":view = createAutoCompleteTextView(context, attrs);verifyNotNull(view, name);break;case "MultiAutoCompleteTextView":view = createMultiAutoCompleteTextView(context, attrs);verifyNotNull(view, name);break;case "RatingBar":view = createRatingBar(context, attrs);verifyNotNull(view, name);break;case "SeekBar":view = createSeekBar(context, attrs);verifyNotNull(view, name);break;case "ToggleButton":view = createToggleButton(context, attrs);verifyNotNull(view, name);break;default:// The fallback that allows extending class to take over view inflation// for other tags. Note that we don't check that the result is not-null.// That allows the custom inflater path to fall back on the default one// later in this method.view = createView(context, name, attrs);}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:onClickcheckOnClickListener(view, attrs);}return view;}// MaterialComponentsViewInflater.javaprotected AppCompatButton createButton(@NonNull Context context, @NonNull AttributeSet attrs) {return new MaterialButton(context, attrs);}

可以看到 填充子布局的时候会根据xml解析后的节点名称 如SeekBar CheckBox Button 将该节点创建为不同的view
而MaterialButton内部有很多设置背景的方法 自然可以显示和普通button不同的式样
值得注意的是其中一个变量
private final MaterialButtonHelper materialButtonHelper;
它包含了一系列setBackground的方法 帮助MaterialButton进行背景设置 我们的换肤可以参考这种设计
AppCompateActivity创建View的时候会被拦截,不会走系统的LayoutInflater的创建而是走的AppCompatViewInflater的创建,就会被替换掉一些特定的View
其中最重要的是我们继承了AppCompatActivity 并在他的delegate调用installViewFactory AppCompatDelegateImpl本身又继承自Factory2
LayoutInflaterCompat.setFactory2(layoutInflater, this);
这句执行是关键

4.view创建的拦截

4.1 view的创建方式有如下三种

         View layoutView = View.inflate(this,R.layout.activity_main,null);layoutView = LayoutInflater.from(this).inflate(R.layout.activity_main,null);layoutView = LayoutInflater.from(this).inflate(R.layout.activity_main,null,false);

第一种我们看代码实际调用的就是第二种方式 LayoutInflater.from(this).inflate(R.layout.activity_main,null);

    public static View inflate(Context context, @LayoutRes int resource, ViewGroup root) {LayoutInflater factory = LayoutInflater.from(context);return factory.inflate(resource, root);}

我们看第二种的调用

    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {return inflate(resource, root, root != null);}

实际上又是调用的第三种 所以三种方式没有区别

4.2 LayoutInflater的初始化

	// LayoutInflaterpublic static LayoutInflater from(Context context) {LayoutInflater LayoutInflater =(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);if (LayoutInflater == null) {throw new AssertionError("LayoutInflater not found.");}return LayoutInflater;}//Activitypublic Object getSystemService(@ServiceName @NonNull String name) {if (getBaseContext() == null) {throw new IllegalStateException("System services not available to Activities before onCreate()");}if (WINDOW_SERVICE.equals(name)) {return mWindowManager;} else if (SEARCH_SERVICE.equals(name)) {ensureSearchManager();return mSearchManager;}return super.getSystemService(name);//here}// android.view.ContextThemeWrapperpublic Object getSystemService(String name) {if (LAYOUT_INFLATER_SERVICE.equals(name)) {if (mInflater == null) {mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this);}return mInflater;}return getBaseContext().getSystemService(name);}// LayoutInflater public static LayoutInflater from(Context context) {LayoutInflater LayoutInflater =(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);if (LayoutInflater == null) {throw new AssertionError("LayoutInflater not found.");}return LayoutInflater;}// androidx.appcompat.view.ContextThemeWrapperpublic Object getSystemService(String name) {if (LAYOUT_INFLATER_SERVICE.equals(name)) {if (mInflater == null) {mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this);}return mInflater;}return getBaseContext().getSystemService(name);}// LayoutInflater public static LayoutInflater from(Context context) {LayoutInflater LayoutInflater =(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);if (LayoutInflater == null) {throw new AssertionError("LayoutInflater not found.");}return LayoutInflater;}// ContextImplpublic Object getSystemService(String name) {return SystemServiceRegistry.getSystemService(this, name);}// SystemServiceRegistrypublic static Object getSystemService(ContextImpl ctx, String name) {ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);return fetcher != null ? fetcher.getService(ctx) : null;}//所有系统服务存储在这个hashMap中了private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =new HashMap<String, ServiceFetcher<?>>();// 提供了静态注册service的方法private static <T> void registerService(String serviceName, Class<T> serviceClass,ServiceFetcher<T> serviceFetcher) {SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);}// 我们在静态代码块中找到了初始化LAYOUT_INFLATER_SERVICE的部分static{...registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,new CachedServiceFetcher<LayoutInflater>() {@Overridepublic LayoutInflater createService(ContextImpl ctx) {return new PhoneLayoutInflater(ctx.getOuterContext());}});...}

因此LayoutInflater是一个单例 从始至终只有一个实例

4.3 inflate方法的参数attachToRoot

起点:View.inflate

	// Viewpublic static View inflate(Context context, @LayoutRes int resource, ViewGroup root) {LayoutInflater factory = LayoutInflater.from(context);return factory.inflate(resource, root);}// LayoutInflaterpublic View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {return inflate(resource, root, root != null);}// LayoutInflaterpublic View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {final Resources res = getContext().getResources();if (DEBUG) {Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("+ Integer.toHexString(resource) + ")");}// 获取xml解析器final XmlResourceParser parser = res.getLayout(resource);try {return inflate(parser, root, attachToRoot);} finally {parser.close();}}// LayoutInflaterpublic View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {synchronized (mConstructorArgs) {Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");final Context inflaterContext = mContext;final AttributeSet attrs = Xml.asAttributeSet(parser);Context lastContext = (Context) mConstructorArgs[0];mConstructorArgs[0] = inflaterContext;View result = root;try {// Look for the root node. // 寻找xml布局的根标签int type;while ((type = parser.next()) != XmlPullParser.START_TAG &&type != XmlPullParser.END_DOCUMENT) {// Empty}// 没有找到起始点START_TAG 会抛出异常if (type != XmlPullParser.START_TAG) {throw new InflateException(parser.getPositionDescription()+ ": No start tag found!");}// 获取当前标签的名字 比如 TextViewfinal String name = parser.getName();if (DEBUG) {System.out.println("**************************");System.out.println("Creating root view: "+ name);System.out.println("**************************");}// merge标签只能用在parent不为空并且attachToRoot=true的情况// 否则抛出异常if (TAG_MERGE.equals(name)) {if (root == null || !attachToRoot) {throw new InflateException("<merge /> can be used only with a valid "+ "ViewGroup root and attachToRoot=true");}rInflate(parser, root, inflaterContext, attrs, false);} else {// Temp is the root view that was found in the xmlfinal View temp = createViewFromTag(root, name, inflaterContext, attrs);//4.4用到// 布局参数     ViewGroup.LayoutParams params = null;if (root != null) {if (DEBUG) {System.out.println("Creating params from root: " +root);}// Create layout params that match root, if supplied// 生成布局参数 注意只有root不为空的时候 这些参数才会生效params = root.generateLayoutParams(attrs);if (!attachToRoot) {// Set the layout params for temp if we are not// attaching. (If we are, we use addView, below)temp.setLayoutParams(params);}}if (DEBUG) {System.out.println("-----> start inflating children");}// Inflate all children under temp against its context.rInflateChildren(parser, temp, attrs, true);if (DEBUG) {System.out.println("-----> done inflating children");}// We are supposed to attach all the views we found (int temp)// to root. Do that now.// 如果attachToRoot为true 并且root不为空 将之前创建的temp都填充到根view rootif (root != null && attachToRoot) {root.addView(temp, params);}// Decide whether to return the root that was passed in or the// top view found in xml.// 如果attachToRoot == false 返回子view(temp)填充的viewif (root == null || !attachToRoot) {result = temp;}}} catch (XmlPullParserException e) {final InflateException ie = new InflateException(e.getMessage(), e);ie.setStackTrace(EMPTY_STACK_TRACE);throw ie;} catch (Exception e) {final InflateException ie = new InflateException(parser.getPositionDescription()+ ": " + e.getMessage(), e);ie.setStackTrace(EMPTY_STACK_TRACE);throw ie;} finally {// Don't retain static reference on context.mConstructorArgs[0] = lastContext;mConstructorArgs[1] = null;Trace.traceEnd(Trace.TRACE_TAG_VIEW);}return result;}}

也就是说
inflate(int resource, ViewGroup root, boolean attachToRoot)这个方法有两种使用场景
1.root为空的情况
attachToRoot的作用可以忽略 返回的是填充完毕的resource的view
2.root不为为空的情况
如果 attachToRoot = false 则会将布局文件最外层的所有layout属性进行设置,当该view被添加到父view当中时,这些layout属性会自动生效
如果 attachToRoot = true 那么会给加载的布局文件的指定一个父布局,即root
其实很简单 即如果参数root不为空并且attachToRoot 为true 那么Android会解析resource的布局并调用addView添加到root中去
否则 仅仅是解析resource并将他转换为一个view对象 由外部自己决定是否调用addView将布局解析出的view添加到哪里去

4.4 如何拦截view 并重新创建view 具体分析

继续跟进createViewFromTag方法

    private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {return createViewFromTag(parent, name, context, attrs, false);}View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,boolean ignoreThemeAttr) {if (name.equals("view")) {name = attrs.getAttributeValue(null, "class");}// Apply a theme wrapper, if allowed and one is specified.if (!ignoreThemeAttr) {final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);final int themeResId = ta.getResourceId(0, 0);if (themeResId != 0) {context = new ContextThemeWrapper(context, themeResId);}ta.recycle();}if (name.equals(TAG_1995)) {// 彩蛋??//试了一下使用TAG_1995 报错 找不到这个类BlinkLayout 可能是AndroidX没有这个类// Let's party like it's 1995!return new BlinkLayout(context, attrs);}try {View view;if (mFactory2 != null) {view = mFactory2.onCreateView(parent, name, context, attrs);//走这里拦截view 重新创建//上面已经分析过 不再分析} 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) {// 如果mFactory2.onCreateView返回的view为空 调用其他方法继续创建view// 比如自定义view和其他不在mFactory2.onCreateView所列出类型的viewfinal Object lastContext = mConstructorArgs[0];mConstructorArgs[0] = context;try {// 判断是不是自定义View 因为自定义view中带有. 是路径全名if (-1 == name.indexOf('.')) {// onCreateView内部会调用createView 并且传入的prefix是android.view.view = onCreateView(parent, name, attrs);} else {//Android中的view 如Button TextViewview = createView(name, null, attrs);//因为上下两个case//都会走这个方法 继续分析该方法}} finally {mConstructorArgs[0] = lastContext;}}return view;} catch (InflateException e) {throw e;} catch (ClassNotFoundException e) {final InflateException ie = new InflateException(attrs.getPositionDescription()+ ": Error inflating class " + name, e);ie.setStackTrace(EMPTY_STACK_TRACE);throw ie;} catch (Exception e) {final InflateException ie = new InflateException(attrs.getPositionDescription()+ ": Error inflating class " + name, e);ie.setStackTrace(EMPTY_STACK_TRACE);throw ie;}}// LayoutInflater// 通过反射创建viewpublic final View createView(String name, String prefix, AttributeSet attrs)throws ClassNotFoundException, InflateException {// 先从缓存中拿构造方法 sConstructorMap是一个静态hashmapConstructor<? extends View> constructor = sConstructorMap.get(name);if (constructor != null && !verifyClassLoader(constructor)) {constructor = null;sConstructorMap.remove(name);}Class<? extends View> clazz = null;try {Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);// 从缓存中没有取到构造方法if (constructor == null) {// Class not found in the cache, see if it's real, and try to add it// 加载 Class// 如果前缀prefix不为空 就将其拼接clazz = mContext.getClassLoader().loadClass(prefix != null ? (prefix + name) : name).asSubclass(View.class);// 创建View的构造函数if (mFilter != null && clazz != null) {boolean allowed = mFilter.onLoadClass(clazz);if (!allowed) {failNotAllowed(name, prefix, attrs);}}constructor = clazz.getConstructor(mConstructorSignature);constructor.setAccessible(true);// 加入缓存集合集合sConstructorMap.put(name, constructor);} else {//省略生成构造方法的步骤// If we have a filter, apply it to cached constructorif (mFilter != null) {// Have we seen this name before?Boolean allowedState = mFilterMap.get(name);if (allowedState == null) {// New class -- remember whether it is allowedclazz = mContext.getClassLoader().loadClass(prefix != null ? (prefix + name) : name).asSubclass(View.class);boolean allowed = clazz != null && mFilter.onLoadClass(clazz);mFilterMap.put(name, allowed);if (!allowed) {failNotAllowed(name, prefix, attrs);}} else if (allowedState.equals(Boolean.FALSE)) {failNotAllowed(name, prefix, attrs);}}}Object lastContext = mConstructorArgs[0];if (mConstructorArgs[0] == null) {// Fill in the context if not already within inflation.mConstructorArgs[0] = mContext;}Object[] args = mConstructorArgs;args[1] = attrs;// 通过反射创建Viewfinal View view = constructor.newInstance(args);if (view instanceof ViewStub) {// Use the same context when inflating ViewStub later.final ViewStub viewStub = (ViewStub) view;viewStub.setLayoutInflater(cloneInContext((Context) args[0]));}mConstructorArgs[0] = lastContext;return view;} ...finally {Trace.traceEnd(Trace.TRACE_TAG_VIEW);}}

拦截view创建的demo:

首先有这样的关系
public class MainActivity extends BaseSkinActivity
public abstract class BaseSkinActivity extends BaseActivity

public abstract class BaseSkinActivity extends BaseActivity {private static final String TAG = "BaseSkinActivity";@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {LayoutInflater layoutInflater = LayoutInflater.from(this);LayoutInflaterCompat.setFactory2(layoutInflater, new LayoutInflater.Factory2() {@Nullable@Overridepublic View onCreateView(@Nullable View parent, @NonNull String name, @NonNull Context context, @NonNull AttributeSet attrs) {//在这里拦截view创建//可以在这里进行换肤Log.e(TAG, "onCreateView: 拦截了view " + name);View view = null;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);((Button) view).setText("拦截");// 预先将所有Button的文字改了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;}@Nullable@Overridepublic View onCreateView(@NonNull String name, @NonNull Context context, @NonNull AttributeSet attrs) {//Factory的方法 可以忽略Log.e(TAG, "onCreateView: ");return null;}});super.onCreate(savedInstanceState);}
}

小结:

本篇讲述的内容稍显杂乱 知识点整理如下:
1.换肤方案分析,分两步 第一,皮肤包存储位置 第二,如何找到所有的view并换肤
2.setContentView源码分析 我们需要清晰知道我们在mainActivity加载的布局 在系统布局中的具体位置
3.分析继承AppCompatActivity时inflate的方法 我们能知道AppCompatViewInflater内部如何将各种view替换为其他的view的
4.LayoutInflater的初始化是在一个静态代码块中 它是一个系统服务,并且是一个单例 各种获取LayoutInflater的方法其实都是返回的同一个对象
5.LayoutInflater.inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)方法详解 主要是看root是否为空以及attachToRoot是否为true
6.拦截view的demo
本节讲述的都是换肤的前提条件 下一步我们就可以具体实现换肤了

这篇关于红橙Darren视频笔记 换肤框架2 原理篇 view创建的拦截的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1016222

相关文章

流媒体平台/视频监控/安防视频汇聚EasyCVR播放暂停后视频画面黑屏是什么原因?

视频智能分析/视频监控/安防监控综合管理系统EasyCVR视频汇聚融合平台,是TSINGSEE青犀视频垂直深耕音视频流媒体技术、AI智能技术领域的杰出成果。该平台以其强大的视频处理、汇聚与融合能力,在构建全栈视频监控系统中展现出了独特的优势。视频监控管理系统EasyCVR平台内置了强大的视频解码、转码、压缩等技术,能够处理多种视频流格式,并以多种格式(RTMP、RTSP、HTTP-FLV、WebS

综合安防管理平台LntonAIServer视频监控汇聚抖动检测算法优势

LntonAIServer视频质量诊断功能中的抖动检测是一个专门针对视频稳定性进行分析的功能。抖动通常是指视频帧之间的不必要运动,这种运动可能是由于摄像机的移动、传输中的错误或编解码问题导致的。抖动检测对于确保视频内容的平滑性和观看体验至关重要。 优势 1. 提高图像质量 - 清晰度提升:减少抖动,提高图像的清晰度和细节表现力,使得监控画面更加真实可信。 - 细节增强:在低光条件下,抖

【Python编程】Linux创建虚拟环境并配置与notebook相连接

1.创建 使用 venv 创建虚拟环境。例如,在当前目录下创建一个名为 myenv 的虚拟环境: python3 -m venv myenv 2.激活 激活虚拟环境使其成为当前终端会话的活动环境。运行: source myenv/bin/activate 3.与notebook连接 在虚拟环境中,使用 pip 安装 Jupyter 和 ipykernel: pip instal

在cscode中通过maven创建java项目

在cscode中创建java项目 可以通过博客完成maven的导入 建立maven项目 使用快捷键 Ctrl + Shift + P 建立一个 Maven 项目 1 Ctrl + Shift + P 打开输入框2 输入 "> java create"3 选择 maven4 选择 No Archetype5 输入 域名6 输入项目名称7 建立一个文件目录存放项目,文件名一般为项目名8 确定

【学习笔记】 陈强-机器学习-Python-Ch15 人工神经网络(1)sklearn

系列文章目录 监督学习:参数方法 【学习笔记】 陈强-机器学习-Python-Ch4 线性回归 【学习笔记】 陈强-机器学习-Python-Ch5 逻辑回归 【课后题练习】 陈强-机器学习-Python-Ch5 逻辑回归(SAheart.csv) 【学习笔记】 陈强-机器学习-Python-Ch6 多项逻辑回归 【学习笔记 及 课后题练习】 陈强-机器学习-Python-Ch7 判别分析 【学

Java 创建图形用户界面(GUI)入门指南(Swing库 JFrame 类)概述

概述 基本概念 Java Swing 的架构 Java Swing 是一个为 Java 设计的 GUI 工具包,是 JAVA 基础类的一部分,基于 Java AWT 构建,提供了一系列轻量级、可定制的图形用户界面(GUI)组件。 与 AWT 相比,Swing 提供了许多比 AWT 更好的屏幕显示元素,更加灵活和可定制,具有更好的跨平台性能。 组件和容器 Java Swing 提供了许多

系统架构师考试学习笔记第三篇——架构设计高级知识(20)通信系统架构设计理论与实践

本章知识考点:         第20课时主要学习通信系统架构设计的理论和工作中的实践。根据新版考试大纲,本课时知识点会涉及案例分析题(25分),而在历年考试中,案例题对该部分内容的考查并不多,虽在综合知识选择题目中经常考查,但分值也不高。本课时内容侧重于对知识点的记忆和理解,按照以往的出题规律,通信系统架构设计基础知识点多来源于教材内的基础网络设备、网络架构和教材外最新时事热点技术。本课时知识

cross-plateform 跨平台应用程序-03-如果只选择一个框架,应该选择哪一个?

跨平台系列 cross-plateform 跨平台应用程序-01-概览 cross-plateform 跨平台应用程序-02-有哪些主流技术栈? cross-plateform 跨平台应用程序-03-如果只选择一个框架,应该选择哪一个? cross-plateform 跨平台应用程序-04-React Native 介绍 cross-plateform 跨平台应用程序-05-Flutte

Spring框架5 - 容器的扩展功能 (ApplicationContext)

private static ApplicationContext applicationContext;static {applicationContext = new ClassPathXmlApplicationContext("bean.xml");} BeanFactory的功能扩展类ApplicationContext进行深度的分析。ApplicationConext与 BeanF

顺序表之创建,判满,插入,输出

文章目录 🍊自我介绍🍊创建一个空的顺序表,为结构体在堆区分配空间🍊插入数据🍊输出数据🍊判断顺序表是否满了,满了返回值1,否则返回0🍊main函数 你的点赞评论就是对博主最大的鼓励 当然喜欢的小伙伴可以:点赞+关注+评论+收藏(一键四连)哦~ 🍊自我介绍   Hello,大家好,我是小珑也要变强(也是小珑),我是易编程·终身成长社群的一名“创始团队·嘉宾”