support-v7是如何将TextView替换为AppCompatTextView的?

Posted Alex_MaHao

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了support-v7是如何将TextView替换为AppCompatTextView的?相关的知识,希望对你有一定的参考价值。

日常在使用AppCompatActivity的时候,发现对于在xml中编写的布局最终都会变成Compat...等控件,那么是如何实现这种全局的控件替换的呢,这是此次分析的如要问题。

AppCompatActivitysetContentView()

首先,我们猜想,会不会是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中去。

LayoutInflaterinflater()

在上面的逻辑中,发现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的替换关键节点就在这里呢 ?

AppCompatActivityonCreate()

 @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?

在 support-v7 包中使用 LiveData

用字符串中的图像替换字符,然后设置为 Textview

Android的RecyclerView

如何将一个/两个 TextView 设置为另一个 TextView 的高度?

用片段替换某些东西