返回介绍

6 LayoutInflaterCompat

发布于 2024-12-23 22:04:08 字数 9696 浏览 0 评论 0 收藏 0

LayoutInflater 我们用的很多,一般都是用来把布局填充成 View,它里面有两个方法:

// api 1 引入
setFactory(Factory factory)

// api 11 引入
setFactory2(Factory2 factory)

这里面需要传入接口的实现类,如下:

public interface Factory {
  /**
   * Hook you can supply that is called when inflating from a LayoutInflater.
   * You can use this to customize the tag names available in your XML
   * layout files.
   * 
   * <p>
   * Note that it is good practice to prefix these custom names with your
   * package (i.e., com.coolcompany.apps) to avoid conflicts with system
   * names.
   * 
   * @param name Tag name to be inflated.
   * @param context The context the view is being created in.
   * @param attrs Inflation attributes as specified in XML file.
   * 
   * @return View Newly created view. Return null for the default
   *     behavior.
   */
  public View onCreateView(String name, Context context, AttributeSet attrs);
}

public interface Factory2 extends Factory {
  /**
   * Version of {@link #onCreateView(String, Context, AttributeSet)}
   * that also supplies the parent that the view created view will be
   * placed in.
   *
   * @param parent The parent that the created view will be placed
   * in; <em>note that this may be null</em>.
   * @param name Tag name to be inflated.
   * @param context The context the view is being created in.
   * @param attrs Inflation attributes as specified in XML file.
   *
   * @return View Newly created view. Return null for the default
   *     behavior.
   */
  public View onCreateView(View parent, String name, Context context, AttributeSet attrs);
}

通过上述接口可以看出来,新的 setFactory2(Factory2 factory) 比老的 setFactory(Factory factory) 在构建 View 的时候多传入了一个 Parent View。如果你想用 setFactory2(Factory factory) 需要实现带 Parent View 和不带 Parent View 的两个方法,比较复杂,所以 v4 包中的 LayoutInflaterCompat 就为我们提供了兼容性处理,先看用法:

LayoutInflater layoutInflater = getLayoutInflater();
LayoutInflaterCompat.setFactory(layoutInflater, new LayoutInflaterFactory() {
		
		@Override
		public View onCreateView(View parent, String name, Context context,
				AttributeSet attrs) {
			// name 是布局文件中 View 的名称,在这里可以坐很多操作,比如:
			// 给 TextView 设置字体。
			// 把 TextView 变成 Button,如果需要的话。

			// 修改后的 View,如果返回空则会调用 LayoutInflater 本身实例化 View 的方法,详情见 createViewFromTag 中 try 下面的逻辑。
			return null;
		}
	});

举个例子: 现在我们用 AS 开发,一般默认是继承 AppCompatActivity ,在初始化的时候:

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
	// 获取兼容包的委托类
  final AppCompatDelegate delegate = getDelegate();

	// 安装 Factory
  delegate.installViewFactory();
  delegate.onCreate(savedInstanceState);
  if (delegate.applyDayNight() && mThemeId != 0) {
    // If DayNight has been applied, we need to re-apply the theme for
    // the changes to take effect. On API 23+, we should bypass
    // setTheme(), which will no-op if the theme ID is identical to the
    // current theme ID.
    if (Build.VERSION.SDK_INT >= 23) {
      onApplyThemeResource(getTheme(), mThemeId, false);
    } else {
      setTheme(mThemeId);
    }
  }
  super.onCreate(savedInstanceState);
}

AppCompatDelegate 的实现类 AppCompatDelegateImplV7 中,· installViewFactory()

@Override
public void installViewFactory() {
	// 使用 LayoutInflaterCompat 进行兼容性适配
  LayoutInflater layoutInflater = LayoutInflater.from(mContext);
  if (layoutInflater.getFactory() == null) {
		// 如果之前没有设置工厂,则自己实现接口然后传入,用来实现 AppCompat 特性,比如支持向低版本 tint 着色等新特性。
		// 实际上它也是在工厂的实现类中用 AppCompatXXX 去替换 XXX,比如用 AppConpatTextView 替换 TextView 等诸如此类。
    LayoutInflaterCompat.setFactory(layoutInflater, this);
  } else {
		// 如果已经设置了 Factory 并且不是当前类,则什么也不做,只打印日志。这样就会失去上述中 tint 等特性。
    if (!(LayoutInflaterCompat.getFactory(layoutInflater)
        instanceof AppCompatDelegateImplV7)) {
      Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"
          + " so we can not install AppCompat's");
    }
  }
}

上述方法实际上调用 LayoutInflaterCompat 的下面的方法:

public static void setFactory(LayoutInflater inflater, LayoutInflaterFactory factory) {
  IMPL.setFactory(inflater, factory);
}

IMPL 是 LayoutInflaterCompat 中内部接口 LayoutInflaterCompatImpl 的实现类。这个接口有三个实现类:

  1. LayoutInflaterCompatImplBase。
  2. LayoutInflaterCompatImplV11。
  3. LayoutInflaterCompatImplV21。
static final LayoutInflaterCompatImpl IMPL;
static {
  final int version = Build.VERSION.SDK_INT;
  if (version >= 21) {
		// 5.0 及其以上版本
    IMPL = new LayoutInflaterCompatImplV21();
  } else if (version >= 11) {
		// 3.0 及其以上版本
    IMPL = new LayoutInflaterCompatImplV11();
  } else {
		// 低于 3.0 版本
    IMPL = new LayoutInflaterCompatImplBase();
  }
}

