Android 动态添加布局 LayoutInflater原理分析 LayoutParams


LayoutParams详解

 

非常好的参考:

https://www.jianshu.com/p/36b200a0bff4

 

动态布局:

    如果想要代码动态写出上面的布局,就需要使用 LayoutParams 这个关键类了

   LayoutParams 的作用是:子控件告诉父控件,自己要如何布局

 

在添加到父布局时,设置 LayoutParams,通知父布局如何摆放自己

 

<span style="color:#cccccc"><code class="language-java">ll.<span style="color:#f08d49">addView</span>(vt, layoutParams);<span style="color:#999999">// 在添加到父布局的时候</span></code></span>

 

 

 

 

利用setLayoutParams方法对控件的layout进行布局更新

<span style="color:#000000"><span style="color:#cccccc"><code class="language-css">textView.<span style="color:#f08d49">setLayoutParams</span>(textParams);</code></span></span>
<span style="color:#000000"><span style="color:#cccccc"><code class="language-csharp">     <span style="color:#f8c555">RelativeLayout<span style="color:#cccccc">.</span>LayoutParams</span> <span style="color:#cc99cd">params</span> <span style="color:#67cdcc">=</span> <span style="color:#cc99cd">new</span> <span style="color:#f8c555">RelativeLayout<span style="color:#cccccc">.</span>LayoutParams</span>(mVgLp);
                <span style="color:#cc99cd">params</span>.<span style="color:#f08d49">addRule</span>(RelativeLayout.ALIGN_PARENT_BOTTOM);
                <span style="color:#cc99cd">params</span>.<span style="color:#f08d49">addRule</span>(RelativeLayout.ALIGN_PARENT_RIGHT);
                mIv.<span style="color:#f08d49">setLayoutParams</span>(<span style="color:#cc99cd">params</span>);</code></span></span>
 
我们发现,在对 LinearLayout 和 TextView 的 都不设置 LayoutParams 的情况下,
LinearLayout 使用 MATCH_PARENT,而 TextView 使用 WRAP_CONTENT,至于为什么,我们要分析一下源码。



ViewGroup里面的addview();

所以测量由父类控制饿了

public void addView(View child, int index) {
    if (child == null) {
        throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
    }
    LayoutParams params = child.getLayoutParams();
    if (params == null) {
        params = generateDefaultLayoutParams();
        if (params == null) {
            throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null");
        }
    }
    addView(child, index, params);
}

 

 

动态设置view的padding

 

private void changesize(){
    if(weeklyRewardObtainTv.getText()!=null&&weeklyRewardObtainTv.getText().toString()!=null&&weeklyRewardObtainTv.getText().toString().length()>=10){
        int left=weeklyRewardObtainTv.getPaddingLeft();
        int top=weeklyRewardObtainTv.getPaddingTop();
        int bottom=weeklyRewardObtainTv.getPaddingBottom();
        int right=weeklyRewardObtainTv.getPaddingRight();
        weeklyRewardObtainTv.setPadding(DensityUtil.dip2px(ActivityWeeklyReward.this,53),top,right,bottom);
    }
}

 

 

 

     虽然setContentView()方法大家都会用,但实际上Android界面显示的原理要比我们所看到的东西复杂得多。任何一个Activity中显示的界面其实主要都由两部分组成,标题栏和内容布局。标题栏就是在很多界面顶部显示的那部分内容,比如刚刚我们的那个例子当中就有标题栏,可以在代码中控制让它是否显示。而内容布局就是一个FrameLayout,这个布局的id叫作content,我们调用setContentView()方法时所传入的布局其实就是放到这个FrameLayout中的,这也是为什么这个方法名叫作setContentView(),而不是叫setView()。

最后再附上一张Activity窗口的组成图吧,以便于大家更加直观地理解:

 

LayoutInflater的基本用法有2种:

1:LayoutInflater layoutInflater = LayoutInflater.from(context);

2 :LayoutInflater layoutInflater = (LayoutInflater) context

.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

第一种方法是第二种的封装简写。

得到了LayoutInflater的实例之后就可以调用它的inflate()方法来加载布局了,:layoutInflater.inflate(resourceId, root);

那LayoutInflater 是如何工作的呢 ?

LayoutInflater其实就是使用Android提供的pull解析方式来解析布局文件的。

 
LayoutInflater实战用法举例:
把一个自定义动态添加到一个布局里面
1.自定义了一个view
public class LayoutRunHealthCheck extends FrameLayout implements View.OnClickListener {

    
    private LinearLayout textViewClose;
    private HealthAnimationCircleProgressBar healthAnimationCircleProgressBar;
    private TextView tv_body_socre, tv_body_des, tv_body_detail, tv_start;
    private TextView tv_face_score;

    private SimpleDraweeView simpleDraweeView;

    private RelativeLayout layoutStart;

    private void buildView() {
        View view = LayoutInflater.from(context).inflate(R.layout.layout_run_health, null);
        textViewClose = view.findViewById(R.id.tv_close);
        tv_body_socre = view.findViewById(R.id.tv_body_socre);
        tv_body_des = view.findViewById(R.id.tv_body_des);
        tv_body_detail = view.findViewById(R.id.tv_body_detail);
        tv_start = view.findViewById(R.id.tv_start);

        layoutStart = view.findViewById(R.id.layout_start);

        simpleDraweeView = view.findViewById(R.id.body_feature);

        tv_face_score = view.findViewById(R.id.tv_face_score);

        healthAnimationCircleProgressBar = view.findViewById(R.id.cpb_face_score);

        textViewClose.setOnClickListener(this);
        layoutStart.setOnClickListener(this);

        view.setPadding(0, 0, 0, DensityUtil.dip2px(context, 0));
        this.addView(view);

        iniView();
        setData();
    }
2.布局里面设置
<LinearLayout
    android:id="@+id/frame_layout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"></LinearLayout>
3.在代码里面动态添加:
frameLayout = bottomView.findViewById(R.id.frame_layout);

addHealthView();
if (layoutRunHealthCheck == null && activity != null) {
    layoutRunHealthCheck = new LayoutRunHealthCheck(activity, type, healthListener);
    frameLayout.addView(layoutRunHealthCheck);
 
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 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("**************************");
            }

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

                ViewGroup.LayoutParams params = null;

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

                if (DEBUG) {
                    System.out.println("-----> start inflating children");
                }

                // Inflate all children under temp against its context.
                rInflateChildren(parser, temp, attrs, true);

                if (DEBUG) {
                    System.out.println("-----> done inflating children");
                }

                // We are supposed to attach all the views we found (int temp)
                // to root. Do that now.
                if (root != null && attachToRoot) {
                    root.addView(temp, params);
                }

                // Decide whether to return the root that was passed in or the
                // top view found in xml.
                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;
    }
}
发现merge,include标签,tag标签也在这个类里面
public abstract class LayoutInflater {

    private static final String TAG = LayoutInflater.class.getSimpleName();
    private static final boolean DEBUG = false;

    /** Empty stack trace used to avoid log spam in re-throw exceptions. */
    private static final StackTraceElement[] EMPTY_STACK_TRACE = new StackTraceElement[0];

    /**
     * This field should be made private, so it is hidden from the SDK.
     * {@hide}
     */
    protected final Context mContext;

    // these are optional, set by the caller
    private boolean mFactorySet;
    private Factory mFactory;
    private Factory2 mFactory2;
    private Factory2 mPrivateFactory;
    private Filter mFilter;

    final Object[] mConstructorArgs = new Object[2];

    static final Class<?>[] mConstructorSignature = new Class[] {
            Context.class, AttributeSet.class};

    private static final HashMap<String, Constructor<? extends View>> sConstructorMap =
            new HashMap<String, Constructor<? extends View>>();

    private HashMap<String, Boolean> mFilterMap;

    private TypedValue mTempValue;

    private static final String TAG_MERGE = "merge";
    private static final String TAG_INCLUDE = "include";
    private static final String TAG_1995 = "blink";
    private static final String TAG_REQUEST_FOCUS = "requestFocus";
    private static final String TAG_TAG = "tag";
1. 如果root为null,attachToRoot将失去作用,设置任何值都没有意义。
2. 如果root不为null,attachToRoot设为true,则会给加载的布局文件的指定一个父布局,即root。
3. 如果root不为null,attachToRoot设为false,则会将布局文件最外层的所有layout属性进行设置,当该view被添加到父view当中时,这些layout属性会自动生效。
4. 在不设置attachToRoot参数的情况下,如果root不为null,attachToRoot参数默认为true。
 
private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
    return createViewFromTag(parent, name, context, attrs, false);
}
问题:控件的大小怎么调试都没有用,外面少了一层父布局。首先View必须存在于一个布局中???、
 
 
 
