Bootstrap

Android之LayoutInflater的解析过程和使用方法

获取LayoutInflater

获取LayoutInflater的方法也有三种

LayoutInflater inflater1 = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE)
LayoutInflater inflater2 = LayoutInflater.from(this);
LayoutInflater inflater3 = getLayoutInflater();

LayoutInflater的解析过程

LayoutInflater可以将xml的布局文件解析为view,它是一个抽象类,实现类是PhoneLayoutInflater。LayoutInflater会遍历View树,然后根据View的全路径名利用反射获取构造器,从而实现View实例。PhoneLayoutInflater实现了onCreateView()方法,当调用LayoutInflater的inflate()时,最终就会调用onCreateView(),从而创建出view

解析的过程分为三步

  • 获取XmlResourceParse
  • 解析View树
  • 解析View

1. 获取XmlResourceParser

当获取了LayoutInflater后,会通过inflater()方法进行调度,这个方法也是最常用的方法

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)

inflate方法中的三个参数分别为

  • int resource
    传入即将解析的xml文件,例如R.layout.activity_main
  • boolean attachToRoot
    用于表示是否要添加到父布局root中
  • ViewGroup root
    root参数比较关键,虽然有时我们会传入null值。root和attachToRoot参数结合使用
    (1)root != null时
    当attachToRoot == true新解析出来的View会被add到root中去,然后将root作为结果返回。
    当attachToRoot == false时,新解析的View会直接作为结果返回,而且root会为新解析的View生成LayoutParams并设置到该View中去。
    (2) root == null时 attachToRoot == false,新解析的View会直接作为结果返回。例如root为null时,新解析出来的View没有LayoutParams参数,这时候设置layout_width和layout_height是不生效的。

inflate方法的源码如下


public abstract class LayoutInflater {

    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
    // 1. 先获取整体Resources资源
        final Resources res = getContext().getResources();
        if (DEBUG) {
            Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
                    + Integer.toHexString(resource) + ")");
        }
        
        //2. 通过xml资源解析器XmlResourceParser,获取要解析的xml文件,得到parse
        final XmlResourceParser parser = res.getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);//解析View
        } finally {
            parser.close();
        }
    }
}

从上述代码可以看到,Resources中的getLayout()方法比较关键,该方法调用了Resources的loadXmlResourceParser() 方法来完成XmlResourceParser的加载,如下所示:

public class Resources {
    
     XmlResourceParser loadXmlResourceParser(@AnyRes int id, @NonNull String type)
             throws NotFoundException {
         final TypedValue value = obtainTempTypedValue();
         try {
             // 1. 声明ResourcesImpl的实现类impl实例
             final ResourcesImpl impl = mResourcesImpl;
             //2. 从impl获取xml布局资源,并保存在TypedValue中。
             impl.getValue(id, value, true);
             if (value.type == TypedValue.TYPE_STRING) {
                 //3. 加载对应的loadXmlResourceParser解析器。
                 return impl.loadXmlResourceParser(value.string.toString(), id,
                         value.assetCookie, type);
             }
             throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
                     + " type #0x" + Integer.toHexString(value.type) + " is not valid");
         } finally {
             releaseTempTypedValue(value);
         }
     }   
}

其中关键的方法是impl.loadXmlResourceParser(value.string.toString(), id,
value.assetCookie, type)
,用来加载对应的loadXmlResourceParser解析器,源码如下

public class ResourcesImpl {
   
 loadXmlResourceParser(@NonNull String file, @AnyRes int id, int assetCookie,
               @NonNull String type)
               throws NotFoundException {
           if (id != 0) {
               try {
                   synchronized (mCachedXmlBlocks) {
                       //... 从缓存中查找xml资源
                       //如果不在的话就新创建一个区域然后放进缓存
                       //
                       final XmlBlock block = mAssets.openXmlBlockAsset(assetCookie, file);
                       if (block != null) {
                           final int pos = (mLastCachedXmlBlockIndex + 1) % num;
                           mLastCachedXmlBlockIndex = pos;
                           final XmlBlock oldBlock = cachedXmlBlocks[pos];
                           if (oldBlock != null) {
                               oldBlock.close();
                           }
                           cachedXmlBlockCookies[pos] = assetCookie;
                           cachedXmlBlockFiles[pos] = file;
                           cachedXmlBlocks[pos] = block;
                           return block.newParser();
                       }
                   }
               } catch (Exception e) {
                   final NotFoundException rnf = new NotFoundException("File " + file
                           + " from xml type " + type + " resource ID #0x" + Integer.toHexString(id));
                   rnf.initCause(e);
                   throw rnf;
               }
           }
   
           throw new NotFoundException("File " + file + " from xml type " + type + " resource ID #0x"
                   + Integer.toHexString(id));
       } 
}

