關於流逝佈局作業7.0顯示問題詳解

weixin_33860722發表於2018-06-15

首先我們來看造成這個問題的原因:
那天在完成這組程式碼的時候,為了給大家體現可操作的自由性,我並沒有去給大家完善業務邏輯,所以造成了這個坑存在的問題,那麼我門首先來看造成這個問題的原因是什麼
首先看程式碼,我門採取的方式,是將測量後的程式碼用集合儲存起來再給layout進行佈局,
那麼參考原始碼


12625345-a7241f5e636347d1.png
image.png

這裡呼叫兩次,而在低版本當中,,而用集合收集儲存兩次之後總行書數為6行,那麼這個問題深入分析後,做了一次測試


12625345-5d2dd01e0ba18dc7.jpg
AFC080BBA1E9D5E4B00F7F7137982B22.jpg

12625345-165d62112ceffa24.jpg
3CB0B65114DA89305A016BDB1098A2B1.jpg

那麼從這兩張圖可以分析出,在7.0之後和7.0之前系統對於原始碼由一定程度的改變,


12625345-8589e3f9a5034022.png
21版本

12625345-c13efb3304ccf7a0.png
27版本
12625345-9f970c56a8901d06.png
image.png
12625345-b43d373c301886fd.png
image.png

可以很明顯看到,27版本依賴於一個mReportNextDraw變數而這個變數在下面performDraw進行了一次變幻,這裡面執行流程比較複雜,有需要的同學自己深入,我們在這裡明白一點就是,layout呼叫被被了一層條件,第一次不執行, 第二次執行了reportNextDraw()過後才執行

從執行機制來講,在7.0之前圖1所呼叫的scheduleTraversals她會依次呼叫onMeasure-->onLayout,但是在7.0之後我門會發現執行機制發生了改變,如果是第一次的onMeasure那麼他不會去呼叫onLayout,所以這裡變成了兩次onMeasure一次onLayout 那麼這個時候在我們的List當中就會存在6行,當我講測量固定了一個大的值之後我們看到了效果


12625345-75edc823b9678c1d.jpg
1AEA585E7E20C2A67593167BC32B7314.jpg

這裡可以很明顯看到其實子控制元件被我們定位到了3行開外超過了父控制元件的顯示高度

所以解決有兩種方案,第一種在onMeasure裡面新增clear()


12625345-fcd594f37214ffa6.png
image.png

第二種
我直接修改了原始碼不採用第三方的容器這裡我修改了佈局程式碼

public class WaterfallFLowLayout extends ViewGroup {

@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
    return new MarginLayoutParams(getContext(), attires
}


//思路,通過前面兩節課我門知道了其實,繪製流程最終會呼叫到我門的OnMesure  和   onLayout,
//而不通的佈局,他們自己的實現不一樣,所以才有了我門使用的這些基本佈局元件
//那麼我們現在自己來開發一個瀑布式的流式佈局

public WaterfallFLowLayout(Context context) {
    super(context);
}

public WaterfallFLowLayout(Context context, AttributeSet attrs) {
    super(context, attires
}

public WaterfallFLowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
}




@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {


    Log.i("barry", "onMeasure.......");

    //此處我門可以知道這裡是我們的爸爸的SIZE
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);

    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int heightMode = MeasureSpec.getMode(heightMeasureSpec);


    //當前空間寬高
    int measureWidth = 0;
    int measureHeight = 0;

    //當前行寬,行高,因為存在多行,下一行資料要放到下方,行高需要儲存
    int iCurLineW = 0;
    int iCurLineH = 0;


