support-v7是如何将TextView替换为AppCompatTextView的?
Posted Alex_MaHao
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了support-v7是如何将TextView替换为AppCompatTextView的?相关的知识,希望对你有一定的参考价值。
日常在使用AppCompatActivity
的时候,发现对于在xml
中编写的布局最终都会变成Compat...
等控件,那么是如何实现这种全局的控件替换的呢,这是此次分析的如要问题。
AppCompatActivity
的setContentView()
首先,我们猜想,会不会是AppCompatActivity
重写了setContentView()
做了一层封装呢,基于这种猜想,看一下它里面的实现方法
@Override
public void setContentView(@LayoutRes int layoutResID)
getDelegate().setContentView(layoutResID);
getDelegate()
方法是为了在不同的android
版本上获取不同的处理方法
private static AppCompatDelegate create(Context context, Window window,
AppCompatCallback callback)
if (Build.VERSION.SDK_INT >= 24)
return new AppCompatDelegateImplN(context, window, callback);
else if (Build.VERSION.SDK_INT >= 23)
return new AppCompatDelegateImplV23(context, window, callback);
else if (Build.VERSION.SDK_INT >= 14)
return new AppCompatDelegateImplV14(context, window, callback);
else if (Build.VERSION.SDK_INT >= 11)
return new AppCompatDelegateImplV11(context, window, callback);
else
return new AppCompatDelegateImplV9(context, window, callback);
这些delegate
是一个集成关系,新版本会继承老的版本
class AppCompatDelegateImplN extends AppCompatDelegateImplV23
对于setContentView()
方法,实际是在AppCompatDelegateImplV9
中实现的,逻辑如下
@Override
public void setContentView(int resId)
ensureSubDecor();
ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
LayoutInflater.from(mContext).inflate(resId, contentParent);
mOriginalWindowCallback.onContentChanged();
看到这里一脸懵逼!!!貌似什么都没有做啊,查到contentView
,调用LayoutInflater
初始化xml
布局文件,然后添加到contentView
中去。
LayoutInflater
的inflater()
在上面的逻辑中,发现AppCompatActivity
的实质是调用了LayoutInflater
的方法,基于此,看一下他的实现逻辑。
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot)
// paser : xml布局文件的解析对象 root : 要加入的父布局 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.
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!");
// 获取布局文件的根节点
final String name = parser.getName();
if (DEBUG)
System.out.println("**************************");
System.out.println("Creating root view: "
+ name);
System.out.println("**************************");
// merge 标签
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 xml
// 创建根节点
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
// 如果root不为null,则基于root对temp添加布局参数,宽高等
if (root != null)
if (DEBUG)
System.out.println("Creating params from root: " +
root);
// Create layout params that match root, if supplied
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);
// 初始化所有的子节点,里面的实质其实也是调用createViewFromTag
rInflateChildren(parser, temp, attrs, true);
//....
// 是否添加到父布局
if (root != null && attachToRoot)
root.addView(temp, params);
catch (Exception e)
// ....
return result;
inflator()
中首先获取根节点,然后通过createViewFromTag
方法创建View
。
然后判断root
(父布局),不为null就对布局的根节点添加布局参数。这也是inflater()
不同参数产生的布局不同的原因。
其次调用rInflateChildren()
创造布局文件中所有的子控件,其实质也会调用createViewFromTag
方法。
最后根据参数attachToRoot
判断是否加到父布局中。
无论是子布局还是父布局,最终都是通过调用createViewFromTag()
生成子控件的。
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr)
// ...
try
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);
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;
// ...
省略部分逻辑,在createViewFromTag()
中,可以看到先判断factory
是否为null,如果不为null,那么会通过factory
来构造view
。
并且LayoutInfater
提供了public void setFactory(Factory factory)
方法,那么AppCompat
的替换关键节点就在这里呢 ?
AppCompatActivity
的onCreate()
@Override
protected void onCreate(@Nullable Bundle savedInstanceState)
final AppCompatDelegate delegate = getDelegate();
// 关键方法
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);
delegate.installViewFactory()
方法其内部就是设置了setFactory()
逻辑如下
@Override
public void installViewFactory()
LayoutInflater layoutInflater = LayoutInflater.from(mContext);
if (layoutInflater.getFactory() == null)
LayoutInflaterCompat.setFactory2(layoutInflater, this);
else
if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImplV9))
Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"
+ " so we can not install AppCompat's");
以上是关于support-v7是如何将TextView替换为AppCompatTextView的?的主要内容,如果未能解决你的问题,请参考以下文章
如何将setOnClickListener设置为以编程方式添加的TextView?