其中,四个关键的形参为
我们先来看看这个方法的四个形参:

  • String file:xml文件的路径
  • int id:xml文件的资源ID
  • int assetCookie:xml文件的资源缓存
  • String type:资源类型

通过openXmlBlockAsset()方法创建新的XmlBlock

public final class AssetManager implements AutoCloseable {
   /*package*/ final XmlBlock openXmlBlockAsset(int cookie, String fileName)
       throws IOException {
       synchronized (this) {
           //...
           long xmlBlock = openXmlAssetNative(cookie, fileName);
           if (xmlBlock != 0) {
               XmlBlock res = new XmlBlock(this, xmlBlock);
               incRefsLocked(res.hashCode());
               return res;
           }
       }
       //...
   } 
}

上述过程可表示如下
在这里插入图片描述

2. 解析View树

inflate中解析View树

  • 获取根元素root(形参)
  • 获取元素名name
  • 如果name是merge标签,则将所以子View都添加到root中rInflate(parser, root, inflaterContext, attrs, false);
  • 如果不是merge标签,调用**createViewFromTag()**方法解析成布局中的视图temp(View类型),再根据attachToRoot决定是否添加到root中
  • 调用rInflateChildren()方法解析当前View下面的所有子View
public abstract class LayoutInflater {
    
    public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
           synchronized (mConstructorArgs) {
               Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
   
               final Context inflaterContext = mContext;
               final AttributeSet attrs = Xml.asAttributeSet(parser);
               
               //Context对象
               Context lastContext = (Context) mConstructorArgs[0];
               mConstructorArgs[0] = inflaterContext;
               
               //存储根视图
               View result = root;
   
               try {
                   // 获取根元素
                   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("**************************");
                   }
   
                   //1. 解析merge标签,rInflate()方法会将merge下面的所有子View直接添加到根容器中,这里
                   //我们也理解了为什么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 {
                       //2. 不是merge标签那么直接调用createViewFromTag()方法解析成布局中的视图,这里的参数name就是要解析视图的类型,例如:ImageView
                       final View temp = createViewFromTag(root, name, inflaterContext, attrs);
   
                       ViewGroup.LayoutParams params = null;
   
                       if (root != null) {
                           if (DEBUG) {
                               System.out.println("Creating params from root: " +
                                       root);
                           }
                           //3. 调用generateLayoutParams()f方法生成布局参数,如果attachToRoot为false,即不添加到根容器里,为View设置布局参数
                           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);
                           }
                       }
   
                       if (DEBUG) {
                           System.out.println("-----> start inflating children");
                       }
   
                       //4. 调用rInflateChildren()方法解析当前View下面的所有子View
                       rInflateChildren(parser, temp, attrs, true);
   
                       if (DEBUG) {
                           System.out.println("-----> done inflating children");
                       }
   
                       //如果根容器不为空,且attachToRoot为true,则将解析出来的View添加到根容器中
                       if (root != null && attachToRoot) {
                           root.addView(temp, params);
                       }
   
                       //如果根布局为空或者attachToRoot为false,那么解析出来的额View就是返回结果
                       if (root == null || !attachToRoot) {
                           result = temp;
                       }
                   }
   
               } catch (XmlPullParserException e) {
                   final InflateException ie = new InflateException(e.getMessage(), e);
                   ie.setStackTrace(EMPTY_STACK_TRACE);
                   throw ie;
               } catch (Exception e) {
                   final InflateException ie = new InflateException(parser.getPositionDescription()
                           + ": " + e.getMessage(), e);
                   ie.setStackTrace(EMPTY_STACK_TRACE);
                   throw ie;
               } finally {
                   // Don't retain static reference on context.
                   mConstructorArgs[0] = lastContext;
                   mConstructorArgs[1] = null;
   
                   Trace.traceEnd(Trace.TRACE_TAG_VIEW);
               }
   
               return result;
           }
     }
}