    //1.確認自己當前空間的寬高,這裡因為會有兩次OnMeasure,進行二級測量優化,所以採用IF_ELSE結構
    //二級優化原理在原始碼具體Draw時,第一次不會直接進行performDraw的呼叫反而是在下面重新進行了一次scheduleTraversals
    //在ViewRootImpl原始碼2349-2372之中我門會看到  scheduleTraversals在我們的2363
    if (widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY) {
        measureWidth = widthSize;
        measureHeight = heightSize;
    } else {
        //當前VIEW寬高
        int iChildWidth = 0;
        int iChildHeight = 0;
        //獲取子VIEW數量用於迭代
        int childCount = getChildCount();
        Log.i("barry", "childCount:" + childCount);
        //單行資訊容器
        for (int i = 0; i < childCount; i++) {
            View childAt = getChildAt(i);
            //1.測量自己
            measureChild(childAt, widthMeasureSpec, heightMeasureSpec);
            //2.獲取XML資源
            MarginLayoutParams layoutParams = (MarginLayoutParams) childAt.getLayoutParams();


            //3.獲得實際寬度和高度(MARGIN+WIDTH)
            iChildWidth = childAt.getMeasuredWidth() + layoutParams.leftMargin + layoutParams.rightMargin;
            iChildHeight = childAt.getMeasuredHeight() + layoutParams.topMargin + layoutParams.bottomMargin;

            Log.i("barry", "------widthSize-----:" + widthSize);

            //4.是否需要換行
            if (iCurLineW + iChildWidth > widthSize) {
                //4.1.紀錄當前行資訊
                //4.1.1.紀錄當前行最大寬度,高度累加
                measureWidth = Math.max(measureWidth, iCurLineH);
                measureHeight += iCurLineH;
                Log.i("barry", "------height---換行--:" + measureHeight);
                //4.1.2.儲存這一行資料,及行高

                //4.2.紀錄新的行資訊
                //4.2.1.賦予新行新的寬高
                iCurLineW = iChildWidth;
                iCurLineH = iChildHeight;



            } else {
                //5.1.不換行情況
                //5.1.1.記錄某行內的訊息行內寬度的疊加、高度比較
                iCurLineW += iChildWidth;
                iCurLineH = Math.max(iCurLineH, iChildHeight);



            }

            //6.如果正好是最後一行需要換行
            if (i == childCount - 1) {
                //6.1.記錄當前行的最大寬度,高度累加
                measureWidth = Math.max(measureWidth, iCurLineW);
                measureHeight += iCurLineH;
            }
        }
    }


    Log.i("barry", "width:" + measureWidth);
    Log.i("barry", "height:" + measureHeight);


    //確認儲存自己的寬高
    setMeasuredDimension(widthSize, measureHeight);


}

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    Log.i("barry", "onLayout.....");

    int startX = getPaddingLeft();
    int startY = getPaddingTop();
    int measuredWidth = getMeasuredWidth();
    int measuredHeight = getMeasuredHeight();

    //每個子控制元件佔據的寬度
    int childViewUseWidth = 0;
    int childViewUseLineHight = 0;
    int childCount = getChildCount();
    for (int i = 0; i < childCount; i++) {


        View childView = getChildAt(i);

        if (childView.getVisibility() == GONE) {

            continue;
        }
        //獲取每個子控制元件的layoutParams
        MarginLayoutParams layoutParams = (MarginLayoutParams) childView.getLayoutParams();

        int childViewMeasuredWidth = childView.getMeasuredWidth();
        int childViewMeasuredHeight = childView.getMeasuredHeight();

        //startX 變化為0 就換行, 每個子控制元件在擺放之前,判斷剩餘控制元件是否足夠,用startX + childViewMeasuredWidth是否大於整個控制元件的寬度
        //判斷的時候考慮PaddingRight
        //考慮了子控制元件自己的margin值,每個子控制元件佔據的寬度:childViewMeasuredWidth + leftMargin + rightMargin
        childViewUseWidth = childViewMeasuredWidth + layoutParams.leftMargin + layoutParams.rightMargin;
        if (startX + /*childViewMeasuredWidth*/childViewUseWidth > measuredWidth - getPaddingRight()) {

            startX = getPaddingLeft();

            //換行的時候,上一行使用的高度以一行的最高的為準
            startY += /*childViewMeasuredHeight*/childViewUseLineHight; //y左邊累加,因為現在所有的子控制元件高度都一樣
        }


        //擺放子控制元件
        int leftChildView = startX + layoutParams.leftMargin;//考慮自己的margin
        int topChildView = startY + layoutParams.topMargin;
        int rightChildView = leftChildView + childViewMeasuredWidth;
        int bottomChildView = topChildView + childViewMeasuredHeight;
        //子控制元件佈局
        childView.layout(leftChildView, topChildView, rightChildView, bottomChildView);

        //子控制元件擺放之後累加startX的值, 考慮每個孩子佔據的寬度要加上marginLeft , marginRingt
        startX += /*childViewMeasuredWidth*/childViewUseWidth;

        //計算每一行使用的高度
        childViewUseLineHight = Math.max(childViewUseLineHight, childViewMeasuredHeight + layoutParams.topMargin + layoutParams.bottomMargin);
    }
}

相關文章