Android流式布局實現
摘要
本文原創,轉載請注明地址: http://kymjs.com/code/2015/06/06/01本博客客戶端下載【 請點擊】
新項目用到了一種全新布局————Android標簽流式布局的功能,正好一直說給大家講自定義控件的實現,今天就為大家講一種android流式布局的實現。
在日常的app使用中,我們會在android 的app中看見熱門標簽等自動換行的流式布局,今天,我們就來看看如何自定義一個類似熱門標簽那樣的流式布局吧(源碼下載在下面最后給出)
這個控件并不是我實現的,代碼是從網上搜流式布局找到的。我只是為大家講解一下實現過程以及原理。
先看代碼
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; } } }
從控件創建過程說起
- 當這個流式布局在被加載如內存并顯示在屏幕上這一過程中,首先會調用view.measure(w,h)這個方法,表示測量view的寬度與高度,其中參數w與h分別表示這個控件的父控件的寬高。
- 在view.measure()方法的調用過程中又會調用view本身的一個回調方法,onMeasure(),這個是view自身的一個回 調方法,用于讓開發者在自定義View的時候重新計算自身的大小。一般會在這個方法中循環遍歷,計算出這個控件的全部子孫控件的寬高。
- 在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,就放到當前行,如果放不下就放到下一行的最左邊。
最終,遍歷完成,也就相當于把自己的位置顯示完成了。