其中重要的方法是rInflate,上述代码中有使用到 rInflate(parser, root, inflaterContext, attrs, false),rInflate和rInflateChildren的源码如下:

  • 获取树的深度depth,执行深度优先遍历
  • 逐个元素进行解析,先通过parser.getName()得到元素名,然后匹配name是否为request_focus、Tag、include、merge等标签,做相应处理,如果都不是开始根据元素名进行解析
  • 先根据元素名生成Viewfinal View view = createViewFromTag(parent, name, context, attrs);
  • 生成相应的LayoutParamsfinal ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
  • 递归生成子View:rInflateChildren(parser, view, attrs, true);
  • 将解析出来的View添加到它的父View中viewGroup.addView(view, params)
  • 最后回调根容器的parent.onFinishInflate();
public abstract class LayoutInflater {
    
    void rInflate(XmlPullParser parser, View parent, Context context,
            AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {

        //1. 获取树的深度,执行深度优先遍历
        final int depth = parser.getDepth();
        int type;

        //2. 逐个进行元素解析
        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)) {
                //3. 解析添加ad:focusable="true"的元素,并获取View焦点。
                parseRequestFocus(parser, parent);
            } else if (TAG_TAG.equals(name)) {
                //4. 解析View的tag。
                parseViewTag(parser, parent, attrs);
            } else if (TAG_INCLUDE.equals(name)) {
                //5. 解析include标签,注意include标签不能作为根元素。
                if (parser.getDepth() == 0) {
                    throw new InflateException("<include /> cannot be the root element");
                }
                parseInclude(parser, context, parent, attrs);
            } else if (TAG_MERGE.equals(name)) {
                //merge标签必须为根元素
                throw new InflateException("<merge /> must be the root element");
            } else {
                //6. 根据元素名进行解析,生成View。
                final View view = createViewFromTag(parent, name, context, attrs);
                final ViewGroup viewGroup = (ViewGroup) parent;
                final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
                //7. 递归调用解析该View里的所有子View,也是深度优先遍历,rInflateChildren内部调用的也是rInflate()方
                //法,只是传入了新的parent View
                rInflateChildren(parser, view, attrs, true);
                //8. 将解析出来的View添加到它的父View中。
                viewGroup.addView(view, params);
            }
        }

        if (finishInflate) {
            //9. 回调根容器的onFinishInflate()方法,这个方法我们应该很熟悉。
            parent.onFinishInflate();
        }
    }
}

rInflaterChildren方法

LayoutInflater.java
    //rInflateChildren内部调用的也是rInflate()方法,只是传入了新的parent View
    final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
            boolean finishInflate) throws XmlPullParserException, IOException {
        rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
    }

3.解析View

  1. 如果标签与主题相关,则需要将context与themeResId包裹成ContextThemeWrapper。
  2. 通过view = onCreateView(parent, name, attrs)对内置View, view = createView(name, null, attrs)解析自定义View
public abstract class LayoutInflater {

