Android一杯冰美式的时间--LayoutInflater

2,224 阅读13分钟

一、前言

上文【Android一杯冰美式的时间--去找setContentView】,最后因为我太困了而结束在LayoutInflater。这篇文章还是要补足玩这一步的。

在 Android 应用中,界面是通过布局文件(通常是 XML 文件)来定义的。这些布局文件描述了界面的结构和外观,包括各种控件和它们的属性。但是,为了在应用运行时使用这些布局,我们需要将它们从 XML 文件转换成 Java 或 Kotlin 代码中的View对象。这就是 LayoutInflater 的作用所在。

如果你没用过LayoutInflater....当我没说。

(说完了...)

如果您有任何疑问、对文章写的不满意、发现错误、想吐槽或者有更好的想法,欢迎在评论、私信或邮件中提出,非常感谢您的支持。🙏

二、使用

看看我们平常使用LayoutInflater的方法:

  1. 通过系统服务获取布局加载器

     LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
     View view = inflater.inflate(resource,root,attachToRoot);
    
  2. 通过Activity中的getLayoutInflater()方法

     View view = getLayoutInflater().inflate(resource,root,attachToRoot);
    
  3. 通过View的静态inflate()方法

     View view = View.inflate(resource,root,attachToRoot);
    
  4. 通过LayoutInflater的from()方法

     View view = LayoutInflater.from(this).inflate(resource,root,attachToRoot);
    

三、inflate

最后你会发现“二”中这些用法间接或直接的调用了LayoutInflater中的静态方法:

 public static LayoutInflater from(@UiContext Context context) {
     LayoutInflater LayoutInflater =
             (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
     if (LayoutInflater == null) {
         throw new AssertionError("LayoutInflater not found.");
     }
     return LayoutInflater;
 }

它们使用的都是:

 LayoutInflater LayoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

PS:系统会初始化LAYOUT_INFLATER_SERVICE服务,AssertionError("LayoutInflater not found.")几乎不会出现(反正我没看到过)。

然后它们都会调用LayoutInflater.inflate ,而它有三个重载函数

 public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
     return inflate(resource, root, root != null);
 }
 ​
 public View inflate(XmlPullParser parser, @Nullable ViewGroup root) {
     return inflate(parser, root, root != null);
 }
 //会创建一个XmlResourceParser对象
 public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
     final Resources res = getContext().getResources(); 
     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)

最后方法都会指向inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot)。我将展开/省略该方法的部分代码:

 public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
     synchronized (mConstructorArgs) {
         // ... [省略部分代码]
         View result = root;
         try {
             //⬇️advanceToRootNode(parser)展开 
             //移动解析器到 XML 文档的根元素,找根布局
             int type;
             while ((type = parser.next()) != XmlPullParser.START_TAG &&
                 type != XmlPullParser.END_DOCUMENT) {
                 // Empty
             }
 ​
             if (type != XmlPullParser.START_TAG) {
                 throw new InflateException(parser.getPositionDescription()
                     + ": No start tag found!");
             }
             //⬆️advanceToRootNode(parser)展开 
             final String name = parser.getName();
             // <merge> 标签只能在 root 非 null 且 attachToRoot 为 true 的情况下使用
             if (TAG_MERGE.equals(name)) {
                 if (root == null || !attachToRoot) {
                     throw new InflateException("<merge /> can only be used with a valid ViewGroup root and attachToRoot=true");
                 }
                 rInflate(parser, root, inflaterContext, attrs, false);
             } else {
                 // 创建根视图
                 // 按下不表 1⃣️
                 final View temp = createViewFromTag(root, name, inflaterContext, attrs);
                 ViewGroup.LayoutParams params = null;
                 // 如果 root 非 null,则生成与 root 匹配的布局参数
                 if (root != null) {
                     params = root.generateLayoutParams(attrs);
                     // 如果不附加到 root,则先设置布局参数
                     if (!attachToRoot) {
                         temp.setLayoutParams(params);
                     }
                 }
 ​
                 // 递归地填充所有子视图
                 rInflateChildren(parser, temp, attrs, true);
 ​
                 // 如果 root 非 null 且 attachToRoot 为 true,则将解析的视图附加到 root
                 if (root != null && attachToRoot) {
                     root.addView(temp, params);
                 }
 ​
                 // 根据 attachToRoot 决定返回哪个视图
                 if (root == null || !attachToRoot) {
                     result = temp;
                 }
             }
         } 
         // ... [省略部分代码]
         return result;
     }
 }

