package com.mcxtzhang.zxtcommonlib.widget.FlowLayout; import android.content.Context; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; /** * 流式布局 * Created by zhangxutong on 2016/1/17. * 1 增加点击事件 * 2 增加Adapter * Updated by zhangxutong on 2016/10/11 */ public class FlowViewGroup extends ViewGroup { private static final String TAG = "zxt/FlowViewGroup"; private FlowBaseAdapter mAdapter; public FlowViewGroup(Context context) { this(context, null); } public FlowViewGroup(Context context, AttributeSet attrs) { this(context, attrs, 0); } public FlowViewGroup(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } //在onMeasure里,测量所有子View的宽高,以及确定Viewgroup自己的宽高。 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //获取系统传递过来测量出的宽度 高度,以及相应的测量模式。 //如果测量模式为 EXACTLY( 确定的dp值,match_parent),则可以调用setMeasuredDimension()设置, //如果测量模式为 AT_MOST(wrap_content),则需要经过计算再去调用setMeasuredDimension()设置 int widthMeasure = MeasureSpec.getSize(widthMeasureSpec); int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightMeasure = MeasureSpec.getSize(heightMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); //计算宽度 高度 //wrap_content测量模式下会使用到: //存储最后计算出的宽度, int maxLineWidth = 0; //存储最后计算出的高度 int totalHeight = 0; //存储当前行的宽度 int curLineWidth = 0; //存储当前行的高度 int curLineHeight = 0; // 得到内部元素的个数 int count = getChildCount(); //存储子View View child = null; //存储子View的LayoutParams MarginLayoutParams params = null; //子View Layout需要的宽高(包含margin),用于计算是否越界 int childWidth; int childHeight; //遍历子View 计算父控件宽高 for (int i = 0; i < count; i++) { child = getChildAt(i); //如果gone,不测量了 if (View.GONE == child.getVisibility()) { continue; } //先测量子View measureChild(child, widthMeasureSpec, heightMeasureSpec); //获取子View的LayoutParams,(子View的LayoutParams的对象类型,取决于其ViewGroup的generateLayoutParams()方法的返回的对象类型,这里返回的是MarginLayoutParams) params = (MarginLayoutParams) child.getLayoutParams(); //子View需要的宽度 为 子View 本身宽度+marginLeft + marginRight childWidth = child.getMeasuredWidth() + params.leftMargin + params.rightMargin; childHeight = child.getMeasuredHeight() + params.topMargin + params.bottomMargin; //Log.i(TAG, "子View Layout需要的宽高(包含margin):childWidth:" + childWidth + " ,childHeight:" + childHeight); //如果当前的行宽度大于 父控件允许的最大宽度 则要换行 //父控件允许的最大宽度 如果要适配 padding 这里要- getPaddingLeft() - getPaddingRight() //即为测量出的宽度减去父控件的左右边距 if (curLineWidth + childWidth > widthMeasure - getPaddingLeft() - getPaddingRight()) { //通过比较 当前行宽 和以前存储的最大行宽,得到最新的最大行宽,用于设置父控件的宽度 maxLineWidth = Math.max(maxLineWidth, curLineWidth); //父控件的高度增加了,为当前高度+当前行的高度 totalHeight += curLineHeight; //换行后 刷新 当前行 宽高数据: 因为新的一行就这一个View,所以为当前这个view占用的宽高(要加上View 的 margin) curLineWidth = childWidth; curLineHeight = childHeight; } else { //不换行:叠加当前行宽 和 比较当前行高: curLineWidth += childWidth; curLineHeight = Math.max(curLineHeight, childHeight); } //如果已经是最后一个View,要比较当前行的 宽度和最大宽度,叠加一共的高度 if (i == count - 1) { maxLineWidth = Math.max(maxLineWidth, curLineWidth); totalHeight += childHeight; } } //Log.i(TAG, "系统测量允许的尺寸最大值:widthMeasure:" + widthMeasure + " ,heightMeasure:" + heightMeasure); //Log.i(TAG, "经过我们测量实际的尺寸(不包括父控件的padding):maxLineWidth:" + maxLineWidth + " ,totalHeight:" + totalHeight); //Log.i(TAG, "heightMode == MeasureSpec.AT_MOST:" +(heightMode == MeasureSpec.AT_MOST)); //Log.i(TAG, "heightMode == MeasureSpec.EXACTLY:" +(heightMode == MeasureSpec.EXACTLY)); //Log.i(TAG, "heightMode == MeasureSpec.UNSPECIFIED:" +(heightMode == MeasureSpec.UNSPECIFIED)); //适配padding,如果是wrap_content,则除了子控件本身占据的控件,还要在加上父控件的padding setMeasuredDimension( widthMode != MeasureSpec.EXACTLY ? maxLineWidth + getPaddingLeft() + getPaddingRight() : widthMeasure, heightMode != MeasureSpec.EXACTLY ? totalHeight + getPaddingTop() + getPaddingBottom() : heightMeasure);//fix by zhangxutong 有些页面太长了 } //布局父控件位置以及子控件的位置 @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { //Log.i(TAG, "changed:" + changed + " ,l:" + l + " t:" + t + " r:" + r + " b:" + b); //子控件的个数 int count = getChildCount(); //ViewParent宽度(包含padding) int width = getWidth(); //ViewParent 的右边x的布局限制值 int rightLimit = width - getPaddingRight(); //存储基准的left top (子类.layout(),里的坐标是基于父控件的坐标,所以 x应该是从0+父控件左内边距开始,y从0+父控件上内边距开始) int baseLeft = 0 + getPaddingLeft(); int baseTop = 0 + getPaddingTop(); //存储现在的left top int curLeft = baseLeft; int curTop = baseTop; //子View View child = null; //子view用于layout的 l t r b int viewL, viewT, viewR, viewB; //子View的LayoutParams MarginLayoutParams params = null; //子View Layout需要的宽高(包含margin),用于计算是否越界 int childWidth; int childHeight; //子View 本身的宽高 int childW, childH; //临时增加一个temp 存储上一个View的高度 解决过长的两行View导致显示不正确的bug int lastChildHeight = 0; // for (int i = 0; i < count; i++) { child = getChildAt(i); //add by zhangxutong 2016 10 11 begin:Feature 1:点击事件 final int finalI = i; child.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (null != mOnItemClickListener) { mOnItemClickListener.onItemClick(v, finalI, FlowViewGroup.this); } } }); //add by zhangxutong 2016 10 11 end //如果gone,不布局了 if (View.GONE == child.getVisibility()) { continue; } //获取子View本身的宽高: childW = child.getMeasuredWidth(); childH = child.getMeasuredHeight(); //获取子View的LayoutParams,用于获取其margin params = (MarginLayoutParams) child.getLayoutParams(); //子View需要的宽高 为 本身宽高+marginLeft + marginRight childWidth = childW + params.leftMargin + params.rightMargin; childHeight = childH + params.topMargin + params.bottomMargin; //这里要考虑padding,所以右边界为 ViewParent宽度(包含padding) -ViewParent右内边距 if (curLeft + childWidth > rightLimit) { //如果当前行已经放不下该子View了 需要换行放置: //在新的一行布局子View,左x就是baseLeft,上y是 top +前一行高(这里假设的是每一行行高一样), curTop = curTop + lastChildHeight; //layout时要考虑margin viewL = baseLeft + params.leftMargin; viewT = curTop + params.topMargin; viewR = viewL + childW; viewB = viewT + childH; //child.layout(baseLeft + params.leftMargin, curTop + params.topMargin, baseLeft + params.leftMargin + child.getMeasuredWidth(), curTop + params.topMargin + child.getMeasuredHeight()); //Log.i(TAG,"新的一行:" +" ,baseLeft:"+baseLeft +" curTop:"+curTop+" baseLeft+childWidth:"+(baseLeft+childWidth)+" curTop+childHeight:"+ ( curTop+childHeight)); curLeft = baseLeft + childWidth; } else { //当前行可以放下子View: viewL = curLeft + params.leftMargin; viewT = curTop + params.topMargin; viewR = viewL + childW; viewB = viewT + childH; //child.layout(curLeft + params.leftMargin, curTop + params.topMargin, curLeft + params.leftMargin + child.getMeasuredWidth(), curTop + params.topMargin + child.getMeasuredHeight()); //Log.i(TAG,"当前行:"+changed +" ,curLeft:"+curLeft +" curTop:"+curTop+" curLeft+childWidth:"+(curLeft+childWidth)+" curTop+childHeight:"+(curTop+childHeight)); curLeft = curLeft + childWidth; } lastChildHeight = childHeight; //布局子View child.layout(viewL, viewT, viewR, viewB); } } /** * @return 当前ViewGroup返回的Params的类型 */ @Override public LayoutParams generateLayoutParams(AttributeSet attrs) { return new MarginLayoutParams(getContext(), attrs); } /** * 外部调用 同时刷新视图 * * @param mAdapter */ public void setAdapter(FlowBaseAdapter mAdapter) { this.mAdapter = mAdapter; updateUI(); } /** * 刷新UI */ public void updateUI() { removeAllViews(); int count = mAdapter.getCount(); if (null != mAdapter && count > 0) { for (int i = 0; i < count; i++) { View child = mAdapter.getView(this, i); addView(child); } } } //看名字 public interface OnItemClickListener { void onItemClick(View view, int pos, FlowViewGroup parent); } private OnItemClickListener mOnItemClickListener; public OnItemClickListener getOnItemClickListener() { return mOnItemClickListener; } public FlowViewGroup setOnItemClickListener(OnItemClickListener onItemClickListener) { mOnItemClickListener = onItemClickListener; return this; } }