        View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
                boolean ignoreThemeAttr) {
            
            //1. 解析view标签。注意是小写view,这个不太常用,下面会说。
            if (name.equals("view")) {
                name = attrs.getAttributeValue(null, "class");
            }
    
            //2. 如果标签与主题相关,则需要将context与themeResId包裹成ContextThemeWrapper。
            if (!ignoreThemeAttr) {
                final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
                final int themeResId = ta.getResourceId(0, 0);
                if (themeResId != 0) {
                    context = new ContextThemeWrapper(context, themeResId);
                }
                ta.recycle();
            }
    
            //3. BlinkLayout是一种会闪烁的布局,被包裹的内容会一直闪烁,像QQ消息那样。
            if (name.equals(TAG_1995)) {
                // Let's party like it's 1995!
                return new BlinkLayout(context, attrs);
            }
    
            try {
                View view;
                
                //4. 用户可以设置LayoutInflater的Factory来进行View的解析,但是默认情况下
                //这些Factory都是为空的。
                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);
                }
    
                //5. 默认情况下没有Factory,而是通过onCreateView()方法对内置View进行解析,createView()
                //方法进行自定义View的解析。
                if (view == null) {
                    final Object lastContext = mConstructorArgs[0];
                    mConstructorArgs[0] = context;
                    try {
                        //这里有个小技巧,因为我们在使用自定义View的时候是需要在xml指定全路径的,例如:
                        //com.guoxiaoxing.CustomView,那么这里就有个.了,可以利用这一点判定是内置View
                        //还是自定义View,Google的工程师很机智的。😎
                        if (-1 == name.indexOf('.')) {
                            //内置View解析
                            view = onCreateView(parent, name, attrs);
                        } else {
                            //自定义View解析
                            view = createView(name, null, attrs);
                        }
                    } finally {
                        mConstructorArgs[0] = lastContext;
                    }
                }
    
                return view;
            } catch (InflateException e) {
                throw e;
    
            } catch (ClassNotFoundException e) {
                final InflateException ie = new InflateException(attrs.getPositionDescription()
                        + ": Error inflating class " + name, e);
                ie.setStackTrace(EMPTY_STACK_TRACE);
                throw ie;
    
            } catch (Exception e) {
                final InflateException ie = new InflateException(attrs.getPositionDescription()
                        + ": Error inflating class " + name, e);
                ie.setStackTrace(EMPTY_STACK_TRACE);
                throw ie;
            }
        }
}

onCreateView在PhoneLayoutInflater被实现,主要就是给内置View加下面三种前缀

public class PhoneLayoutInflater extends LayoutInflater {
    private static final String[] sClassPrefixList = {
        "android.widget.",
        "android.webkit.",
        "android.app."
    };

    @Override protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
        
        //循环遍历三种前缀,尝试创建View
        for (String prefix : sClassPrefixList) {
            try {
                View view = createView(name, prefix, attrs);
                if (view != null) {
                    return view;
                }
            } catch (ClassNotFoundException e) {
                // In this case we want to let the base class take a crack
                // at it.
            }
        }
        return super.onCreateView(name, attrs);
    }
   }

真正构建View还是createView方法完成的,根据完整的类的路径名利用反射机制构建View对象

public abstract class LayoutInflater {
    
    public final View createView(String name, String prefix, AttributeSet attrs)
                throws ClassNotFoundException, InflateException {
        
            //1. 从缓存中读取构造函数。
            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) {
                    // Class not found in the cache, see if it's real, and try to add it
                    
                    //2. 没有在缓存中查找到构造函数,则构造完整的路径名,并加装该类。
                    clazz = mContext.getClassLoader().loadClass(
                            prefix != null ? (prefix + name) : name).asSubclass(View.class);
                    
                    if (mFilter != null && clazz != null) {
                        boolean allowed = mFilter.onLoadClass(clazz);
                        if (!allowed) {
                            failNotAllowed(name, prefix, attrs);
                        }
                    }
                    //3. 从Class对象中获取构造函数,并在sConstructorMap做下缓存,方便下次使用。
                    constructor = clazz.getConstructor(mConstructorSignature);
                    constructor.setAccessible(true);
                    sConstructorMap.put(name, constructor);
                } else {
                    //4. 如果sConstructorMap中有当前View构造函数的缓存,则直接使用。
                    if (mFilter != null) {
                        // Have we seen this name before?
                        Boolean allowedState = mFilterMap.get(name);
                        if (allowedState == null) {
                            // New class -- remember whether it is allowed
                            clazz = mContext.getClassLoader().loadClass(
                                    prefix != null ? (prefix + name) : name).asSubclass(View.class);
                            
                            boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
                            mFilterMap.put(name, allowed);
                            if (!allowed) {
                                failNotAllowed(name, prefix, attrs);
                            }
                        } else if (allowedState.equals(Boolean.FALSE)) {
                            failNotAllowed(name, prefix, attrs);
                        }
                    }
                }
    