总注释中,我们可以了解到

  • @Nullable ViewGroup root:

    • 这个参数指定了布局文件中顶级视图的父容器。它可以是 null,表示没有父容器。
    • rootnull 时,解析出的视图可以选择性地附加到这个 root
    • 当使用 <merge> 标签时,root 不能为 null,且 attachToRoot 必须为 true
  • boolean attachToRoot:

    • 这个参数决定了解析出的视图是否应该立即附加到 root 视图组。
    • attachToRoottruerootnull 时,解析出的视图会被添加到 root 中。
    • attachToRootfalse 时,即使 rootnull,解析出的视图也不会立即添加到 root 中,而是返回这个顶级视图供后续操作。

你可能还是有点迷糊,总结性的来说:在任何我们不负责将View添加进ViewGroup的情况下都应该将attachToRoot设置为false。比如RecyclerViewonCreateViewHolder。比如FragmentonCreateViewFragmentManager 负责将 Fragment 的视图插入到容器中。如果在 onCreateView 中已经将视图附加到 root,那么当 FragmentManager 尝试再次执行这个操作时,就会引发 IllegalStateException,因为一个视图不能有多个父视图。

反之你就可以使用true值。

四、Factory2和Factory

inflate代码1⃣️中,选择性的忽略了createViewFromTag这个方法的细节。视图如何创建出来?让我们看看最终指向的方法:

 View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
         boolean ignoreThemeAttr) {
     // ... [省略部分代码]
     try {
         //tryCreateView
         View view = tryCreateView(parent, name, context, attrs);
 ​
         if (view == null) {
             final Object lastContext = mConstructorArgs[0];
             mConstructorArgs[0] = context;
             try {
                 if (-1 == name.indexOf('.')) {
                     //onCreateView
                     view = onCreateView(context, parent, name, attrs);
                 } else {
                     //createView
                     view = createView(context, name, null, attrs);
                 }
             } finally {
                 mConstructorArgs[0] = lastContext;
             }
         }
     // ... [省略部分代码] 
         return view;
     }
 }
  1. tryCreateView

    我们可以看到的是,view是否为空,直接影响着下面流程。那有必要看看tryCreateView的具体内容:

     public final View tryCreateView(@Nullable View parent, @NonNull String name, @NonNull Context context, @NonNull AttributeSet attrs) {
         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);
         }
     ​
         return view;
     }
    

    好了,对于不太熟悉的人来说,直接懵掉。mFactory2?mFactory?先来说mFactory:

     private Factory mFactory;
     public interface Factory {
         //当从 LayoutInflater 加载布局时,你可以提供一个回调(hook),在布局加载过程中被调用。你可以使用这个回调来自定义你的 XML 布局文件中可用的标签名
         View onCreateView(@NonNull String name, @NonNull Context context,@NonNull AttributeSet attrs);
     }
    

    看起来mFactory 允许开发者提供自定义的逻辑来替代或增强标准的视图创建过程。官方🪝钩子!

    像这样:

     LayoutInflater inflater = LayoutInflater.from(context);
     inflater.setFactory(new LayoutInflater.Factory() {
         @Override
         public View onCreateView(String name, Context context, AttributeSet attrs) {
             // 根据 name 创建自定义视图
             if (name.equals("HarmonyOSGreateAgain")) {
                 return new HarmonyOSView(context, attrs);
             }
             // 对于非自定义视图,返回 null 以使用默认行为
             return null;
         }
     });
    

    当然你也可以这样:

     LayoutInflater inflater = LayoutInflater.from(context);
     inflater.setFactory(new LayoutInflater.Factory() {
         @Override
         public View onCreateView(String name, Context context, AttributeSet attrs) {
             // 根据 name 创建自定义视图
             if (name.equals("TextView")) {
                 return new HarmonyOSTextView(context, attrs);
             }
             // 对于非自定义视图,返回 null 以使用默认行为
             return null;
         }
     });
    

    没错!你可以创建自定义 UI 组件或者改变标准组件的行为!

    接着看看mFactory2:

     private Factory2 mFactory2;
     public interface Factory2 extends Factory {
         View onCreateView(@Nullable View parent, @NonNull String name, @NonNull Context context, @NonNull AttributeSet attrs);
     }
    

    细心的你发现,这玩意继承自Factory,且多了一个parent参数。是的,如你想的那样,它可以对创建 View 的 Parent 进行控制。这就是它的主要目的。

    你已经注意到Factory是通过setFactory设置的,那Factory2你也该猜到了。setFactory2.....

    那么什么是标准组件的行为呢?

  2. onCreateView

    这就要看到createViewFromTag中的onCreateViewcreateView了。onCreateView最终指向createView,我们看看createView

     //反射构造View
     public final View createView(@NonNull Context viewContext, @NonNull String name,@Nullable String prefix, @Nullable AttributeSet attrs) throws ClassNotFoundException, InflateException {
         // 确保参数不为空
         Objects.requireNonNull(viewContext);
         Objects.requireNonNull(name);
     ​
         // 尝试从缓存中获取视图的构造函数
         Constructor<? 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) {
                 // 如果构造函数不在缓存中,尝试加载视图类
                 clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
                         mContext.getClassLoader()).asSubclass(View.class);
     ​
                 // 应用过滤器(如果有)来决定是否允许加载类
                 if (mFilter != null && clazz != null) {
                     boolean allowed = mFilter.onLoadClass(clazz);
                     if (!allowed) {
                         failNotAllowed(name, prefix, viewContext, attrs);
                     }
                 }
     ​
                 // 获取并缓存构造函数
                 constructor = clazz.getConstructor(mConstructorSignature);
                 constructor.setAccessible(true);
                 sConstructorMap.put(name, constructor);
             } else {
                 // 对缓存的构造函数应用过滤器
                 // ... [省略过滤器逻辑]
             }
     ​
             // 设置构造函数参数并创建视图实例
             Object lastContext = mConstructorArgs[0];
             mConstructorArgs[0] = viewContext;
             Object[] args = mConstructorArgs;
             args[1] = attrs;
     ​
             try {
                 //反射构造
                 final View view = constructor.newInstance(args);
                 // 特殊处理 ViewStub
                 // ... [省略 ViewStub 处理逻辑]
                 return view;
             } finally {
                 mConstructorArgs[0] = lastContext;
             }
         } catch (NoSuchMethodException | ClassCastException | ClassNotFoundException | Exception e) {
             // 处理各种异常
             // ... [省略异常处理逻辑]
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_VIEW);
         }
     }
    

    简单吧~总结下来就两个:

    1. 从缓存集合中获取当前View对应的构造方法,没有则创建,并存入缓存。
    2. 反射构造方法,创建对应的View对象
  3. IllegalStateException

    当我们兴致勃勃的去准备大改特改的时候,你会发现:

     class MainActivity : AppCompatActivity() {
         override fun onCreate(savedInstanceState: Bundle?) {
             super.onCreate(savedInstanceState)
             setContentView(R.layout.activity_main)
     ​
             val inflater: LayoutInflater = LayoutInflater.from(this)
             //使用LayoutInflater.Factory
             inflater.factory = LayoutInflater.Factory { name, context, attrs -> null }
             //使用LayoutInflater.Factory2
             inflater.factory2 = object : LayoutInflater.Factory2 {
                 override fun onCreateView(
                     parent: View?,
                     name: String,
                     context: Context,
                     attrs: AttributeSet
                 ): View? {
                     return XXX
                 }
     ​
                 override fun onCreateView(name: String, context: Context, attrs: AttributeSet): View? {
                     return XXXX
                 }
     ​
             }
         }
     }
    

    两个设置方法都会崩溃~

     Caused by: java.lang.IllegalStateException: A factory has already been set on this LayoutInflater
        at android.view.LayoutInflater.setFactory(LayoutInflater.java:317)
       //和
     Caused by: java.lang.IllegalStateException: A factory has already been set on this LayoutInflater
        at android.view.LayoutInflater.setFactory2(LayoutInflater.java:375)
    

    错误来自setFactory2/setFactory:(PS:setFactory2/setFactory基本一致)

     public void setFactory(Factory factory) {
         if (mFactorySet) {
             throw new IllegalStateException("A factory has already been set on this LayoutInflater");
         }
         if (factory == null) {
             throw new NullPointerException("Given factory can not be null");
         }
         mFactorySet = true;
         if (mFactory == null) {
             mFactory = factory;
         } else {
             mFactory = new FactoryMerger(factory, null, mFactory, mFactory2);
         }
     }
     public void setFactory2(Factory2 factory) {
         if (mFactorySet) {
             throw new IllegalStateException("A factory has already been set on this LayoutInflater");
         }
         if (factory == null) {
             throw new NullPointerException("Given factory can not be null");
         }
         mFactorySet = true;
         if (mFactory == null) {
             mFactory = mFactory2 = factory;
         } else {
             mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2);
         }
     }
    

    我们可以看出,setFactory2/setFactory均只能调用一次。但是明明我们只调用了一次?为什么会抛出异常呢?

  4. AppCompatDelegate.createView

    我们四处寻找setFactory2/setFactory的使用者,找到了AppCompatDelegate以及它的实现类AppCompatDelegateImpl,眼熟吧!【Android一杯冰美式的时间--去找setContentView】提到的!

    最终我们可以在实现类中找到

     @Override
     public void installViewFactory() {
         LayoutInflater layoutInflater = LayoutInflater.from(mContext);
         if (layoutInflater.getFactory() == null) {
             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");
             }
         }
     }
    

    至于为什么没有setFactory的调用,你也找不到呢,因为被弃用了,而setFactory2也会 mFactory = mFactory2 = factory。相信细心的你也发现~

    我们继续对setFactory2,进行跟踪,那么我们肯定需要寻找,它的实现类。最后我们可以定位到AppCompatDelegateImplonCreateView->createView方法,我们和LayoutInflater中的createView对比就知道

     @Override
     public View createView(View parent, final String name, @NonNull Context context,
             @NonNull AttributeSet attrs) {
         // 检查是否已经有一个 AppCompatViewInflater 实例,如果没有,则创建一个
         if (mAppCompatViewInflater == null) {
             TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
             String viewInflaterClassName = a.getString(R.styleable.AppCompatTheme_viewInflaterClass);
             if (viewInflaterClassName == null) {
                 // 如果在主题中没有指定自定义视图创建器,则使用默认的 AppCompatViewInflater
                 mAppCompatViewInflater = new AppCompatViewInflater();
             } else {
                 try {
                     // 尝试通过反射加载并实例化自定义的 AppCompatViewInflater
                     Class<?> viewInflaterClass = mContext.getClassLoader().loadClass(viewInflaterClassName);
                     mAppCompatViewInflater = (AppCompatViewInflater) viewInflaterClass.getDeclaredConstructor().newInstance();
                 } catch (Throwable t) {
                     // 如果反射失败,回退到默认的 AppCompatViewInflater
                     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) {
             // 对于 Android Lollipop 之前的版本,检查是否需要继承上下文
             if (mLayoutIncludeDetector == null) {
                 mLayoutIncludeDetector = new LayoutIncludeDetector();
             }
             if (mLayoutIncludeDetector.detect(attrs)) {
                 inheritContext = true;
             } else {
                 inheritContext = (attrs instanceof XmlPullParser) ? ((XmlPullParser) attrs).getDepth() > 1 : shouldInheritContext((ViewParent) parent);
             }
         }
     ​
         // 使用 AppCompatViewInflater 创建视图
         return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,
                 IS_PRE_LOLLIPOP, // 仅在 Lollipop 之前的版本中读取 android:theme
                 true, // 始终读取 app:theme,用于遗留原因
                 VectorEnabledTintResources.shouldBeUsed() // 根据配置决定是否使用着色资源
         );
     }
    

    显然核心代码在AppCompatViewInflater.createView中。

  5. AppCompatViewInflater.createView

     @Nullable
     public final View createView(@Nullable View parent, @NonNull final String name,
             @NonNull Context context, @NonNull AttributeSet attrs, boolean inheritContext,
             boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
         final Context originalContext = context;
     ​
         // 根据需要调整上下文
         if (inheritContext && parent != null) {
             context = parent.getContext();
         }
         if (readAndroidTheme || readAppTheme) {
             context = themifyContext(context, attrs, readAndroidTheme, readAppTheme);
         }
         if (wrapContext) {
             context = TintContextWrapper.wrap(context);
         }
     ​
         View view = null;
     ​
         // 根据标签名创建 AppCompat 支持的视图
         switch (name) {
             case "TextView":
                 view = createTextView(context, attrs);
                 verifyNotNull(view, name);
                 break;
             // ...其他视图创建逻辑
             default:
                 // 尝试使用自定义方法创建视图
                 view = createView(context, name, attrs);
         }
     ​
         // 如果原始上下文和调整后的上下文不同,尝试重新创建视图
         if (view == null && originalContext != context) {
             view = createViewFromTag(context, name, attrs);
         }
     ​
         // 检查视图的 onClick 属性和无障碍属性
         if (view != null) {
             checkOnClickListener(view, attrs);
             backportAccessibilityAttributes(context, view, attrs);
         }
     ​
         return view;
     }
     ​
    

    在switch (name)中,返回的都是AppCompatXXX。因此,我们可以确认,默认用于确保在旧版 Android 系统上,应用也能够使用 Material Design 样式的视图,同时保持向后兼容性。也就是统一 Material Design样式。而它最后指向了AppCompatViewInflater

    好了现在你已经学会使用Factory了。

    值得注意的是,一般而言你需要保留AppCompatViewInflater做出的兼容操作。所以你需要如此做:

     ​
      LayoutInflater.from(this).setFactory2(new LayoutInflater.Factory2() {
             @Override
             public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
         
                 // 调用 AppCompatDelegate 的createView方法
                 getDelegate().createView(parent, name, context, attrs);
                 // 自由发挥
                 return XXX;
             }
         
             @Override
             public View onCreateView(String name, Context context, AttributeSet attrs) {
                 return XXX;
             }
         });
    

    显然使用setFactory需要在加载布局前,也就是调用inflate方法之前。

