具体实现步骤:
1.继承ViewGroup,实现三个构造方法
2.通过generateLayoutParams给自定义的控件指定参数
3.实现onMeasure方法
a.在这个方法里面首先要做是要知道自己的大小,onMeasure方法会通过父类获取具体的模式和大小。通过getMode方法获得模式(三种模式就不详细说了),然后通过getSize方法获取具体的尺寸。
b.通过遍历子控件得到ViewGroup显示时的高度。当子控件(即标签)的宽度之和大于父控件的时候开启行并累加高度
c.setMeasuredDimension((modeWidth == MeasureSpec.EXACTLY) ? sizeWidth
: width, (modeHeight == MeasureSpec.EXACTLY) ? sizeHeight
: height); 方法确定我们ViewGroup最终的大小
4.重写onLayout方法
a.同样的需要遍历子控件,根据宽度控制是否换行。并将每一行的空间用一个列表记录下来,并记录高度从而决定下一行需要显示的位置。
b.上面已经将标签以行为单位分别放到适当的list里面。这里就是将list里面的控件显示出来。说到底其实就是一个道理:不管通过什么样的算法,只要知道子控件正确的显示位置即可。
c.确定完位置后调用child.layout(lc, tc, rc, bc); 方法将它画出来即可。需要注意的地方是最后一行我们需要特殊处理。通过上面的判断我们实际的最后一行是永远不会大于ViewGroup的宽度的,而这一行的高度同样需要记录下来
完整的代码如下:
package com.xiaoying.widget; import java.util.ArrayList; import java.util.List; import android.content.Context; import android.util.AttributeSet; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; import android.widget.RelativeLayout; import android.widget.TextView; import com.xiaoying.common.util.Utils; import com.xiaoying.cuishou.R; /** * @author Roy * @version V1.0 date:2017/11/30 下午6:07 */ public class MyFlowLayout extends ViewGroup { private MarkClickListener markClickListener; public MyFlowLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } public MyFlowLayout(Context context, AttributeSet attrs) { super(context, attrs); } public MyFlowLayout(Context context) { super(context); } public void setData(String[] data){ createChild(data,10, 15, 10, 15, 10, 0, 12, 12 , 0); } public void setData(String[] data, int textSize,int pl,int pt,int pr,int pb,int ml,int mt,int mr,int mb){ createChild(data,textSize, pl, pt, pr, pb, ml, mt, mr, mb); } public void setData(List<String> data){ String[] mydata = null; if(data!=null){ int length = data.size(); mydata = new String[length]; for(int i = 0 ; i<length;i++){ mydata[i] = data.get(i); } } setData(mydata); } public void setData(List<String> data,int textSize,int pl,int pt,int pr,int pb,int ml,int mt,int mr,int mb){ String[] mydata = null; if(data!=null){ int length = data.size(); mydata = new String[length]; for(int i = 0 ; i<length;i++){ mydata[i] = data.get(i); } } setData(mydata, textSize,pl, pt, pr, pb, ml, mt, mr, mb); } public void setOnClickListener(MarkClickListener markClickListener){ this.markClickListener = markClickListener; } public interface MarkClickListener{ void onRemarkClick(String str); } private void createChild(String[] data,int textSize,int pl,int pt,int pr,int pb,int ml,int mt,int mr,int mb){ int size = data.length; for(int i = 0;i<size;i++){ String text = data[i]; TextView btn = new TextView(getContext()); btn.setClickable(true); btn.setGravity(Gravity.CENTER); btn.setText(text); btn.setTag(text); btn.setTextSize(textSize); btn.setPadding(Utils.dip2px(getContext(), pl), Utils.dip2px(getContext(), pt), Utils.dip2px(getContext(), pr), Utils.dip2px(getContext(), pb)); btn.setTextColor(0xff2b3041); /*btn.setTextColor(getResources().getColorStateList(R.color.selector_button_tc));*/ btn.setBackgroundResource(R.drawable.mark_green); MarginLayoutParams params = new MarginLayoutParams(MarginLayoutParams.WRAP_CONTENT, MarginLayoutParams.WRAP_CONTENT); params.setMargins(Utils.dip2px(getContext(), ml), Utils.dip2px(getContext(), mt), Utils.dip2px(getContext(), mr), Utils.dip2px(getContext(), mb)); btn.setLayoutParams(params); btn.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { markClickListener.onRemarkClick(((TextView)v).getText().toString()); } }); this.addView(btn); } } @Override public LayoutParams generateLayoutParams(AttributeSet attrs) { return new MarginLayoutParams(getContext(), attrs); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); int childCount = getChildCount(); int lineWidth = 0; int lineHeight = 0; int width = 0;//warpcontet是需要记录的宽度 int height = 0; for(int i = 0 ; i< childCount;i++){ View child = getChildAt(i); // 测量每一个child的宽和高 measureChild(child, widthMeasureSpec, heightMeasureSpec); MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); int childWidth = child.getMeasuredWidth()+lp.leftMargin+lp.rightMargin; int childHeight = child.getMeasuredHeight()+lp.topMargin+lp.bottomMargin; if(lineWidth+childWidth>widthSize){ //这种情况就是排除单个标签很长的情况 width = Math.max(lineWidth, childWidth); //开启新行 lineWidth = childWidth; //记录总行高 height += lineHeight; //因为开了新行,所以这行的高度要记录一下 lineHeight = childHeight; }else{ lineWidth += childWidth; //记录行高 lineHeight = Math.max(lineHeight, childHeight); } // 如果是最后一个,则将当前记录的最大宽度和当前lineWidth做比较 if (i == childCount - 1) { //宽度 width = Math.max(width, lineWidth); height += lineHeight; } } setMeasuredDimension((widthMode == MeasureSpec.EXACTLY) ? widthSize : width, (heightMode == MeasureSpec.EXACTLY) ? heightSize : height); } /** * 存储所有的View,按行记录 */ private List<List<View>> mAllViews = new ArrayList<List<View>>(); /** * 记录每一行的最大高度 */ private List<Integer> mLineHeight = new ArrayList<Integer>(); //onLayout中完成对所有childView的位置以及大小的指定 @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { //清空子控件列表 mAllViews.clear(); //清空高度记录列表 mLineHeight.clear(); //得到当前控件的宽度(在onmeasure方法中已经测量出来了) int width = getWidth(); int childCount = getChildCount(); // 存储每一行所有的childView List<View> lineViews = new ArrayList<View>(); //行高 int lineWidth = 0; //总行高 int lineHeight = 0; for(int i = 0 ; i<childCount;i++){ View child = getChildAt(i); //得到属性参数 MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); int childWidth = child.getMeasuredWidth(); int childHeight = child.getMeasuredHeight(); // 如果需要换行 if (childWidth + lp.leftMargin + lp.rightMargin + lineWidth > width) { mLineHeight.add(lineHeight); // 将当前行的childView保存,然后开启新的ArrayList保存下一行的childView mAllViews.add(lineViews); // 重置行宽 lineWidth = 0; lineHeight = 0; lineViews = new ArrayList<View>(); } /** * 如果不需要换行,则累加 */ lineWidth += childWidth + lp.leftMargin + lp.rightMargin; lineHeight = Math.max(lineHeight, childHeight + lp.topMargin + lp.bottomMargin); lineViews.add(child); } // 记录最后一行 (因为最后一行肯定大于父布局的宽度,所以添加最后一行是必要的) mLineHeight.add(lineHeight); mAllViews.add(lineViews); int left = 0; int top = 0; int lineNums = mAllViews.size(); for(int i = 0;i<lineNums;i++){ // 每一行的所有的views lineViews = mAllViews.get(i); // 当前行的最大高度 lineHeight = mLineHeight.get(i); for(int j = 0 ;j < lineViews.size() ; j++){ View lineChild = lineViews.get(j); if(lineChild.getVisibility() == View.GONE){ continue; } MarginLayoutParams lp = (MarginLayoutParams) lineChild.getLayoutParams(); //开始画标签了。左边和上边的距离是要根据累计的数确定的。 int lc = left + lp.leftMargin; int tc = top+lp.topMargin; int rc = lc+lineChild.getMeasuredWidth(); int bc = tc+lineChild.getMeasuredHeight(); lineChild.layout(lc, tc, rc, bc); left += lineChild.getMeasuredWidth() + lp.rightMargin + lp.leftMargin; } //将left归零 left = 0; top += lineHeight; } } }
在界面中调用的方法
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); MyFlowLayout myview = (MyFlowLayout) findViewById(R.id.myview); String[] myData = {"one","two","dkfjdkf","kdfkdfj","jdkfdfkjdkfdkfkdkdfj","kdjkfdjkjkjskkjkd"}; myview.setData(myData, this, 15, 10, 10, 10, 10, 10, 10, 10, 10); }
下载连接: http://download.csdn.net/detail/u010419467/8956027
希望爱好编程的小伙伴能加这个群,互相帮助,共同学习。群号: 141877583
最后附上完整项目(不需要积分)(ps:项目中的类请以博客中为准)