                Object[] args = mConstructorArgs;
                args[1] = attrs;
    
                //5. 利用构造函数,构建View对象。
                final View view = constructor.newInstance(args);
                if (view instanceof ViewStub) {
                    // Use the same context when inflating ViewStub later.
                    final ViewStub viewStub = (ViewStub) view;
                    viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
                }
                return view;
    
            } catch (NoSuchMethodException e) {
                final InflateException ie = new InflateException(attrs.getPositionDescription()
                        + ": Error inflating class " + (prefix != null ? (prefix + name) : name), e);
                ie.setStackTrace(EMPTY_STACK_TRACE);
                throw ie;
    
            } catch (ClassCastException e) {
                // If loaded class is not a View subclass
                final InflateException ie = new InflateException(attrs.getPositionDescription()
                        + ": Class is not a View " + (prefix != null ? (prefix + name) : name), e);
                ie.setStackTrace(EMPTY_STACK_TRACE);
                throw ie;
            } catch (ClassNotFoundException e) {
                // If loadClass fails, we should propagate the exception.
                throw e;
            } catch (Exception e) {
                final InflateException ie = new InflateException(
                        attrs.getPositionDescription() + ": Error inflating class "
                                + (clazz == null ? "<unknown>" : clazz.getName()), e);
                ie.setStackTrace(EMPTY_STACK_TRACE);
                throw ie;
            } finally {
                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            }
        }   
}

LayoutParams

LayoutParams 的作用是用于子View的布局,是ViewGroup的一个内部抽象类,封装了Layout的位置、高、宽等信息,常用子类的简单使用:LinearLayout.LayoutParams、RelativeLayout.Params以及WindowManager.Params。

        TextView textView = new TextView(context);
        ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 200);
        layoutParams.width=100;
        layoutParams.height=200;
        textView.setLayoutParams(layoutParams);

如果textview外围包围着LinearLayout

        LinearLayout.LayoutParams layoutParams1 = (LinearLayout.LayoutParams) textView.getLayoutParams();
        
        //设置权重比例
        layoutParams1.weight=1;
        
        //设置上部间距
        layoutParams1.topMargin=5;
        
        //设置右边间距
        layoutParams1.setMarginEnd(20);
        
        //设置四周间距
        layoutParams1.setMargins(3,3,3,3);

RelativeLayout

        RelativeLayout.LayoutParams layoutParams1 = (RelativeLayout.LayoutParams) textView.getLayoutParams();

        //设置位置居中
        layoutParams1.addRule(RelativeLayout.CENTER_IN_PARENT);
        
        //设置在某一个id所对应的view的右边
        layoutParams1.addRule(RelativeLayout.RIGHT_OF, R.id.all);
        
        layoutParams1.topMargin=5;
        layoutParams1.setMarginEnd(20);
        layoutParams1.setMargins(3,3,3,3);
        
        layoutParams1.removeRule(RelativeLayout.CENTER_IN_PARENT);

FrameLayout

        FrameLayout.LayoutParams layoutParams1 = (FrameLayout.LayoutParams) textView.getLayoutParams();
        
        //设置位置居中
        layoutParams1.gravity = Gravity.CENTER;

        layoutParams1.topMargin = 5;
        layoutParams1.setMarginEnd(20);
        layoutParams1.setMargins(3, 3, 3, 3);

例子1:动态添加Button

布局文件

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:id="@+id/RelativeLayout1"  
    android:layout_width="match_parent"  
    android:layout_height="match_parent" >  
  
    <TextView   
        android:id="@+id/txtTitle"  
        android:layout_width="match_parent"  
        android:layout_height="wrap_content"  
        android:text="我是xml文件加载的布局"/>  
      
</RelativeLayout>  

方法一:

public class MainActivity extends Activity {  
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        Button btnOne = new Button(this);  
        btnOne.setText("我是动态添加的按钮");  
    
        RelativeLayout.LayoutParams lp2 = new RelativeLayout.LayoutParams(    
                LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);    
        lp2.addRule(RelativeLayout.CENTER_IN_PARENT);  
        