五、用途

你可能已经意识到了,setFactory的用途,通过 LayoutInflater 创建 View 时候的一个回调,可以通过 LayoutInflater.Factory 来改造或定制创建 View 的过程。比如样式替换,比如自定义的View等等等。这里我们展示接管View的背景绘制,你可以扩展成“无需自定义View,直接添加属性便可以实现shape、selector的效果”

以下算是一个通用操作了,也是模仿AppCompatViewInflater的流程:

 import android.annotation.SuppressLint
 import android.app.Activity
 import android.content.Context
 import android.view.LayoutInflater
 import androidx.appcompat.app.AppCompatActivity
 import androidx.core.view.LayoutInflaterCompat
 ​
 object BackgroundLibrary {
     fun inject(context: Context?): LayoutInflater? {
         val inflater: LayoutInflater? = if (context is Activity) {
             context.layoutInflater
         } else {
             LayoutInflater.from(context)
         }
         if (inflater == null) {
             return null
         }
         if (inflater.factory2 == null) {
             val factory = setDelegateFactory(context!!)
             inflater.factory2 = factory
         } else if (inflater.factory2 !is BackgroundFactory) {
             forceSetFactory2(inflater)
         }
         return inflater
     }
     /**
      * 注入自定义 LayoutInflater 工厂的主方法
      * 如果因为其他库已经设置了factory,可以使用该方法去进行inject,在其他库的setFactory后面调用即可
      */
     fun inject2(context: Context?): LayoutInflater? {
         // 根据 Context 类型获取 LayoutInflater 实例
         val inflater: LayoutInflater? = if (context is Activity) {
             context.layoutInflater
         } else {
             LayoutInflater.from(context)
         }
         if (inflater == null) {
             return null
         }
         // 强制设置自定义工厂
         forceSetFactory2(inflater)
         return inflater
     }
 ​
     // 创建并配置 BackgroundFactory 实例
     private fun setDelegateFactory(context: Context): BackgroundFactory {
         val factory = BackgroundFactory()
         if (context is AppCompatActivity) {
             // 如果是 AppCompatActivity 实例,使用其委托创建视图
             val delegate = context.delegate
             factory.setInterceptFactory { name, context, attrs ->
                 delegate.createView(null, name, context, attrs)
             }
         }
         return factory
     }
 ​
     // 通过反射技术强制为 LayoutInflater 设置自定义工厂
     @SuppressLint("DiscouragedPrivateApi")
     private fun forceSetFactory2(inflater: LayoutInflater) {
         val compatClass = LayoutInflaterCompat::class.java
         val inflaterClass = LayoutInflater::class.java
         try {
             // 访问私有字段并修改其值,以便可以设置自定义工厂
             val sCheckedField = compatClass.getDeclaredField("sCheckedField").apply {
                 isAccessible = true
                 setBoolean(compatClass, false)
             }
             val mFactory = inflaterClass.getDeclaredField("mFactory").apply {
                 isAccessible = true
             }
             val mFactory2 = inflaterClass.getDeclaredField("mFactory2").apply {
                 isAccessible = true
             }
             // 创建 BackgroundFactory 实例
             val factory = BackgroundFactory()
             if (inflater.factory2 != null) {
                 factory.setInterceptFactory2(inflater.factory2)
             } else if (inflater.factory != null) {
                 factory.setInterceptFactory(inflater.factory)
             }
             // 设置工厂到 LayoutInflater 的 mFactory 和 mFactory2 字段
             mFactory2[inflater] = factory
             mFactory[inflater] = factory
         } catch (e: IllegalAccessException) {
             // 处理反射访问异常
             e.printStackTrace()
         } catch (e: NoSuchFieldException) {
             // 处理反射访问异常
             e.printStackTrace()
         }
     }
 }
 class BackgroundFactory : LayoutInflater.Factory2 {
     // 已经存在的工厂和工厂2的引用
     private var mViewCreateFactory: LayoutInflater.Factory? = null
     private var mViewCreateFactory2: LayoutInflater.Factory2? = null
 ​
     // 用于创建视图的方法
     override fun onCreateView(name: String, context: Context, attrs: AttributeSet): View? {
         // 检查是否为特定前缀的视图,如果是,则不处理
         if (name.startsWith("com.fuck.harmonyos.view")) {
             return null
         }
 ​
         var view: View? = null
 ​
         // 首先尝试使用已经存在的工厂创建视图
         if (mViewCreateFactory2 != null) {
             view = mViewCreateFactory2!!.onCreateView(name, context, attrs)
             if (view == null) {
                 view = mViewCreateFactory2!!.onCreateView(null, name, context, attrs)
             }
         } else if (mViewCreateFactory != null) {
             view = mViewCreateFactory!!.onCreateView(name, context, attrs)
         }
 ​
         // 对创建的视图应用自定义背景处理
         return setViewBackground(name, context, attrs, view)
     }
 ​
     // 设置拦截的工厂
     fun setInterceptFactory(factory: LayoutInflater.Factory) {
         mViewCreateFactory = factory
     }
 ​
     fun setInterceptFactory2(factory: LayoutInflater.Factory2) {
         mViewCreateFactory2 = factory
     }
 ​
     // Factory2 接口的另一个方法实现
     override fun onCreateView(
         parent: View?,
         name: String,
         context: Context,
         attrs: AttributeSet
     ): View? {
         return onCreateView(name, context, attrs)
     }
 ​
     // 伴生对象,包含静态方法和属性
     companion object {
         // ... [省略静态方法和属性的注释]
     }
 }

一个BackgroundFactory,BackgroundLibrary。就可以随意组合了,具体实现可以在BackgroundFactory。慢慢琢磨。

如果是一个懒狗,肯定不乐意在每个Activity中,去添加inject的操作。所以可以直接如此做:

 class FuckApplication : Application() {
     init {
         registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks {
             override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
                 BackgroundLibrary.inject(activity)
         //BackgroundLibrary.inject2(activity)
             }
         // ... [省略部分代码]
         })
     }
 }

六、 结尾

部分代码可以在这里看到

感谢仓库BackgroundLibrary,该库基于LayoutInflater.Factory原理完成。

如果您有任何疑问、对文章写的不满意、发现错误、想吐槽或者有更好的想法,欢迎在评论、私信或邮件中提出,非常感谢您的支持。🙏