自定义流式布局FlowLayout
今天给大家分享一个自定义的流式布局。自定义流式布局首先要知道什么是流式布局什么场景下会用到流式布局。
流式布局:就是由左向右依次排列如果上一行显示不下就另起一行然后再由左向右排列以此类推。
使用场景:主要用于关键词搜索或者热门标签等场景
效果图:
自定义ViewGroup步骤
自定义viewgroup步骤:
(1)继承ViewGroup重写构造方法。
(2)重写onMeasure方法在内部做子view的测量并根据子view的尺寸确定自己的尺寸。
(3)重写onLayout方法根据onMeasure方法测量的尺寸确定所有子view的位置。
(4)重写generateLayoutParams方法并返回需要绑定的LayoutParams对象。
代码分析
接下来我们就根据上面的步骤来看一下我们今天分享的自定义流式布局FlowLayout
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int childCount = getChildCount();
//记录控件的总宽度和总高度
int width = 0;
int height = 0;
//记录每一行的宽度和高度
int lineWidth = 0;
int lineHeight = 0;
for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);
//测量子View的宽高
measureChild(childView, widthMeasureSpec, heightMeasureSpec);
//得到layoutParams
MarginLayoutParams lp = (MarginLayoutParams) childView.getLayoutParams();
//获取子view的宽度
int childWidth = childView.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
//获取子view的高度
int childHeight = childView.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
if (lineWidth + childWidth > widthSize-getPaddingLeft()-getPaddingRight()) {
//换行
width = Math.max(width, lineWidth);
//重置行宽开辟一个新行
lineWidth = childWidth;
//记录行高
height += lineHeight;
lineHeight = childHeight;
} else {//不换行
lineWidth += childWidth;
lineHeight = Math.max(lineHeight, childHeight);
}
if (i == childCount - 1) {
//对比最后一行的宽度与记录的宽度
width = Math.max(width, lineWidth);
//加上最后一行的高度
height += lineHeight;
}
}
setMeasuredDimension(widthMode == MeasureSpec.AT_MOST ? width+getPaddingLeft()+getPaddingRight() : widthSize, heightMode == MeasureSpec.AT_MOST ? height+getPaddingTop()+getPaddingBottom() : heightSize);
}
上面这一步分代码是自定义ViewGroup很重要的方法,首先我们要获取到viewgroup中子view的个数然后遍历分别测量子view的尺寸,然后通过子view的尺寸确认ViewGroup的尺寸。并且我们这个ViewGroup即支持padding又支持margin,上面这一段代码注释的都比较详细我们在这里就不做过多的解释了。
有一点我需要强调一下就是测量模式的问题:如果获取的测量模式是MeasureSpec.AT_MOST 说明我们ViewGroup的宽高设置的是wrap_content,那么我们需要通过计算所有子view的宽高确认ViewGroup的宽高,如果是MeasureSpec.EXACTL模式说明我们的ViewGroup给定的宽高是一个精确的值例如:match_parent或者500dp
那么我们viewGroup的尺寸就是固定的尺寸设置多大就是多大,我们无需通过子view的宽高来确定。
那么我们是怎么支持的margin呢?如下代码
/**
* 指定绑定的layoutparams
*
* @param attrs
* @return
*/
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(), attrs);
}
我们这里必须指定我们支持的layoutParams并通过上面的代码与我们的viewgroup绑定。
接下来我们看一下我们自定义ViewGroup的onLayout方法中的代码
/**
* 内部list为每行的view集合
*/
private List<List<View>> mAllViews = new ArrayList<>();
/**
* 所有行高的集合
*/
private List<Integer> mLineHeights = new ArrayList<>();
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
mAllViews.clear();
mLineHeights.clear();
//获取当前viewgroup的宽度
int width = getWidth();
int lineWidth = 0;
int lineHeight = 0;
List<View> lineViews = new ArrayList<>();
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);
MarginLayoutParams lp = (MarginLayoutParams) childView.getLayoutParams();
int childWidth = childView.getMeasuredWidth();
int childHeight = childView.getMeasuredHeight();
if (lineWidth + childWidth + lp.leftMargin + lp.rightMargin > width-getPaddingLeft()-getPaddingRight()) {
//记录行高
mLineHeights.add(lineHeight);
//记录该行所有view
mAllViews.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(childView);
}
//处理最后一行
mAllViews.add(lineViews);
mLineHeights.add(lineHeight);
//确定子view的位置
int left = getPaddingLeft();
int top = getPaddingTop();
int lineNum = mAllViews.size();
for (int i = 0; i < lineNum; i++) {
//当前行所有的view
lineViews = mAllViews.get(i);
lineHeight = mLineHeights.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清0 top累加行高
left = getPaddingLeft();
top += lineHeight;
}
}
第一个for循环主要是根据是否换行把每一行的View添加到一个list集合中然后添加到总集合mAllViews中,然后在记录每一行的行高并添加到list集合mLineHeights中。接下来就是根据共有多少行然后遍历确认每一个view在ViewGroup中的位置。代码中的注释写的比较清楚我就不再给大家细说了。
使用
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.yesway.flowlayout.MainActivity">
<com.yesway.flowlayout.view.FlowLayout
android:id="@+id/id_flow"
android:padding="30dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</com.yesway.flowlayout.view.FlowLayout>
</LinearLayout>
MainActivity
package com.yesway.flowlayout;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import com.yesway.flowlayout.view.FlowLayout;
public class MainActivity extends AppCompatActivity {
private FlowLayout mFlowLayout;
private String[] datas = new String[]{
"guojingbu shi", "baijun yu", "wei zeng", "zhao xue wei", "jin yong chang"
, "gaven", "zhu wen ling hen mei", "xiao wu", "yang", "guo"
};
private LayoutInflater layoutInflater;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
initData();
}
private void initData() {
for (int i = 0; i < datas.length; i++) {
TextView textView = (TextView) layoutInflater.inflate(R.layout.textview, mFlowLayout,false);
textView.setText(datas[i]);
mFlowLayout.addView(textView);
}
}
private void initView() {
mFlowLayout = findViewById(R.id.id_flow);
layoutInflater = LayoutInflater.from(this);
}
}
完整的FlowLayout代码
package com.yesway.flowlayout.view;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import java.util.ArrayList;
import java.util.List;
/**
* @Description
* @Author guojingbu
* @Date 2019/1/30
* @Update
*/
public class FlowLayout extends ViewGroup {
/**
* 内部list为每行的view集合
*/
private List<List<View>> mAllViews = new ArrayList<>();
/**
* 所有行高的集合
*/
private List<Integer> mLineHeights = new ArrayList<>();
public FlowLayout(Context context) {
this(context, null);
}
public FlowLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int childCount = getChildCount();
//记录控件的总宽度和总高度
int width = 0;
int height = 0;
//记录每一行的宽度和高度
int lineWidth = 0;
int lineHeight = 0;
for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);
//测量子View的宽高
measureChild(childView, widthMeasureSpec, heightMeasureSpec);
//得到layoutParams
MarginLayoutParams lp = (MarginLayoutParams) childView.getLayoutParams();
//获取子view的宽度
int childWidth = childView.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
//获取子view的高度
int childHeight = childView.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
if (lineWidth + childWidth > widthSize-getPaddingLeft()-getPaddingRight()) {
//换行
width = Math.max(width, lineWidth);
//重置行宽开辟一个新行
lineWidth = childWidth;
//记录行高
height += lineHeight;
lineHeight = childHeight;
} else {//不换行
lineWidth += childWidth;
lineHeight = Math.max(lineHeight, childHeight);
}
if (i == childCount - 1) {
//对比最后一行的宽度与记录的宽度
width = Math.max(width, lineWidth);
//加上最后一行的高度
height += lineHeight;
}
}
setMeasuredDimension(widthMode == MeasureSpec.AT_MOST ? width+getPaddingLeft()+getPaddingRight() : widthSize, heightMode == MeasureSpec.AT_MOST ? height+getPaddingTop()+getPaddingBottom() : heightSize);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
mAllViews.clear();
mLineHeights.clear();
//获取当前viewgroup的宽度
int width = getWidth();
int lineWidth = 0;
int lineHeight = 0;
List<View> lineViews = new ArrayList<>();
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);
MarginLayoutParams lp = (MarginLayoutParams) childView.getLayoutParams();
int childWidth = childView.getMeasuredWidth();
int childHeight = childView.getMeasuredHeight();
if (lineWidth + childWidth + lp.leftMargin + lp.rightMargin > width-getPaddingLeft()-getPaddingRight()) {
//记录行高
mLineHeights.add(lineHeight);
//记录该行所有view
mAllViews.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(childView);
}
//处理最后一行
mAllViews.add(lineViews);
mLineHeights.add(lineHeight);
//确定子view的位置
int left = getPaddingLeft();
int top = getPaddingTop();
int lineNum = mAllViews.size();
for (int i = 0; i < lineNum; i++) {
//当前行所有的view
lineViews = mAllViews.get(i);
lineHeight = mLineHeights.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清0 top累加行高
left = getPaddingLeft();
top += lineHeight;
}
}
/**
* 指定绑定的layoutparams
*
* @param attrs
* @return
*/
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(), attrs);
}
}
以上就是今天我给大家分享的自定义流式布局,由于本人水平有限,如果大家发现有什么错误的地方欢迎大家多多指正。