5v >流式布局是啥呢?相信大家各种app里面都见过这样的布局,下面2张图分别是网易云音乐的热门搜索栏,和淘宝的历史搜索栏。用的就是流式布局,可见流式布局应用还是挺广泛的
在自定义流式布局之前,我们先说下它的特点以及应用场景。
流式布局的特点:简单的来说就是,一行放不下,就放到下一行。
应用场景:各大APP的热门搜索栏,历史搜索栏等等。
1、分析
1、首先应该继承ViewGroup
2、对于FlowLayout,需要指定的LayoutParams,我们目前只需要能够识别margin即可,即使用MarginLayoutParams,需要重写generateLayoutParams方法.
3、重写onMeasure方法,测量子view的宽高,设置自己的宽和高;在onMeasure中根据 child views 计算出FlowLayout高度;如果不是wrap_content(可能是确切的值或者match_parent),此时的测量模式都是EXACTTLY,接使用父ViewGroup传入的计算值即可。如果是wrap_content,onMeasure中计算所有childView的宽和高,然后根据childView的宽和高,计算自己的宽和高。
4、重写onLayout方法,在onLayout中根据子View的宽高对所有的childView进行布局。
2、重写generateLayoutParams方法
ViewGroup LayoutParams :每个 ViewGroup 对应一个 LayoutParams; 即 ViewGroup -> LayoutParams
generateLayoutParams不知道转为哪个对应的LayoutParams ,其实很简单,就是如下:
子View.getLayoutParams 得到的LayoutParams对应的就是 子View所在的父控件的LayoutParams;
例如,LinearLayout 里面的子view.getLayoutParams ->LinearLayout.LayoutParams
所以 咱们的FlowLayout 也需要一个LayoutParams,由于上面的效果图是子View的 margin,
所以应该使用MarginLayoutParams。即FlowLayout->MarginLayoutParams
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(),attrs);
}
3、重写onMeasure
/*** 负责测量子控件的宽高,根据子控件的宽高,来设置自己的宽高
*
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//获取父容器给子View(FlowLayout)设置的测量模式和大小
int iWidthMode = MeasureSpec.getMode(widthMeasureSpec);//宽的测量模式
int iWidthSize = MeasureSpec.getSize(widthMeasureSpec);//宽的测量大小
int iHeightMode = MeasureSpec.getMode(heightMeasureSpec);
int iHeightSize = MeasureSpec.getSize(heightMeasureSpec);
int iMeasureW = 0;//最终测量出来的自身(FlowLayout)的宽
int iMeasureH = 0;//最终测量出来的自身(FlowLayout)的高
if (iWidthMode == MeasureSpec.EXACTLY && iHeightMode == MeasureSpec.EXACTLY) {
iMeasureW = iWidthSize;
iMeasureH = iHeightSize;
} else {
int iChildCount = getChildCount();
int iChildWidth = 0;//测量出来的子View的宽
int iChildHeight = 0;//测量出来的子View的高
int iCurLineW = 0;//记录当前行的宽度(是通过累加这一行所有View的宽度得来的)
int iCurLineH = 0;//记录当前行的高度
List<View> viewList = new ArrayList<>();
for (int i = 0; i < iChildCount; i++) {
View childView = getChildAt(i);
measureChild(childView, widthMeasureSpec, heightMeasureSpec);
MarginLayoutParams layoutParams = (MarginLayoutParams) childView.getLayoutParams();
iChildWidth = childView.getMeasuredWidth() + layoutParams.leftMargin + layoutParams.rightMargin;
iChildHeight = childView.getMeasuredHeight() + layoutParams.topMargin + layoutParams.bottomMargin;
//当(不断累加的行的宽度+正在算的子View的宽度)>父容器的宽度时,就要换行
if (iCurLineW + iChildWidth > iWidthSize) {
/**************************记录当前行的信息*********************/
//iMeasureW(历史最大宽度,也是最终的FlowLayout的宽度)
//记录当前行的信息(取历史测量出的宽度和当前这行的宽度中的最大值)
iMeasureW = Math.max(iMeasureW, iCurLineW);
iMeasureH += iCurLineH;//行高累加
//将当前行的viewList添加至总的mViewList
mViewLinesList.add(viewList);
//当前行的行高添加到总的行高里
mLineHeights.add(iCurLineH);
/**************************记录新建行的信息*********************/
//1、新建一行,重新赋值新建的那一行的宽高
iCurLineW = iChildWidth;//新建行的宽=当前子View的宽
iCurLineH = iChildHeight;
//2、新建一行的viewList初始化,添加第一个childView
viewList = new ArrayList<>();
viewList.add(childView);
// /**************************换行时,如果正好是最后一个需要换行*********************/
// //需要特别注意,特别容易出错,负责会少一行
// if(i==iChildCount-1){
// iMeasureW = Math.max(iMeasureW,iCurLineW);
// iMeasureH +=iCurLineH;//行高累加
//
// //将当前行的viewList添加至总的mViewList
// mViewLinesList.add(viewList);
// //当前行的行高添加到总的行高里
// mLineHeights.add(iCurLineH);
// }
} else {//没有超过,就行内宽高累加
/**************************1记录行内的信息*********************/
iCurLineW += iChildWidth;
//取当前行高与当前View的行高中的大值
iCurLineH = Math.max(iCurLineH, iChildHeight);
/**************************2添加至当前行的viewList里*********************/
viewList.add(childView);
}
//注意别加错地方
/**************************换行时,如果正好是最后一个需要换行*********************/
//需要特别注意,特别容易出错,负责会少一行
if (i == iChildCount - 1) {
//为啥比较历史最大宽和当前行宽的大小(因为如果只有一个view,那么历史最大宽首先为0
// ,还有下一行宽可能大于或者小于之前的历史最大宽度)
iMeasureW = Math.max(iMeasureW, iCurLineW);
iMeasureH += iCurLineH;//行高累加
//将当前行的viewList添加至总的mViewList
mViewLinesList.add(viewList);
//当前行的行高添加到总的行高里
mLineHeights.add(iCurLineH);
}
}
}
setMeasuredDimension(iMeasureW, iMeasureH);
}
4、重写onLayout
代码与onMeasure非常类似,只需要根据child view的宽度和高度放到指定位置即可。
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int iLineSize=mViewLinesList.size();
int iChildLeft;
int iChildTop;
int iChildRight;
int iChildBottom;
int iCurLeft = 0;
int iCurTop = 0;
for (int i=0;i<iLineSize;i++){
List<View> viewList = mViewLinesList.get(i);
for (int j=0;j<viewList.size();j++){
View childView = viewList.get(j);
MarginLayoutParams layoutParams = (MarginLayoutParams) childView.getLayoutParams();
iChildLeft = iCurLeft+layoutParams.leftMargin;
iChildTop = iCurTop+layoutParams.topMargin;
//下面少写了childView.getMeasuredWidth()的child导致背景连成一片
iChildRight = iChildLeft+childView.getMeasuredWidth();
iChildBottom = iChildTop+childView.getMeasuredHeight();
childView.layout(iChildLeft,iChildTop,iChildRight,iChildBottom);
//摆完一个View后,起始点要跟着挪
iCurLeft += childView.getMeasuredWidth()+layoutParams.leftMargin+layoutParams.rightMargin;
}
//画完一行后iCurTop也要累加
iCurTop += mLineHeights.get(i);
iCurLeft = 0;//因为换行了,所以iCurLeft归0
}
//特别注意:一定得清空,否则没有效果
mViewLinesList.clear();
mLineHeights.clear();
}