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懸浮球實現  
 


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章