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);