Android流式佈局實現
Android流式佈局實現
上一篇給大家介紹了一下 Android根據標籤長度自動換行,後來在開發實際中發現了這個自定義控件在addview(textview)時,修改textview顯示的內容時,這個自定義控件不能自動修改寬度去適應佈局的變化,這一篇經過查找資料後摸索了一個比較流行的流式佈局,這個流式佈局在各大app上都非常的手歡迎(天貓、京東等的購物車都採用了這個流式佈局)
接下來走一下完整的自定義控件的流程來給大家簡單的介紹一下
1.控件的創建
(1)當這個流式佈局在被加載如內存並顯示在屏幕上這一過程中,首先會調用view.measure(w,h)這個方法,表示測量view的寬度與高度,其中參數w與h分別表示這個控件的父控件的寬高。
(2)在view.measure()方法的調用過程中又會調用view本身的一個回調方法,onMeasure(),這個是view自身的一個回調方法,用於讓開發者在自定義View的時候重新計算自身的大小。一般會在這個方法中循環遍歷,計算出這個控件的全部子孫控件的寬高。
(3)在View的寬高計算完成以後,考慮將這個控件顯示到屏幕的指定位置上,此時view的onLayout()方法會被調用。 一般同時會在這個方法中計算出全部子孫控件在這個控件中的位置。
可能基本流程有些枯燥,接下來結合代碼看看。
2.流式佈局的實現
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 + childWidth;
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 + childWidth;
childTop += mVerticalSpacing + lineHeight;
lineHeight = childHeight;
}
childView.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
childLeft += childWidth + mHorizontalSpacing;
}
首先通過循環遍歷,控制每個item子控件的顯示位置,如果當前行還能放得下一個item,就放到當前行,如果放不下就放到下一行的最左邊。
最終,遍歷完成,也就相當於把自己的位置顯示完成了。
完整代碼如下
/**
* Created by Administrator on 2016/7/20.
* Android流式佈局
* @auther madreain
*/
public class LabelLayout extends ViewGroup {
private float mVerticalSpacing; //每個item縱向間距
private float mHorizontalSpacing; //每個item橫向間距
public LabelLayout (Context context) {
super(context);
}
public LabelLayout (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;
}
}
}
下面介紹代碼中加入這個流式佈局及在流式佈局中添加子佈局
label_layout_show_label = new LabelLayout (this);
//加入流式佈局
LinearLayout.LayoutParams label_layout_show_labellayoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
label_layout_show_labellayoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
label_layout_show_labellayoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
label_layout_show_labellayoutParams.setMargins(PixelOrdpManager.dip2px(getBaseContext(), 13), PixelOrdpManager.dip2px(getBaseContext(), 12), 0, PixelOrdpManager.dip2px(getBaseContext(), 12));
label_layout_show_labellayoutParams.gravity = Gravity.CENTER;
label_layout_show_label.setLayoutParams(label_layout_show_labellayoutParams);
label_layout_show_label_parent.addView(label_layout_show_label);
//流式佈局中加入子佈局
textViewLabelOne = new TextView(this);
textViewLabelTwo = new TextView(this);
textViewLabelThere = new TextView(this);
textViewLabelOne.setText("");
textViewLabelTwo.setText("");
textViewLabelThere.setText("");
textViewLabelOne.setBackgroundResource(R.drawable.add_success_label);
textViewLabelOne.setTextColor(getResources().getColor(R.color.M4A4D4F));
textViewLabelOne.setTextSize(10);
textViewLabelOne.setGravity(Gravity.CENTER);
textViewLabelTwo.setBackgroundResource(R.drawable.add_success_label);
textViewLabelTwo.setTextColor(getResources().getColor(R.color.M4A4D4F));
textViewLabelTwo.setTextSize(10);
textViewLabelTwo.setGravity(Gravity.CENTER);
textViewLabelThere.setBackgroundResource(R.drawable.add_success_label);
textViewLabelThere.setTextColor(getResources().getColor(R.color.M4A4D4F));
textViewLabelThere.setTextSize(10);
textViewLabelThere.setGravity(Gravity.CENTER);
LinearLayout.LayoutParams textViewLabellayoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
textViewLabellayoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT;
// textViewLabellayoutParams.width = PixelOrdpManager.dip2px(getBaseContext(),100);
textViewLabellayoutParams.height = PixelOrdpManager.dip2px(getBaseContext(), 25);
textViewLabellayoutParams.setMargins(PixelOrdpManager.dip2px(getBaseContext(), 13), PixelOrdpManager.dip2px(getBaseContext(), 12), 0, PixelOrdpManager.dip2px(getBaseContext(), 12));
textViewLabellayoutParams.gravity = Gravity.CENTER;
textViewLabelOne.setLayoutParams(textViewLabellayoutParams);
textViewLabelTwo.setLayoutParams(textViewLabellayoutParams);
textViewLabelThere.setLayoutParams(textViewLabellayoutParams);
label_layout_show_label.addView(textViewLabelOne);
label_layout_show_label.addView(textViewLabelTwo);
label_layout_show_label.addView(textViewLabelThere);