一.继承ViewGroup来确定流式布局
/**
* 流式布局的基本步骤,继承ViewGroup
* 如果想要完全自定义布局内控件的排列方式,就直接继承与ViewGroup
* 继承ViewGroup,必须要重写onLayout
*/
public class FlowView extends ViewGroup{
public FlowView(Context context) {
super(context);
}
public FlowView(Context context, AttributeSet attrs) {
super(context, attrs);
}
//生成默认的布局参数的方法
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(),attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
int modeHeight = MeasureSpec.getMode(heightMeasureSpec);
//只有定义成wrap_content才需要计算尺寸
//声明变量记录流式布局最后的宽高
int width = 0;
int height = 0;
//记录每一行的宽度和高度的值
int lineHeight = 0;
int lineWidth = 0;
//获取当前布局当中子view的格式
int count = getChildCount();
for (int i = 0; i < count; i++) {
View child = getChildAt(i); //获取指定角标的子view
//计算子view的宽度和高度
measureChild(child,widthMeasureSpec,heightMeasureSpec);
//得到子view的外边框的参数对象
MarginLayoutParams mp = (MarginLayoutParams) child.getLayoutParams();
//获取子view所占据的宽度和高度
int childHeight = child.getMeasuredHeight()+mp.topMargin+mp.bottomMargin;
int childWidht = child.getMeasuredWidth()+mp.leftMargin+mp.rightMargin;
/**判断当前遍历到的view需不需要换行,如果换行就能要行数+1
* 换行的依据
* 如果当前已经放入的内容的宽度+即将放入的view的宽度>已知的行的宽度-内边距
* 就需要换行了。
* */
if (lineWidth+childWidht>sizeWidth-getPaddingRight()-getPaddingLeft()){
//换行
//1.得到当前几行当中最大的宽度,存储起来,作为流失布局的宽度
width = Math.max(width,lineWidth);
//2.新开启一行,行宽发生变化
lineWidth = childWidht;
//3.叠加新的一行的高度
height+=lineHeight;
//4.新开启一行之后,新行的高度就是新放入的view的高度
lineHeight = childHeight;
}else {
//不用换行
lineWidth+=childWidht; //当前行宽度增加了
lineHeight = Math.max(lineHeight,childHeight);
}
//分析发现,最后一行没有添加到高度中,也没有计算最后一行的宽度和目前宽度的大小
//保证最后一行只加一次
if (i==count-1){ //判断是为了确保只加一次
height+=lineHeight;
width = Math.max(width,lineWidth);
}
}
//只有设置成wrap_content时需要计算,设置成精确值时,给多大就多大
setMeasuredDimension((modeWidth==MeasureSpec.EXACTLY)?sizeWidth:width+getPaddingLeft()+getPaddingRight(),
(modeHeight==MeasureSpec.EXACTLY)?sizeHeight:height+getPaddingTop()+getPaddingBottom());
}
/**
* onLayout :在此方法中去排列子控件
* changed :
* */
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
//已每一行作为单位把子view放置到容器当中
List<List<View>>mAllViews = new ArrayList<List<View>>();
//记录每一行的高度
List<Integer>mLineHeight = new ArrayList<Integer>();
//获取当前布局的宽度
int width = getWidth();
//记录每一行的宽度和高度
int lineHeight = 0;
int lineWidth = 0;
//记录每一行的view
List<View>mLineViews = new ArrayList<>();
//获取子view的个数
int count = getChildCount();
for (int i = 0; i < count; i++) {
//拿到每一个子view对象
View child = getChildAt(i);
//获取子view外边框的参数
MarginLayoutParams mp = (MarginLayoutParams) child.getLayoutParams();
//能够获取子控件存在的宽,高
int childHeight = child.getMeasuredHeight()+mp.topMargin+mp.bottomMargin;
int childWidth = child.getMeasuredWidth()+mp.leftMargin+mp.rightMargin;
if (childWidth+lineWidth>width-getPaddingRight()-getPaddingLeft()){
//做换行的工作
mLineHeight.add(lineHeight);
//记录当前行的高度,直到了当前行的高度,就能直到放入此行的view距离top有多大
//添加这一行的view到总容器当中
mAllViews.add(mLineViews);
//新开启一行
lineWidth = 0;
lineHeight = 0;
//更新记录,创建一个新的容器对象,存放换行之后的view的对象
mLineViews = new ArrayList<>();
}
//不换行做的操作和换行之后所做的操作是一致的
//叠加view的宽度到原来行中
lineWidth+=childWidth;
//和之前此行中的其他view比较,取出最高的一个,作为行的高度
lineHeight = Math.max(lineHeight,childHeight);
//把当前的view添加到存储此行的容器当中
mLineViews.add(child);
}
/**
* 分析发现: 最后一行的高度没有添加到高度集合中
* 最后一行存放view的容器也没有添加到总容器当中
* */
mLineHeight.add(lineHeight);
mAllViews.add(mLineViews);
//刚才的工作,是为了给容器当中的控件,按照行的方式进行分组
//LIST<LIST>,其中的泛型,就是分组的结果, list<list>的长度代表行数
//获取父布局的padding
int left = getPaddingLeft();
int top = getPaddingTop();
//按照行的方式遍历view
for (int i = 0; i < mAllViews.size(); i++) {
mLineViews = mAllViews.get(i); //拿到存放了每一行view的容器
//拿到每一行的高度
lineHeight = mLineHeight.get(i);
//遍历每一行当中的view进行排列
for (int j = 0; j < mLineViews.size(); j++) {
//拿到这一行当中的所有的view
View child = mLineViews.get(j);
MarginLayoutParams mp = (MarginLayoutParams) child.getLayoutParams();
//知道view的宽度,边距,此行的高度,就可以了解到view放置的左上右下
int lf = left+mp.leftMargin;
int tp = top+mp.topMargin;
int rg = lf+child.getMeasuredWidth();
int bm = tp+child.getMeasuredHeight();
//向布局当中添加view
child.layout(lf,tp,rg,bm);
left+=child.getMeasuredWidth()+mp.leftMargin+mp.rightMargin;
}
//换行
left = getPaddingLeft();
top+=lineHeight;
}
}
}
二.在layout文件里面创建flow_item.xml文件里面写TextView的基本的view
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp"
android:textColor="@color/colorPrimary"
android:background="@drawable/item_bg"
android:layout_margin="5dp">
</TextView>
三.在Activtity布局文件中中添加自定义控件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_custom09"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.qianfeng.customviewrtest.demo09.CustomActivity09">
<com.qianfeng.customviewrtest.demo09.FlowView
android:id="@+id/flow"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#c0c0c0">
</com.qianfeng.customviewrtest.demo09.FlowView>
</RelativeLayout>
四.在Activity中实例化自定义控件,将添加了文字的textView用mFlowView.addView(tv)方法写入流式图中
public class CustomActivity09 extends AppCompatActivity {
private FlowView mFlowView;
private String[]items = {"飞狐外传","雪山飞狐","连城决","天龙八部","射雕英雄传","白马啸西风","鹿鼎记","笑傲江湖","书剑恩仇录","神雕侠侣",
"倚天屠龙记","碧血剑","鸳鸯刀","疯狂JAVA讲义","第一行代码","安卓艺术鉴赏","安卓群英传",
"三国\n演义","水浒传","红楼梦","西游记"};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_custom09);
mFlowView = (FlowView) findViewById(R.id.flow);
initView();
}
private void initView(){
for (int i = 0; i < items.length; i++) {
TextView tv = (TextView) LayoutInflater.from(this).inflate(R.layout.flow_item, mFlowView, false);
tv.setText(items[i]);
mFlowView.addView(tv);
}
}
}