		//有setContentView
		setContentView(R.layout.activity_main);  
		RelativeLayout rly = (RelativeLayout) findViewById(R.id.RelativeLayout1); 

        //添加View
        rly.addView(btnOne,lp2);  
  
    }  
} 

方法二:

public class MainActivity extends Activity {  
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        Button btnOne = new Button(this);  
        btnOne.setText("我是动态添加的按钮");  
        //
        RelativeLayout.LayoutParams lp2 = new RelativeLayout.LayoutParams(    
                LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);    
        lp2.addRule(RelativeLayout.CENTER_IN_PARENT);  
		
        //没有setContentView
        LayoutInflater inflater = LayoutInflater.from(this);  
        RelativeLayout rly = (RelativeLayout) inflater
        .inflate(R.layout.activity_main, null)
        .findViewById(R.id.RelativeLayout1);  
        
        //添加View
        rly.addView(btnOne,lp2);  
        setContentView(rly);  
    }  
} 

例子2 添加各种Button

import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Bundle;
import android.view.Gravity;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;

import com.example.androidstudy.R;

public class AddViewActivity extends AppCompatActivity {
    LinearLayout llAddView;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_addview);
        llAddView = findViewById(R.id.ll_addview);
        addSimpleButton();
        addWrapButton();
        addWrapReButton();
        addFixButton();
        addLinearCenterButton();
        addRelativeCenterButton();
        modifyButtonText();

    }
    @SuppressLint("SetTextI18n")
    private void addSimpleButton(){
        Button btnSimple = new Button(this);
        btnSimple.setText("最简单的直接添加");
        llAddView.addView(btnSimple);
    }

    @SuppressLint("SetTextI18n")
    private void addWrapButton(){
        Button btnWrap = new Button(this);
        btnWrap.setText("WRAP_CONTENT");
        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
                LinearLayout.LayoutParams.WRAP_CONTENT,
                LinearLayout.LayoutParams.WRAP_CONTENT
        );
        llAddView.addView(btnWrap,params);

    }
    @SuppressLint("SetTextI18n")
    private void addWrapReButton(){
        Button btnWrap = new Button(this);
        btnWrap.setText("RELATIVE_WRAP_CONTENT");
        RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
                RelativeLayout.LayoutParams.WRAP_CONTENT,
                RelativeLayout.LayoutParams.WRAP_CONTENT
        );
        llAddView.addView(btnWrap,params);

    }

    @SuppressLint("SetTextI18n")
    private void addFixButton(){
        /**
         * 注意dp转px
         */
        Button btnFix = new Button(this);
        btnFix.setText("100dp,100dp");
        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
                new ViewGroup.LayoutParams(
                        (int) dipToPx(this, 100f),
                        (int) dipToPx(this, 100f))
        );
        llAddView.addView(btnFix,params);
    }

    @SuppressLint("SetTextI18n")
    private void addLinearCenterButton(){
        Button btnFix = new Button(this);
        btnFix.setText("LinearCenter");
        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
                new ViewGroup.LayoutParams((int) dipToPx(this, 300f),500)
        );
        params.gravity = Gravity.CENTER;
        llAddView.addView(btnFix,params);
    }

    @SuppressLint("SetTextI18n")
    private void addRelativeCenterButton(){
        Button btnOne = new Button(this);
        btnOne.setText("RelativeCenter");
        RelativeLayout.LayoutParams lp2 = new RelativeLayout.LayoutParams(
                RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
        lp2.addRule(RelativeLayout.CENTER_IN_PARENT);
        RelativeLayout rly = (RelativeLayout) findViewById(R.id.rr_addview);
        rly.addView(btnOne,lp2);
    }

    private void modifyButtonText(){
        Button btnModifyText = findViewById(R.id.btn_native);
        btnModifyText.setText("Modify");
    }

    private float dipToPx(Context context, float dip) {
        return dip * getDeviceDensity(context) + 0.5f;
    }

    //获取屏幕密度
    private float getDeviceDensity(Context context) {
        return context.getResources().getDisplayMetrics().density;
    }

    //px转dp
    private float pxToDip(Context context, float px) {
        return px / getDeviceDensity(context) + 0.5f;
    }

}

参考博客地址

;