Android setContentView+Layoutinflate ★★★★

1.setContentView源码

每天都在用的setContentView,你真的了解它吗?一起看看它的源码实现吧!

public void setContentView(View view) {

    getWindow().setContentView(view);

    initActionBar();

}

它里面调用了getWindow.setContentView。点击getWindow看看里面是什么!

 public Window getWindow() {

    return mWindow;

}

返回了一个Window对象,这个mWindow就是Window的子类PhoneWindow,每一个Activity都有一个PhoneWindow对象。

PhoneWindow就是布局的第一层了!

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5a2f6Iqz6Iqz,size_10,color_FFFFFF,t_70,g_se,x_16

于是到PhoneWindow类去看看setContentView的实现吧。

Phone window.class:

public class PhoneWindow extends Window implements MenuBuilder.Callback {

    //第一个setContentView

    @Override

    public void setContentView(int layoutResID) {

        if (mContentParent == null) {

            installDecor();

        } else {

            mContentParent.removeAllViews();

        }

        mLayoutInflater.inflate(layoutResID, mContentParent);

        final Callback cb = getCallback();

        if (cb != null && !isDestroyed()) {

            cb.onContentChanged();

        }

    }

    //第二个setContentView

    @Override

    public void setContentView(View view) {

        setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));

    }

    //第三个setContentView

    @Override

    public void setContentView(View view, ViewGroup.LayoutParams params) {

        if (mContentParent == null) {

            installDecor();

        } else {

            mContentParent.removeAllViews();

        }

        mContentParent.addView(view, params);

        final Callback cb = getCallback();

        if (cb != null && !isDestroyed()) {

            cb.onContentChanged();

        }

    }

}

一共三个setContentView方法,平时用的最多的就是第一个了。具体看看:首先判断了mContentParent,当mContentParent为空的时候,就会执行installDecor()方法,那么我们肯定要到installDecor中去找找这个mContentParent是什么了。

Phone window.class:

private void installDecor() {

    if (mDecor == null) {

        mDecor = generateDecor(-1);

        //...

    } else {

        mDecor.setWindow(this)

    }

    if (mContentParent == null) {

        mContentParent = generateLayout(mDecor);

        //...

    }

}

我们发现,当mContentParent为空的时候,会执行generateLayout()方法,同时需要传入一个mDecor,所以要想弄清楚mContentParent,我们首先要知道mDecor是什么。往上面看,mDecor是通过generateDecor()方法创建出来的,于是到generateDecor()中一探究竟。

protected DecorView generateDecor(int featureId) {

    return new DecorView(context,featureId, this,getAttributes());

}

new了一个DecorView对象,DecorView就是我们界面中最顶层的View了,这个View的结构是这样的:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5a2f6Iqz6Iqz,size_20,color_FFFFFF,t_70,g_se,x_16

 DecorView继承于FrameLayout,它有一个子view即LinearLayout,方向为竖直方向,里面有两个FrameLayout,上面的FrameLayout是TitleBar之类的,下面的FrameLayout就是我们的ContentView,所谓的setContentView就是往这个FrameLayout里面添加我们的布局View。

现在可以画出第二层了!

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5a2f6Iqz6Iqz,size_11,color_FFFFFF,t_70,g_se,x_16

 现在mDecor有了,终于可以进入到generateLayout(mDecor);里面去看看了!

protected ViewGroup generateLayout(DecorView decor) {

    //省略一大段设置Window样式的代码

    int layoutResource;

    int features = getLocalFeatures();

    ...

    mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

     ViewGroup contentParent =(ViewGroup)findViewById(ID_ANDROID_CONTENT);

     //...           

     return contentParent;

}

generateLayout 方法主要有 3 点:

①加载系统的布局

②将布局加载到创建的 DecorView 中

③通过DecorView找到 id 为 com.android.internal.R.id.content 的 ViewGroup 返回,也就是 mContentParent。

ID_ANDROID_CONTENT就是R.id.content,就是上图中这个FrameLayout:

3eaf1dd6424143389443d907eb77439b.png

我们看到contentParent就是这个FrameLayout!所以,setContentView方法中的mContentParent就是这个FrameLayout,也就是我们自己布局文件的父控件!

现在回到PhoneWindow中的setContentView方法中,继续追踪。

在setContentView方法中,首先会先判断mContentParent是否为空,如果为空说明我们还没有DecorView,于是会调用installDecor。之后DecorView准备好了,mContentParent就会指向我们的ContentView,由于是新建的,mContentParent中肯定没有子View,如果不是新建的,我们要先把mContentParent中的子View全部清干净。接下来通过反射加载我们传入的布局,也就是这句代码:

mLayoutInflater.inflate(layoutResID, mContentParent);

所以来看看Layoutinflate.inflate()方法的源码吧。

2.LayoutInflater

①LayoutInflater基本用法

​ LayoutInflater对象可以通过LayoutInflater的from方法获取:

public static LayoutInflater from(Context context) {

    LayoutInflater LayoutInflater =(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

    if (LayoutInflater == null) {

        throw new AssertionError("LayoutInflater not found.");

    }

    return LayoutInflater;

}

②LayoutInflater类共有4个inflate方法。

LayoutInflater.class:

public View inflate(int resource, ViewGroup root){

    return inflate(resource, root, root!=null);

}

public View inflate(XmlPullParser parsee, ViewGroup root) {

    return inflate(parser, root, root!=null);

}

public View inflate(int resource, ViewGroup root, boolean attachToRoot) {

    ……

}

 public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {

    ……

}

其中resource是布局文件的id,root表示父View,attachToRoot表示是否将布局表示的View作为子View添加到root中。

前三个inflate方法最终都会调用最后一个。在最后一个inflate方法中通过createViewFromTag方法获得temp,也就是在xml布局文件中找到的根视图(布局文件里根视图只能有一个)。

③Layoutinflater的inflate(int,ViewGroup,boolean)方法分析

public View inflate( int resource, 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();

    }

}

首先获取Context相关联的Resources,然后根据布局文件的id将布局文件加载成XmlResourceParser对象,用于解析布局文件,再调用inflate(XmlPullParser, ViewGroup, boolean)方法:

public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {

    synchronized (mConstructorArgs) {

        final Context inflaterContext = mContext;

        final AttributeSet attrs = Xml.asAttributeSet( parser);

        View result = root;

        try{

            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");

                }

                rInflateChildren( parser, root, inflaterContext, attrs, false);

            } else {

               //temp is the root view that was found in the xml   temp是布局文件里的根view

                final View temp = createViewFromTag( root, name, inflaterContext, attrs);

               ViewGroup.LayoutParams params = null;

                if(root != null) {

                  params = root.generateLayoutParams( attrs);

                    if(!attachToRoot) {

                         temp.setLayoutParams(params);

                    }

                }

                rInflateChildren(parse,temp,attrs,true);

                if(root != null && attachToRoot) {

                    root.addView(temp,params);

                }

                if(root == null || !attachToRoot) {

                    result = temp;

                }

            }

        } catch{ 

        }

        return result;

    }

}

该方法最后返回值为result, result首先赋值为root,当root为null或者attachToRoot为false时,result赋值为temp,也就是布局文件对应的View,意思是root不为null且attachToRoot为true时该方法将返回root,其他情况返回布局文件解析得到的View。

这里总结一下:

1)如果root为null,那么构建的view将是一个独立的个体,其顶层布局设置的属性无效。

2)如果root不为null,又分两种情况:①attachToRoot为false,此时顶层布局的属性值会依托于root构建,所以此时的xml根布局的属性有效,且根布局产生的view不是root的子布局。②attachedToRoot为true,此时顶层布局的属性值会依托于root构建,所以此时的xml根布局的属性有效,且根布局产生的view是root的子布局,是通过addView实现的。

​ try代码块中节点name为最外层的节点名称,根据name不同共有两个分支:①根节点name是merge;②根节点name不是merge。

分别从这两种情况分析。

(1)根节点是merge时分支代码如下:

    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);

​ 当root为null或者attachToRoot为false时,将抛出异常。

​ rInflate(XmlPullParser, View, Context, AttributeSet, boolean)继续解析xml中布局,以root作为根节点,于是merge标签中的布局被合并到root中去(merge标签中高度、宽度、线性布局的方向等参数被舍弃),所以要求root不为null, attachToRoot为true。

(2)根节点不是merge时分支主要代码如下:

    final View temp = createViewFromTag(root, name, inflaterContext, attrs);

    ViewGroup.LayoutParams params = null;

    if (root != null) {

        params = root.generateLayoutParams(attrs);

        if (!attachToRoot) {

            temp.setLayoutParams(params);

        }

    }

    rInflate(parser, temp, attrs, true);

    if (root != null && attachToRoot) {

        root.addView(temp, params);

    }

    if (root == null || !attachToRoot) {

        result = temp;

    }

 主要步骤如下:

1)调用createViewFromTag方法,根据布局文件中最外层标签name创建对应的View,即temp;

