Android流式布局實現

jopen 9年前發布 | 14K 次閱讀 Android Android開發 移動開發

摘要

本文原創,轉載請注明地址: http://kymjs.com/code/2015/06/06/01
本博客客戶端下載【 請點擊
新項目用到了一種全新布局————Android標簽流式布局的功能,正好一直說給大家講自定義控件的實現,今天就為大家講一種android流式布局的實現。

在日常的app使用中,我們會在android 的app中看見熱門標簽等自動換行的流式布局,今天,我們就來看看如何自定義一個類似熱門標簽那樣的流式布局吧(源碼下載在下面最后給出)
Android流式布局實現
這個控件并不是我實現的,代碼是從網上搜流式布局找到的。我只是為大家講解一下實現過程以及原理。

先看代碼

public class FlowLayout extends ViewGroup {
    private float mVerticalSpacing; //每個item縱向間距
    private float mHorizontalSpacing; //每個item橫向間距

    public FlowLayout(Context context) {
        super(context);
    }
    public FlowLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    public void setHorizontalSpacing(float pixelSize) {
        mHorizontalSpacing = pixelSize;
    }
    public void setVerticalSpacing(float pixelSize) {
        mVerticalSpacing = pixelSize;
    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int selfWidth = resolveSize(0, widthMeasureSpec);

        int paddingLeft = getPaddingLeft();
        int paddingTop = getPaddingTop();
        int paddingRight = getPaddingRight();
        int paddingBottom = getPaddingBottom();

        int childLeft = paddingLeft;
        int childTop = paddingTop;
        int lineHeight = 0;

        //通過計算每一個子控件的高度,得到自己的高度
        for (int i = 0, childCount = getChildCount(); i < childCount; ++i) {
            View childView = getChildAt(i);
            LayoutParams childLayoutParams = childView.getLayoutParams();
            childView.measure(
                    getChildMeasureSpec(widthMeasureSpec, paddingLeft + paddingRight,
                            childLayoutParams.width),
                    getChildMeasureSpec(heightMeasureSpec, paddingTop + paddingBottom,
                            childLayoutParams.height));
            int childWidth = childView.getMeasuredWidth();
            int childHeight = childView.getMeasuredHeight();

            lineHeight = Math.max(childHeight, lineHeight);

            if (childLeft + childWidth + paddingRight > selfWidth) {
                childLeft = paddingLeft;
                childTop += mVerticalSpacing + lineHeight;
                lineHeight = childHeight;
            } else {
                childLeft += childWidth + mHorizontalSpacing;
            }
        }

        int wantedHeight = childTop + lineHeight + paddingBottom;
        setMeasuredDimension(selfWidth, resolveSize(wantedHeight, heightMeasureSpec));
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int myWidth = r - l;

        int paddingLeft = getPaddingLeft();
        int paddingTop = getPaddingTop();
        int paddingRight = getPaddingRight();

        int childLeft = paddingLeft;
        int childTop = paddingTop;

        int lineHeight = 0;

        //根據子控件的寬高,計算子控件應該出現的位置。
        for (int i = 0, childCount = getChildCount(); i < childCount; ++i) {
            View childView = getChildAt(i);

            if (childView.getVisibility() == View.GONE) {
                continue;
            }

            int childWidth = childView.getMeasuredWidth();
            int childHeight = childView.getMeasuredHeight();

            lineHeight = Math.max(childHeight, lineHeight);

            if (childLeft + childWidth + paddingRight > myWidth) {
                childLeft = paddingLeft;
                childTop += mVerticalSpacing + lineHeight;
                lineHeight = childHeight;
            }
            childView.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
            childLeft += childWidth + mHorizontalSpacing;
        }
    }
}

從控件創建過程說起

  1. 當這個流式布局在被加載如內存并顯示在屏幕上這一過程中,首先會調用view.measure(w,h)這個方法,表示測量view的寬度與高度,其中參數w與h分別表示這個控件的父控件的寬高。
  2. 在view.measure()方法的調用過程中又會調用view本身的一個回調方法,onMeasure(),這個是view自身的一個回 調方法,用于讓開發者在自定義View的時候重新計算自身的大小。一般會在這個方法中循環遍歷,計算出這個控件的全部子孫控件的寬高。
  3. 在View的寬高計算完成以后,考慮將這個控件顯示到屏幕的指定位置上,此時view的onLayout()方法會被調用。 一般同時會在這個方法中計算出全部子孫控件在這個控件中的位置。
    可能基本流程有些枯燥,接下來結合代碼看看。

流布局的實現

看到onMeasure()方法中的這段: //通過計算每一個子控件的高度,得到自己的高度

for (int i = 0, childCount = getChildCount(); i < childCount; ++i) {
        View childView = getChildAt(i);
        LayoutParams childLayoutParams = childView.getLayoutParams();
        childView.measure(
                getChildMeasureSpec(widthMeasureSpec, paddingLeft + paddingRight,
                        childLayoutParams.width),
                getChildMeasureSpec(heightMeasureSpec, paddingTop + paddingBottom,
                        childLayoutParams.height));
        int childWidth = childView.getMeasuredWidth();
        int childHeight = childView.getMeasuredHeight();

        lineHeight = Math.max(childHeight, lineHeight);

        if (childLeft + childWidth + paddingRight > selfWidth) {
            childLeft = paddingLeft;
            childTop += mVerticalSpacing + lineHeight;
            lineHeight = childHeight;
        } else {
            childLeft += childWidth + mHorizontalSpacing;
        }
    }

首先通過循環,遍歷這個控件的所有子控件,同時調用子控件的measure()方法,這時measure方法的兩個參數是控件能給這個子控件的最大 寬高(我們都知道的,子控件再大,顯示的大小也不能比父控件還大)。這里getChildMeasureSpec()方法的作用是用來計算一個合適子視圖 的尺寸大小(寬度或者高度),結合我們從子視圖的LayoutParams所給出的MeasureSpec信息來獲取最合適的結果。比如,如果這個 View知道自己的大小尺寸(因為它本身的MeasureSpec的model為Exactly,)并且子視圖的大小恰好跟父窗口一樣大,父窗口必須用給 定的大小去layout子視圖
參數含義:spec 父窗口傳遞給子視圖的大小和模式
padding 父窗口的邊距,也就是xml中的android:padding
childDimension 子視圖想要繪制的準確大小,但最終不一定繪制此值

當得到了每一個子控件的大小以后,再要計算自己的寬高就簡單了。
int wantedHeight = childTop + lineHeight + paddingBottom;

同理,在onLayout中的這一句

for (int i = 0, childCount = getChildCount(); i < childCount; ++i) {
        View childView = getChildAt(i);

        if (childView.getVisibility() == View.GONE) {
            continue;
        }

        int childWidth = childView.getMeasuredWidth();
        int childHeight = childView.getMeasuredHeight();

        lineHeight = Math.max(childHeight, lineHeight);

        if (childLeft + childWidth + paddingRight > myWidth) {
            childLeft = paddingLeft;
            childTop += mVerticalSpacing + lineHeight;
            lineHeight = childHeight;
        }
        childView.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
        childLeft += childWidth + mHorizontalSpacing;
    }

首先通過循環遍歷,控制每個item子控件的顯示位置,如果當前行還能放得下一個item,就放到當前行,如果放不下就放到下一行的最左邊。
最終,遍歷完成,也就相當于把自己的位置顯示完成了。

效果截圖

Android流式布局實現
來自:http://www.kymjs.com/code/2015/06/06/01/

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!