因此,调用 LayoutInflaterCompat 的 setFactory 方法,实际是调用对应版本的 IMPL 的 setFactory 方法。

1. 低于 3.0 版本

LayoutInflaterCompatImplBase 中调用的是 LayoutInflaterCompatBase 的 setFactory 方法,

static void setFactory(LayoutInflater inflater, LayoutInflaterFactory factory) {
  inflater.setFactory(factory != null ? new FactoryWrapper(factory) : null);
}

static LayoutInflaterFactory getFactory(LayoutInflater inflater) {
  LayoutInflater.Factory factory = inflater.getFactory();
  if (factory instanceof FactoryWrapper) {
    return ((FactoryWrapper) factory).mDelegateFactory;
  }
  return null;
}

其中, FactoryWrapperLayoutInflaterCompatBase 的静态内部类:

class LayoutInflaterCompatBase {

  static class FactoryWrapper implements LayoutInflater.Factory {

    final LayoutInflaterFactory mDelegateFactory;

    FactoryWrapper(LayoutInflaterFactory delegateFactory) {
      mDelegateFactory = delegateFactory;
    }

    @Override
    public View onCreateView(String name, Context context, AttributeSet attrs) {
      return mDelegateFactory.onCreateView(null, name, context, attrs);
    }

    public String toString() {
      return getClass().getName() + "{" + mDelegateFactory + "}";
    }
  }

  static void setFactory(LayoutInflater inflater, LayoutInflaterFactory factory) {
    inflater.setFactory(factory != null ? new FactoryWrapper(factory) : null);
  }

  static LayoutInflaterFactory getFactory(LayoutInflater inflater) {
    LayoutInflater.Factory factory = inflater.getFactory();
    if (factory instanceof FactoryWrapper) {
      return ((FactoryWrapper) factory).mDelegateFactory;
    }
    return null;
  }

}

核心的方法就是 FactoryWrapperonCreateView ,可以看到它调用的是带有 Parent View 参数的 onCreateView 方法,不过 Parent View 传的是 null。

2. 大于 3.0 小于 5.0

LayoutInflaterCompatImplV11 中调用的就是 LayoutInflaterCompatHC 的 setFactory 方法,LayoutInflaterCompatHC 中:

static void setFactory(LayoutInflater inflater, LayoutInflaterFactory factory) {
	// 如果传入的 Factory 不为空则包装一下,否则传空
  final LayoutInflater.Factory2 factory2 = factory != null
      ? new FactoryWrapperHC(factory) : null;
	// 设置 Factory2.
  inflater.setFactory2(factory2);
	// 获取当前 Inflater 的 Factory。
  final LayoutInflater.Factory f = inflater.getFactory();
	// 如果属于 Factory2 则通过反射把 mFactory 赋值给 mFactory2。
  if (f instanceof LayoutInflater.Factory2) {
    // The merged factory is now set to getFactory(), but not getFactory2() (pre-v21).
    // We will now try and force set the merged factory to mFactory2
    forceSetFactory2(inflater, (LayoutInflater.Factory2) f);
  } else {
		// 否则设置 mFactory2 为新创建的 Factory2。
    // Else, we will force set the original wrapped Factory2
    forceSetFactory2(inflater, factory2);
  }
}

// 利用反射修改 Factory2
/**
 * For APIs >= 11 && < 21, there was a framework bug that prevented a LayoutInflater's
 * Factory2 from being merged properly if set after a cloneInContext from a LayoutInflater
 * that already had a Factory2 registered. We work around that bug here. If we can't we
 * log an error.
 * 对于版本>- 11 并且 < 21,如果调用 cloneInContext 从 LayoutInflater 克隆一个 LayoutInflater,在 FrameWork 层有一个 bug 阻止了 LayoutInflater 的 Factory2 的合并,因为已经有一个 Factory2 被注册了,所在在此通过反射的方式去修改 Factory2。
 */
static void forceSetFactory2(LayoutInflater inflater, LayoutInflater.Factory2 factory) {
  if (!sCheckedField) {
    try {
      sLayoutInflaterFactory2Field = LayoutInflater.class.getDeclaredField("mFactory2");
      sLayoutInflaterFactory2Field.setAccessible(true);
    } catch (NoSuchFieldException e) {
      Log.e(TAG, "forceSetFactory2 Could not find field 'mFactory2' on class "
          + LayoutInflater.class.getName()
          + "; inflation may have unexpected results.", e);
    }
    sCheckedField = true;
  }
  if (sLayoutInflaterFactory2Field != null) {
    try {
      sLayoutInflaterFactory2Field.set(inflater, factory);
    } catch (IllegalAccessException e) {
      Log.e(TAG, "forceSetFactory2 could not set the Factory2 on LayoutInflater "
          + inflater + "; inflation may have unexpected results.", e);
    }
  }
}

bug 的具体产生原因见: LayoutInflater 在 Api 21 以下的 setFactory2 的 bug 是怎么产生的

3. 大于 5.0

static void setFactory(LayoutInflater inflater, LayoutInflaterFactory factory) {
  inflater.setFactory2(factory != null
      ? new LayoutInflaterCompatHC.FactoryWrapperHC(factory) : null);
}

这个其实没啥用,可以跟第二条合并的,但是最新的 v4 包没有改,但是在 api >= 20 的 Android 源码里面,其实已经这么做了:

static final LayoutInflaterCompatImpl IMPL;
static {
  final int version = Build.VERSION.SDK_INT;
  if (version >= 11) {
    IMPL = new LayoutInflaterCompatImplV11();
  } else {
    IMPL = new LayoutInflaterCompatImplBase();
  }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文