2)如果root不为null,调用root的generateLayoutParams生成LayoutParams, 获取temp的layout_width、layout_height、layout_weight等参数(generateLayoutParams方法在LinearLayout、RelativeLayout等ViewGroup中都有具体实现,生成对应的LayoutParams),如果attachToRoot为false,则设置temp的LayoutParams参数;

3)调用rInflateChildren方法,继续解析子节点,该方法调用rInflate方法;

4)如果root不为null,并且attachToRoot为true,则把布局文件解析成的View,添加到root中。

5)如果root为null或者attachToRoot为false,整个方法返回值置为temp, 不然就是root。

这段源码可以解释一个常见的问题:使用ListView的时候,比如item布局如下所示:

<?xml version="1.0" encoding="utf-8">

<LinearLayout xmls:android="https…"

    android:layout_width="match_parent"

    android:layout_height="100dp">

    <TextView

        android:layout_width="wrap_content"

        android:layout_height="50dp"

        android:text="hello">

</LinearLayout>

然后在Adapter的getView方法里通过以下代码获取item布局:

LayoutInflater.from(mContext).inflate(R.layout.listitem, null);

这时候发现,获得的item的顶层布局高度实际为50dp,设置的固定高度100dp不起作用。网上大部分的解决方法要么是在listeitem的根布局外面再套一层layout,要么是在根布局上设置minHeight,其实正确的方式应该是这样解决:

LayoutInflater.from(mContext).inflate(R.layout.listitem, parent, false);

这两种写法最终都会调用同一个inflate方法,他们的区别在这里:

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);  

    }  

rInflate(parser, temp, attrs, true);

if (root != null && attachToRoot) {

    root.addView(temp, params);

}

if (root == null || !attachToRoot) {

    result = temp;

}

只有在传入的父view(root)不为空的时候,才会去解析item顶层布局的LayoutParams参数;否则,root为空的时候,不去解析LayoutParams,直接把temp返回(LayoutParams参数不参与计算),所以在inflate方法第二个参数传入null的时候,item顶层布局设置成多高都不会生效。

那如果此时第二个参数不传null,但是第三个参数改为true呢?如下所示:

LayoutInflater.from(mContext).inflate(R.layout.listitem, parent, false);

这时候程序会崩溃。

UnsupportedOprationException: addView(View, LayoutParams) is not supported in AdapterView。

因为此时会调用root.addView(temp, params);这在Adapter里是不支持的。

④rInflate(XmlPullParser, View, Context, AttributeSet, boolean)方法分析

void rInflate(XmlPullParser parser, View parent, Context context, AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {

    final int depth = parser.getDepth();

    int type;

    boolean pendingRequestFocus = false;

    while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {

        if (type != XmlPullParser.START_TAG) {

            continue;

        }

        final String name = parser.getName();

        if (TAG_REQUEST_FOCUS.equals(name)) {

            pendingRequestFocus = true;

            consumeChildElements(parser);

        } else if (TAG_TAG.equals(name)) {

            parseViewTag(parser, parent, attrs);

        } else if (TAG_INCLUDE.equals(name)) {

            if (parser.getDepth() == 0) {

                throw new InflateException("<include /> cannot be the root element");

            }

            parseInclude(parser, context, parent, attrs);

        } else if (TAG_MERGE.equals(name)) {

            throw new InflateException("<merge /> must be the root element");

        } else {

            final View view = createViewFromTag( parent, name, context, attrs);

            final ViewGroup viewGroup = (ViewGroup) parent;

            final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);

            rInflateChildren(parser, view, attrs, true);

            viewGroup.addView(view, params);

        }

    }

    ......

}

主要看节点为include、merge以及正常节点,主要逻辑如下:

1)节点为include,不能为布局根节点;

2)节点为merge,则抛出异常,提示merge必须为布局根节点;

3)节点为正常节点,则调用createViewFromTag创建对应的View,然后rInflateChildrend方法迭代继续解析节点内的节点,最后将节点对应的View添加到parent中去。

⑤createViewFromTag(View, String, Context, AttributeSet)方法分析

private View createViewFromTag(View parent,String name,Context context,AttributeSet attrs) {   

    return createViewFromTag( parent, name, context, attrs, false);

}

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,boolean ignoreThemeAttr) {

    try {

        View view = tryCreateView(parent, name, context, attrs);

        if (view == null) {

            final Object lastContext = mConstructorArgs[0];

            mConstructorArgs[0] = context;

            try {

                if (-1 == name.indexOf('.')) {

                    view = onCreateView(context, parent, name, attrs);

                } else {

                    view = createView(context, name, null, attrs);

                }

            } finally {

                mConstructorArgs[0] = lastContext;

            }

        }

        return view;

    } catch () {}

}

