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原理完成。

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

相关推荐
Devil枫2 小时前
Kotlin高级特性深度解析
android·开发语言·kotlin
ChinaDragonDreamer2 小时前
Kotlin:2.1.20 的新特性
android·开发语言·kotlin
雨白12 小时前
Jetpack系列(二):Lifecycle与LiveData结合,打造响应式UI
android·android jetpack
kk爱闹14 小时前
【挑战14天学完python和pytorch】- day01
android·pytorch·python
每次的天空16 小时前
Android-自定义View的实战学习总结
android·学习·kotlin·音视频
恋猫de小郭16 小时前
Flutter Widget Preview 功能已合并到 master,提前在体验毛坯的预览支持
android·flutter·ios
断剑重铸之日17 小时前
Android自定义相机开发(类似OCR扫描相机)
android
随心最为安17 小时前
Android Library Maven 发布完整流程指南
android
岁月玲珑17 小时前
【使用Android Studio调试手机app时候手机老掉线问题】
android·ide·android studio
还鮟21 小时前
CTF Web的数组巧用
android