说来惭愧,我跟着视屏敲了一遍代码,受益匪浅,本想跟大家分享,奈何道行太浅,这里就不献丑解说了(偶也说不清楚),姑且做一个实用主义者吧,有需要的朋友直接复制代码就可以上手了。如果你是一只好奇的猫,建议到慕课网中倾听鸿洋大神的教诲,地址:/video/5145
直奔主题:奉上流式标签布局FlowLayout大餐。
/*** Created by JACK on 16/12/12.*/public class FlowLayout extends ViewGroup {private Context mContext;/*** 适用于当New出一个对象的时候会调用一个参数的构造方法** @param context*/public FlowLayout(Context context) {this(context, null);}/*** 在布局文件中调用属性但是没有调用自定义属性时会调用两个参数的构造方法** @param context* @param attrs*/public FlowLayout(Context context, AttributeSet attrs) {this(context, attrs, 0);}/*** 在布局文件中当使用自定义属性的时候会调用三个参数的构造方法** @param context* @param attrs* @param defStyleAttr*/public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}/*** 测量模式和测量值在其中** @param widthMeasureSpec 父类穿来的宽度的测量值* @param heightMeasureSpec 父类穿来的高度的测量值*/@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {//------------传入的精确值match_parentint sizeWidth = MeasureSpec.getSize(widthMeasureSpec);int modeWidth = MeasureSpec.getMode(widthMeasureSpec);int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);int modeHeight = MeasureSpec.getMode(heightMeasureSpec);// Log.e("TAG0","sizeWidth="+sizeWidth);// Log.e("TAG0","sizeHeight="+sizeHeight);//-------------------wrap_contentint width = 0;int height = 0;//记录每一行的宽度和高度int lineWidth = 0;int lineHeight = 0;//得到内部元素的个数int cCount = getChildCount();for (int i = 0; i < cCount; i++) {View child = getChildAt(i);//测量子View的宽和高measureChild(child, widthMeasureSpec, heightMeasureSpec);//得到LayoutParams(子View的LayoutParams其实是父布局的LayouParams)MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();//子View的宽度和高度int childWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;int childHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;//换行if (lineWidth + childWidth > sizeWidth-getPaddingLeft()-getPaddingRight()) {//当前行的宽度和已经记录的最大行的宽度进行对比留下最大宽度//布局的宽度width是最宽行的宽度width = Math.max(width, lineWidth);//重置行宽lineWidth = childWidth;//累加行高height += lineHeight;lineHeight = childHeight;//不换行} else {//叠加行宽lineWidth += childWidth;//以最宽的子View的高度为当前的行高lineHeight = Math.max(lineHeight, childHeight);}//最后一个控件特殊处理(最后一行的高度没有累加,宽度没有比较)//如果换行执行if就没有处理换行后的布局的宽度和高度//如果不换行执行else就没有叠加当前行的高度和比较最大的行宽if (i == cCount -1) {width = Math.max(width, lineWidth);height += lineHeight;}}/*if (modeWidth==MeasureSpec.AT_MOST){setMeasuredDimension(width,height);}else {setMeasuredDimension(sizeWidth,sizeHeight);}if (modeHeight==MeasureSpec.AT_MOST){setMeasuredDimension(width,height);}else {setMeasuredDimension(sizeWidth,sizeHeight);}*///换做下面的代码setMeasuredDimension(modeWidth == MeasureSpec.EXACTLY ? sizeWidth : width+getPaddingLeft()+getPaddingRight(),modeHeight == MeasureSpec.EXACTLY ? sizeHeight : height+getPaddingTop()+getPaddingBottom());//Log.e("TAG", "sizeWidth=" + sizeWidth);//Log.e("TAG", "sizeHeight=" + sizeHeight);//super.onMeasure(widthMeasureSpec, heightMeasureSpec);}/*** 与当前ViewGroup对应的layoutparams** @param attributeSet* @return*/@Overridepublic LayoutParams generateLayoutParams(AttributeSet attributeSet) {return new MarginLayoutParams(getContext(), attributeSet);//这里只关注MarginLayoutParams}// @Override为什么上面的方法不提示?只有下面的方法可选择// protected LayoutParams generateLayoutParams(LayoutParams p) {// return super.generateLayoutParams(p);// }/*** 以行来存储所有的View*/private List<List<View>> mAllViews = new ArrayList<>();/*** 每行的高度*/private List<Integer> mLineHeight = new ArrayList<>();@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {//会多次调用onLayout,所以先clearmAllViews.clear();mLineHeight.clear();//当前ViewGroup的宽度int width = getWidth();//此时已经执行了onMeasure(),可以直接调用getWidth获得宽度int lineWidth = 0;int lineHeight = 0;List<View> lineViews = new ArrayList<>();int cCount = getChildCount();for (int i = 0; i < cCount; i++) {View child = getChildAt(i);MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();int childWidth = child.getMeasuredWidth();int childHeight = child.getMeasuredHeight();if (childWidth + lineWidth + lp.leftMargin + lp.rightMargin > width-getPaddingRight()-getPaddingLeft()) {mLineHeight.add(lineHeight);//记录当前行的ViewsmAllViews.add(lineViews);//重置宽和高lineWidth = 0;lineHeight = childHeight + lp.topMargin + lp.bottomMargin;//重置行View集合lineViews = new ArrayList<>();}lineWidth += childWidth + lp.leftMargin + lp.rightMargin;lineHeight = Math.max(lineHeight, childHeight + lp.topMargin + lp.bottomMargin);lineViews.add(child);//别丢了}//for end//处理最后一行mLineHeight.add(lineHeight);mAllViews.add(lineViews);//设置子View的位置int left = getPaddingLeft();int top = getPaddingTop();//行数int lineNum = mAllViews.size();for (int i = 0; i < lineNum; i++) {//当前行的所有ViewlineViews = mAllViews.get(i);lineHeight = mLineHeight.get(i);for (int j = 0; j < lineViews.size(); j++) {View child = lineViews.get(j);if (child.getVisibility() == View.GONE) {continue;}MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();int lc = left + lp.leftMargin;int tc = top + lp.topMargin;int rc = lc + child.getMeasuredWidth();int bc = tc + child.getMeasuredHeight();//对子View 进行布局child.layout(lc, tc, rc, bc);left += child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;}//换行left = getPaddingLeft();top += lineHeight;}}}
下面是FlowLayout在布局文件中的定义,可以直接作为根布局,也可以嵌套在其他布局之中。
<?xml version="1.0" encoding="utf-8"?><com.jack.tagapp.FlowLayout xmlns:android="/apk/res/android"android:id="@+id/main_flow_layout"android:layout_width="match_parent"android:background="#68e707"android:padding="10dp"android:layout_height="wrap_content"></com.jack.tagapp.FlowLayout>
然后来看看怎么用,使用Button相对简单,但是使用TextView要自己写布局文件,不要直接new,我已经尝试结果直接就三行没有间隙的字母
public class MainActivity extends AppCompatActivity {private String[] mVals=new String[]{"Hello","Android","WelocmeToChina","Button","TopMarginLeft","Layout","AppCompatActivity","SaveInstanceState","HelloWorld","Android","Welocme","Button","Top","LayoutParams","AppCompatActivity","SaveInstanceState"};private FlowLayout mFlowLayout;private LayoutInflater mInflater;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mFlowLayout= (FlowLayout) findViewById(R.id.main_flow_layout);mInflater=LayoutInflater.from(this);initData();}public void initData(){for (int i = 0; i <mVals.length ; i++) {// Button btn=new Button(this);// ViewGroup.MarginLayoutParams lp=new ViewGroup.MarginLayoutParams(//ViewGroup.MarginLayoutParams.WRAP_CONTENT,//ViewGroup.MarginLayoutParams.WRAP_CONTENT// );// btn.setText(mVals[i]);// btn.setClickable(false);// //btn.setBackgroundColor(Color.BLUE);// mFlowLayout.addView(btn,lp);//添加lpTextView tv= (TextView) mInflater.inflate(R.layout.textview,mFlowLayout,false);tv.setText(mVals[i]);mFlowLayout.addView(tv);}}}
最后奉上TextView的布局和背景。
<?xml version="1.0" encoding="utf-8"?><TextView xmlns:android="/apk/res/android"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="HelloWorld"android:layout_marginLeft="10dp"android:layout_marginRight="10dp"android:layout_marginTop="10dp"android:layout_marginBottom="10dp"android:textColor="#ffffff"android:background="@drawable/textview_bg"></TextView>
下面的TextView的背景定义在drawable下,起到了很好的美化作用,圆角背景补白等可以实现很漂亮的整体效果。
<?xml version="1.0" encoding="utf-8"?><shape xmlns:android="/apk/res/android"><solid android:color="#f4c50b"/><corners android:radius="30dp"/><padding android:top="8dp" android:bottom="8dp"android:left="10dp"android:right="10dp"/></shape>
最后附上效果图
最后向鸿洋大神标示敬意,任然希望大家还是抽点时间去看看视频:/video/5145