首先调用tryCreateView创建view,如果创建的view为空,则根据name是否是类名(是否有 . 分隔)来判断调用onCreateView还是createView,而onCreateView实质上也是调用了createView, 中间加了类名前缀 ”android.view.“,这也是xml布局中部分View类不需要加全路径的原因,如View、ViewStub、SurfaceView类等(这些类实际上被Factory2拦截生成了)。

tryCreateView方法源码如下:

public final View tryCreateView(View parent, String name, Context context, 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;

}

该方法根据mFactory、mFactory2、mPrivateFactory三个Factory类来创建View。

⑥LayoutInflater实例的mFactory属性(Factory实例)、mFactory2属性(Factory2实例)的作用:拦截与处理View类的创建

查找LayoutInflater的setFactory2方法使用, LayoutInflater.setFactory2 -> LayoutInflaterCompat.setFactory2 -> AppCompatDelegateImpl.installViewFactory()

于是找到LayoutInflater.Factory2的接口具体实现为AppCompatDelegateImpl类,AppCompatActivity的setContentView也是由该类负责具体实现的。

AppCompatDelegateImpl类中Factory类的onCreateView是调用Factory2类的onCreateView实现的,Factory2.onCreateView最后调用createView方法,最终由AppCompatViewInflater类的createView创建View,部分代码如下:

final View createView(View parent, final String name, Context context,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;

    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);

    }     ...

    return view;

}

于是TextView标签解析成AppCompatTextView,ImageView标签解析成AppCompatImageView等等, 其他View由createViewFromTag创建:

private View createViewFromTag(Context context, String name, AttributeSet attrs) {

    if (name.equals("view")) {

        name = attrs.getAttributeValue(null, "class");

    }

    try {

        mConstructorArgs[0] = context;

        mConstructorArgs[1] = attrs;

        if (-1 == name.indexOf('.')) {

            for (int i = 0; i < sClassPrefixList.length; i++) {

                final View view = createViewByPrefix( context, name, sClassPrefixList[i]);

                if (view != null) {

                    return view;

                }

            }

            return null;

        } else {

            return createViewByPrefix(context, name, null);

        }

    } catch (Exception e) {

        return null;

    } finally {

        mConstructorArgs[0] = null;

        mConstructorArgs[1] = null;

    }

}

createViewFromTag根据View的名称name以及"android.widget."、"android.view."、"android.webkit."这三个前缀尝试生成View,生成View则返回, 如android.widget包名下LinearLayout、RelativeLayout、ListView等,android.view下的View、ViewStub、SurfaceView等(拦截了LayoutInflater中部分View的创建),android.webkit包名下的WebView。

⑥LayoutInflater类的mPrivateFactory属性

断点调试, 查看LayoutInflater的实例属性,如下:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5a2f6Iqz6Iqz,size_20,color_FFFFFF,t_70,g_se,x_16

可以看到mPrivateFactory属性为MainActivity的实例(该属性在Activity的attach方法中设置), 一步步查找MainActivity的父类,最终在Activity类中看到了LayoutInflater.Factory2的实现,在FragmentActivity中进行了重写。

1)Activity中逻辑

Activity中Factory2接口实现如下:

public View onCreateView(View parent, String name,Context context, AttributeSet attrs) {

    if (!"fragment".equals(name)) {

        return onCreateView(name, context, attrs);

    }

    return mFragments.onCreateView(parent, name, context, attrs);

}

于是,当标签是fragment时交给mFragments进行处理, 最终由FragmentManagerImpl实现fragment标签的创建。主要逻辑是: 依次根据fragment的id、tag、containerId来获取已经存在的fragment,没有就创建,然后设置xml中配置的id、tag、containerId,调用addFragment将fragment添加到Activity中,最后返回fragment的View。

2)FragmentActivity中LayoutInflater.Factory2的两个方法均由FragmentLayoutInflaterFactory来负责具体View的创建。

来看看AppCompatActivity类:

public class AppCompatActivity {

  public AppCompatActivity(int contentLayoutId){

    super(contentLayoutId);

    initDelegate();

  }

    private void initDelegate() {

        final AppCompatDelegate delegate = getDelegate();

        delegate.installViewFactory();

        delegate.onCreate(getSavedStateRegistry( ).consumeRestoredStateForkey(DELEGATE_TAG));

    }

}

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");

        }

    }

}