1.progrossbar的高度问题
2.linearylayout的高度问题
 
 
3.场景分析:通过一个viewPager自定义banner?     发现 viewPager wrap的高度不生效
 
原理是:viewPager重写了onMesure。固定了自己的高度。然后测量了子view
 
viewpager本来是用来切换整个屏幕的。不是用来做banner的。
所以高度要自己测量高度。
 
测量原理:树的深度变量。
 
 
解决办法:重写onMesure方法
先度量子view然后再度量自己。
 
这样是不正确的,要用到LayoutParams才行
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

    int height = 0;
    for(int i = 0; i < getChildCount(); i++) {
        View child = getChildAt(i);
        child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
        int h = child.getMeasuredHeight();
        if(h > height) height = h;
    }

    heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);

    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
 
一个viewGroup。父类给他一个参考值,同时要先度量子view。才能确定
有点像view的时间分发。
通过源码发现:测量自己之前先测量自己都子view
 
ViewPager源码:
setMeasuredDimension:测量自己
LayoutParams:子空间的参数测量
MeasureSpec.makeMeasureSpec(widthSize, widthMode);  测量的计算模式
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    // For simple implementation, our internal size is always 0.
    // We depend on the container to specify the layout size of
    // our view.  We can't really know what it is since we will be
    // adding and removing different arbitrary views and do not
    // want the layout to change as this happens.
    setMeasuredDimension(getDefaultSize(0, widthMeasureSpec),
            getDefaultSize(0, heightMeasureSpec));

    final int measuredWidth = getMeasuredWidth();
    final int maxGutterSize = measuredWidth / 10;
    mGutterSize = Math.min(maxGutterSize, mDefaultGutterSize);

    // Children are just made to fill our space.
    int childWidthSize = measuredWidth - getPaddingLeft() - getPaddingRight();
    int childHeightSize = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();

    /*
     * Make sure all children have been properly measured. Decor views first.
     * Right now we cheat and make this less complicated by assuming decor
     * views won't intersect. We will pin to edges based on gravity.
     */
    int size = getChildCount();
    for (int i = 0; i < size; ++i) {
        final View child = getChildAt(i);
        if (child.getVisibility() != GONE) {
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            if (lp != null && lp.isDecor) {
                final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
                final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
                int widthMode = MeasureSpec.AT_MOST;
                int heightMode = MeasureSpec.AT_MOST;
                boolean consumeVertical = vgrav == Gravity.TOP || vgrav == Gravity.BOTTOM;
                boolean consumeHorizontal = hgrav == Gravity.LEFT || hgrav == Gravity.RIGHT;

                if (consumeVertical) {
                    widthMode = MeasureSpec.EXACTLY;
                } else if (consumeHorizontal) {
                    heightMode = MeasureSpec.EXACTLY;
                }

                int widthSize = childWidthSize;
                int heightSize = childHeightSize;
                if (lp.width != LayoutParams.WRAP_CONTENT) {
                    widthMode = MeasureSpec.EXACTLY;
                    if (lp.width != LayoutParams.MATCH_PARENT) {
                        widthSize = lp.width;
                    }
                }
                if (lp.height != LayoutParams.WRAP_CONTENT) {
                    heightMode = MeasureSpec.EXACTLY;
                    if (lp.height != LayoutParams.MATCH_PARENT) {
                        heightSize = lp.height;
                    }
                }
                final int widthSpec = MeasureSpec.makeMeasureSpec(widthSize, widthMode);
                final int heightSpec = MeasureSpec.makeMeasureSpec(heightSize, heightMode);
                child.measure(widthSpec, heightSpec);
 MeasureSpec.makeMeasureSpec(widthSize, widthMode);
 
makeMeasuerMespect:度量计算:把参数转成具体指
LayoutParams      也经常用到。
              
:度量计算:把参数转成具体指
LayoutParams ====== ====转为100dp
 
 
 
fragment 里面的getactivity==null的原因。
比如:推送的时候,点击通知栏。因为他们不在同一个进程
 
LayoutInflate使用:setcontentview。
第二个:adapter里面使用
 
LayoutInflate的调用原理:
里面如何解析including和其他3种方式
 
LayoutInflate相关的地方
有的时候高度无效,和根布局有关系。多添加一层view,设置宽高就可以额。
 
 
onmesure什么时候触发:
1.父类调用一次
2.onlayout的时候又去调用次
 
4.类似的情况,recyleView里面的item高度问题。同样可以
 
 
看源码工具:source insight
 
 
参考博客:
 
360悬浮球实现  
 


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值