前言:
自定义View与自定义ViewGroup的区别:
- 自定义View:在没有现成的View,需要自己实现的时候,就使用自定义View,一般继承自View,SurfaceView或其他的View。这个是控件。
- 自定义ViewGroup:一般是利用现有的组件根据特定的布局方式来组成新的组件,大多继承自ViewGroup或各种Layout。这个是组件。
自定义View的绘制流程图如下:
下面来实现流式布局,定义一个类FlowLayout继承自ViewGroup,重写里面的相关方法,实现流式布局的效果。
public class FlowLayout extends ViewGroup { //每个item横向间距 private int mHorizontalSpacing = dp2px(16); //每个item竖向间距 private int mVerticalSpacing = dp2px(8); //记录所有的行,一行一行的存储,用于Layout private List<List<View>> allLines; //记录每一行的行高,用于Layout List<Integer> lineHeights = new ArrayList<>(); //new FlowLayout(传入上下文) //在代码中创建组件时会调用该构造方法 //比如创建一个按钮:Button btn=new Button(this), // this 是指当前的 Activity,Activity 是 Context 的子类 public FlowLayout(Context context) { super(context); } //在xml中使用 在xml转化为java代码的时候,通过反射调用有两个参数的构造函数 //在 layout 布局文件中使用时调 //用,参数 attrs 表示当前配置中的属性集合,例如在要 layout.xml 中定义一个按钮: //<Button // android:layout_width = "match_parent" // android:layout_height = "wrap_content" //android:text ="OK"/> // Android 会调用第二个构造方法 Inflate 出 Button 对象 public FlowLayout(Context context, AttributeSet attrs) { super(context, attrs); } //代码中有不同的主题style,调用有三个参数的构造函数 //该方法不会自动调用的,当我们在 Theme 中定义了 Style 属性时通常在第二个 //构造方法中手动调用。 public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } //进行集合的初始化 private void initMeasureParams() { if (allLines != null) { allLines.clear(); } else { allLines = new ArrayList<>(); } if (lineHeights != null) { lineHeights.clear(); } else { lineHeights = new ArrayList<>(); } } //度量 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); //每次进入onMeasure()方法中 都需要将全局变量重新初始化 initMeasureParams(); //度量子View的宽度和高度 int childCount = getChildCount(); int paddingLeft = getPaddingLeft(); int paddingRight = getPaddingRight(); int paddingTop = getPaddingTop(); int paddingBottom = getPaddingBottom(); //ViewGroup解析的宽度 int selfWidth = MeasureSpec.getSize(widthMeasureSpec); //ViewGroup解析的高度 int selfHeight = MeasureSpec.getSize(heightMeasureSpec); //保存一行中的所有View List<View> lineViews = new ArrayList<>(); //记录这一行已经使用了多宽的size int lineWidthUsed = 0; //一行的行高 int lineHeight = 0; //measure过程中,子View要求的父ViewGroup的宽 int parentNeededWidth = 0; //measure过程中,子View要求的父ViewGroup的高 int parentNeededHeight = 0; for (int i = 0; i < childCount; i++) { View childView = getChildAt(i); LayoutParams childLP = childView.getLayoutParams(); int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, paddingLeft + paddingRight, childLP.width); int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, paddingBottom + paddingTop, childLP.height); childView.measure(childWidthMeasureSpec, childHeightMeasureSpec); //获取子View的宽高 int childMeasuredWidth = childView.getMeasuredWidth(); int childMeasuredHeight = childView.getMeasuredHeight(); //通过宽度来判断是否需要换行,通过换行后的每行的行高来获取 //整个ViewGroup的行高 if (childMeasuredWidth + lineWidthUsed + mHorizontalSpacing > selfWidth) { allLines.add(lineViews); lineHeights.add(lineHeight); //一旦换行,我们就可以判断当前列需要的宽和高了,所以此时需要记录下来 parentNeededHeight = parentNeededHeight + lineHeight + mVerticalSpacing; parentNeededWidth = Math.max(parentNeededWidth, lineWidthUsed + mHorizontalSpacing); lineViews = new ArrayList<>(); lineWidthUsed = 0; lineHeight = 0; } //view是分行layout的,所以要记录每一行有哪些View,这样可以方便布局 lineViews.add(childView); //每行都会有自己的宽和高 lineWidthUsed = lineWidthUsed + childMeasuredWidth + mHorizontalSpacing; lineHeight = Math.max(lineHeight, childMeasuredHeight); } //根据子View的度量结果,来重新度量自己的ViewGroup //作为一个ViewGroup,它自己也是一个View,它的大小也需要根据它的父亲给它提供的宽高来度量 int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int realWidth = (widthMode == MeasureSpec.EXACTLY) ? selfWidth : parentNeededWidth; int realHeight = (heightMode == MeasureSpec.EXACTLY) ? selfHeight : parentNeededHeight; //度量自己的宽度和高度 setMeasuredDimension(realWidth, realHeight); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { //获取Layout中的行数 int lineCount = allLines.size(); int curL = getPaddingLeft(); int curT = getPaddingTop(); for (int i = 0; i < lineCount; i++) { List<View> lineViews = allLines.get(i); int lineHeight = lineHeights.get(i); for (int j = 0; j < lineViews.size(); j++) { View view = lineViews.get(j); int left = curL; int top = curT; int right = left + view.getMeasuredWidth(); int bottom = top + view.getMeasuredHeight(); view.layout(left, top, right, bottom); curL = right + mHorizontalSpacing; } curL = getPaddingLeft(); curT = lineHeight + curT + mVerticalSpacing; } } //dp转为px public static int dp2px(int dp) { return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, Resources.getSystem().getDisplayMetrics()); } }
之后我们在xml中,进行引用,就可以了。
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".ViewGroupActivity"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="搜索历史" android:textColor="@color/black" android:textSize="17sp" /> <com.example.animationtest.view.FlowLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="8dp"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/tv_back_color" android:text="水果味孕妇奶粉" android:textColor="@color/black" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/tv_back_color" android:text="儿童洗衣机" android:textColor="@color/black" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/tv_back_color" android:text="洗衣机全自动" android:textColor="@color/black" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/tv_back_color" android:text="手机" android:textColor="@color/black" android:textSize="16sp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/tv_back_color" android:text="儿童汽车可坐人111111111111111111" android:textColor="@color/black" android:textSize="16sp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/tv_back_color" android:text="抽真空收纳袋" android:textColor="@color/black" android:textSize="16sp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/tv_back_color" android:text="儿童滑板车" android:textColor="@color/black" android:textSize="16sp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/tv_back_color" android:text="稳定器 电容" android:textColor="@color/black" android:textSize="16sp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/tv_back_color" android:text="儿童洗衣机" android:textColor="@color/black" android:textSize="16sp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/tv_back_color" android:text="衣服" android:textColor="@color/black" android:textSize="16sp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/tv_back_color" android:text="运动鞋" android:textColor="@color/black" android:textSize="16sp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/tv_back_color" android:text="手表" android:textColor="@color/black" android:textSize="16sp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/tv_back_color" android:text="水果味孕妇奶粉" android:textColor="@color/black" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/tv_back_color" android:text="儿童洗衣机" android:textColor="@color/black" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/tv_back_color" android:text="洗衣机全自动" android:textColor="@color/black" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/tv_back_color" android:text="手机" android:textColor="@color/black" android:textSize="16sp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/tv_back_color" android:text="抽真空收纳袋" android:textColor="@color/black" android:textSize="16sp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/tv_back_color" android:text="儿童滑板车" android:textColor="@color/black" android:textSize="16sp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/tv_back_color" android:text="稳定器 电容" android:textColor="@color/black" android:textSize="16sp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/tv_back_color" android:text="儿童洗衣机" android:textColor="@color/black" android:textSize="16sp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/tv_back_color" android:text="衣服" android:textColor="@color/black" android:textSize="16sp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/tv_back_color" android:text="运动鞋" android:textColor="@color/black" android:textSize="16sp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/tv_back_color" android:text="手表" android:textColor="@color/black" android:textSize="16sp" /> </com.example.animationtest.view.FlowLayout> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="搜索发现" android:textColor="@color/black" android:textSize="17sp" /> <com.example.animationtest.view.FlowLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="8dp"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/tv_back_color" android:text="水果味孕妇奶粉" android:textColor="@color/black" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/tv_back_color" android:text="儿童洗衣机" android:textColor="@color/black" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/tv_back_color" android:text="洗衣机全自动" android:textColor="@color/black" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/tv_back_color" android:text="手机" android:textColor="@color/black" android:textSize="16sp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/tv_back_color" android:text="儿童汽车可坐人111111111111111111" android:textColor="@color/black" android:textSize="16sp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/tv_back_color" android:text="抽真空收纳袋" android:textColor="@color/black" android:textSize="16sp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/tv_back_color" android:text="儿童滑板车" android:textColor="@color/black" android:textSize="16sp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/tv_back_color" android:text="稳定器 电容" android:textColor="@color/black" android:textSize="16sp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/tv_back_color" android:text="儿童洗衣机" android:textColor="@color/black" android:textSize="16sp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/tv_back_color" android:text="衣服" android:textColor="@color/black" android:textSize="16sp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/tv_back_color" android:text="运动鞋" android:textColor="@color/black" android:textSize="16sp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/tv_back_color" android:text="手表" android:textColor="@color/black" android:textSize="16sp" /> </com.example.animationtest.view.FlowLayout> </LinearLayout>
运行后,效果如下: