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

一、前言

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

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

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

(说完了...)

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

二、使用

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

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

ini 复制代码
 LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
 View view = inflater.inflate(resource,root,attachToRoot);

通过Activity中的getLayoutInflater()方法

ini 复制代码
 View view = getLayoutInflater().inflate(resource,root,attachToRoot);

通过View的静态inflate()方法

ini 复制代码
 View view = View.inflate(resource,root,attachToRoot);

通过LayoutInflater的from()方法

ini 复制代码
 View view = LayoutInflater.from(this).inflate(resource,root,attachToRoot);

三、inflate

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

less 复制代码
 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;
 }

它们使用的都是:

ini 复制代码
 LayoutInflater LayoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

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

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

less 复制代码
 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)。我将展开/省略该方法的部分代码:

java 复制代码
 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这个方法的细节。视图如何创建出来?让我们看看最终指向的方法:

ini 复制代码
 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;
     }
 }

tryCreateView

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

less 复制代码
 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:

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

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

像这样:

typescript 复制代码
 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;
     }
 });

当然你也可以这样:

typescript 复制代码
 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:

less 复制代码
 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.....

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

onCreateView

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

less 复制代码
 //反射构造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对象

IllegalStateException

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

kotlin 复制代码
 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
             }
 ​
         }
     }
 }

两个设置方法都会崩溃~

csharp 复制代码
 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基本一致)

ini 复制代码
 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均只能调用一次。但是明明我们只调用了一次?为什么会抛出异常呢?
3.

AppCompatDelegate.createView

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

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

java 复制代码
 @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对比就知道

ini 复制代码
 @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中。
4.

AppCompatViewInflater.createView

less 复制代码
 @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做出的兼容操作。所以你需要如此做:

typescript 复制代码
 ​
  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的流程:

kotlin 复制代码
 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()
         }
     }
 }
kotlin 复制代码
 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的操作。所以可以直接如此做:

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

六、 结尾

部分代码可以在这里看到

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

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

相关推荐
众拾达人28 分钟前
Android自动化测试实战 Java篇 主流工具 框架 脚本
android·java·开发语言
吃着火锅x唱着歌1 小时前
PHP7内核剖析 学习笔记 第四章 内存管理(1)
android·笔记·学习
_Shirley3 小时前
鸿蒙设置app更新跳转华为市场
android·华为·kotlin·harmonyos·鸿蒙
hedalei4 小时前
RK3576 Android14编译OTA包提示java.lang.UnsupportedClassVersionError问题
android·android14·rk3576
锋风Fengfeng4 小时前
安卓多渠道apk配置不同签名
android
枫_feng5 小时前
AOSP开发环境配置
android·安卓
叶羽西5 小时前
Android Studio打开一个外部的Android app程序
android·ide·android studio
qq_171538857 小时前
利用Spring Cloud Gateway Predicate优化微服务路由策略
android·javascript·微服务
Vincent(朱志强)8 小时前
设计模式详解(十二):单例模式——Singleton
android·单例模式·设计模式
mmsx9 小时前
android 登录界面编写
android·登录界面