可见,AppCompatActivity 在initDelegate中设置了Factory,所以会执行 mFactory2.onCreateView 的方法。

class AppCompatDelegateImpl {

    public View createView(View parent, final String name, @NonNull Context context, @NonNull AttributeSet attrs) {

        ...

        return mAppCompatViewInflater. createView(parent, name, context, attrs, inheritContext, IS_PRE_LOLLIPOP, true,           VectorEnabledTintResources.shouldBeUsed() 

        );

    }

    ...

}

public class AppCompatViewInflater {

    final View createView(View parent, final String name, @NonNull Context context, @NonNull AttributeSet attrs, boolean inheritContext, boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {

        ...

        switch (name) {

            case "TextView":

                view = createTextView(context, attrs);

                verifyNotNull(view, name);

               …

            default:

                view = createView(context, name, attrs);

        }

        ...

      if (view == null && originalContext != context){

            view = createViewFromTag(context, name, attrs);

        }

    }

    ...

}

可以看到当 API < 21时 View 被转换成为其他的 View,所以我们可以理解为 AppCompatActivity 会兼容低版本。

有兴趣的可以去做个小实验,分别继承 Activity 和 AppCompatActivity,将 TextView 打印出来。继承 Activity TextView 还是 TextView,而继承 AppCompatActivity 时 TextView 打印为 AppCompatTextView。

public abstract class LayoutInflater {

    ...

    View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,boolean ignoreThemeAttr) {

        View view = tryCreateView(parent, name, context, attrs);

        if (view == null) {

            final Object lastContext = mConstructorArgs[0];

            mConstructorArgs[0] = context;

            try {

                if (-1 == name.indexOf('.')) {

                    view = onCreateView(context, parent, name, attrs);

                } else {

                    view = createView(context, name, null, attrs);

                }

            } finally {

                mConstructorArgs[0] = lastContext;

            }

        }

        return view;

}

经过刚才的分析,继承 AppCompatActivity View 会被创建成对应的 View,而Activity 是没有设置 Factory 的,所以 View 为空,将执行下面的判断。

if (-1 == name.indexOf('.')) {

    view = onCreateView(context, parent, name, attrs);

} else {

    view = createView(context, name, null, attrs);

}

如果没有'.',也就是说如果是系统控件会执行 onCreateView 方法;如果有'.',就是我们的自定义控件,如'com.xxx',将会执行 createView 方法。

当为系统控件时,调用的方法:

protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {

        return createView(name, "android.view.", attrs);

}

就是将系统的控件拼接上 android.view ,然后创建出来。

通过这一系列的过程,我们自己布局文件里的View就被加载到那个FrameLayout中了,至此我们的布局就显示到屏幕上了!

总结一下:

①Activity 和 AppCompatActivity 加载布局前都会创建一个 DecorView,并将系统布局加载到 DecorView 中,通过 DecorView 找到 id 为 android.id.content 的FrameLayout,最后通过 LayoutInflater 加载我们的 xml 布局。

②Activity 没有设置Factory ,AppCompatActivity 设置了 Factory。

③Activity 不会拦截 View,而 AppCompatActivity 会拦截 View,并将部分 View 转换成对应的 AppCompatView。

拦截小例子:

public class V8BaseActivity extends Activity {

    @Override

    protected void onCreate(@Nullable Bundle savedInstanceState) {

        inflateTest();

        super.onCreate(savedInstanceState);

    }

    private void inflateTest() {

        LayoutInflater layoutInflater = LayoutInflater.from(this);

        LayoutInflaterCompat.setFactory2( layoutInflater, new LayoutInflater.Factory2() {

            @Nullable

            @Override

            public View onCreateView(@Nullable View parent, @NonNull String name, @NonNull Context context, @NonNull AttributeSet attrs) {

                Log.d("View:", name);

                if (name.equals("Button")) {

                    TextView textView = new TextView(context);

                    textView.setText("test");

                    textView.setTextColor(Color.BLACK);

                    return textView;

                }

                return null;

            }

            @Nullable

            @Override

            public View onCreateView(@NonNull String name, @NonNull Context context, @NonNull AttributeSet attrs) {

                return onCreateView(null, name, context, attrs);

            }

        });

    }

}

通过拦截方法将 Button 换成了 TextView。

3.

接着下面会通过调用getCallBack得到一个CallBack对象cb,其实这个cb就是我们的Activity,接着会调用Activity的onContentChanged方法,这个方法是一个空实现。

猜你喜欢

转载自blog.csdn.net/zenmela2011/article/details/123932819