1. 说明
基于前边3节课我们分析的View的绘制流程,那么这节课我们就来写一个自定义ViewGroup,来实现一个流式布局,当然可以使用TagLayout,但是我们来使用 Adapter设计模式来实现,效果图如下:
2. 思路分析
2.1>:onMeasure()指定宽高
1>:for循环测量子View宽高;
2>:然后根据子View的宽高来计算自己宽高(即就是父布局的宽高)
2.2>:onLayout()摆放
1>:for循环摆放所有的子View
2.3>:onDraw()方法不需要了
如果要绘制背景background的话,就不需要onDraw()方法了,因为View、ViewGroup都自带background背景了,不需要我们自己绘制;
知道为什么权重 weight、gravity、layout_gravity等属性只能在 LinearLayout中用,而不能在RelativeLayout中用。因为RelativeLayout没有定义 这几个属性;
同样的,layout_centerInParent、layout_centerHorizontal、layout_centerVertical这个属性只有在RetiveLayout中才定义的,LinearLayout没有定义;
3. 代码实现如下
自定义流式布局 TagLayout如下:
/**
* Email: 2185134304@qq.com
* Created by JackChen 2018/3/24 18:42
* Version 1.0
* Params:
* Description: 流式布局
*/
public class TagLayout extends ViewGroup {
private List<List<View>> mChildViews = new ArrayList<>() ;
private BaseAdapter mAdapter ;
public TagLayout(Context context) {
super(context);
}
public TagLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public TagLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
// 2.1 onMeasure()指定宽高
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 清空mChildViews集合
mChildViews.clear();
// 获取所有子孩子
int childCount = getChildCount();
// 获取到的宽度
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = getPaddingTop() + getPaddingBottom() ;
// 一行的宽度
int lineWidth = getPaddingLeft() ;
// 把不换行的时候的 childView 添加到 childViews这个集合中
ArrayList<View> childViews = new ArrayList<>() ;
mChildViews.add(childViews) ;
// 子View 高度不一致的情况下
int maxHeight = 0 ;
for (int i = 0; i < childCount; i++) {
// 2.1.1 for循环测量子View
View childView = getChildAt(i) ;
if (childView.getVisibility() == View.GONE){
// 结束本次循环
continue;
}
// 这段话执行完毕之后就可以获取子View的宽高,因为会调用子View的 onMeasure()
measureChild(childView,widthMeasureSpec,heightMeasureSpec);
// margin值 ViewGroup.LayoutParams没有margin值 ,
// 这个时候就需要想一下LinearLayout为什么有margin值?
// 因为LinearLayout有自己的 LayoutParams
ViewGroup.MarginLayoutParams params = (MarginLayoutParams) childView.getLayoutParams();
// 什么时候需要换行 ,肯定是一行不够的情况下需要换行 ,还需要考虑margin
if (lineWidth + (childView.getMeasuredWidth() + params.leftMargin + params.rightMargin) > width){
// 换行,累加高度
height += childView.getMeasuredHeight() + params.topMargin + params.bottomMargin;
lineWidth = childView.getMeasuredWidth() + params.leftMargin + params.rightMargin ;
// 需要换行的时候 , 就把childViews 添加到最外层总的集合中
childViews = new ArrayList<>() ;
mChildViews.add(childViews) ;
}else{
// 不换行,累加宽度
lineWidth += childView.getMeasuredWidth() + params.leftMargin + params.rightMargin ;
// 不需要换行的时候,就把子View添加到 集合childViews中
childViews.add(childView) ;
maxHeight = Math.max(childView.getMeasuredHeight() + params.topMargin + params.bottomMargin ,maxHeight) ;
}
childViews.add(childView) ;
}
height += maxHeight ;
// 2.1.2 根据子View 计算和指定自己的高度
setMeasuredDimension(width , height);
}
@Override
protected LayoutParams generateLayoutParams(LayoutParams p) {
return super.generateLayoutParams(p);
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext() , attrs);
}
/**
* 用于摆放 所有 子View
* @param changed
* @param l
* @param t
* @param r
* @param b
*/
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// 获取所有子孩子
int childCount = getChildCount();
int left , top = getPaddingTop() , right , bottom ;
for (List<View> childViews : mChildViews) {
left = getPaddingLeft() ;
int maxHeight = 0 ;
for (View childView : childViews) {
if (childView.getVisibility() == View.GONE){
// 结束本次循环
continue;
}
ViewGroup.MarginLayoutParams params = (MarginLayoutParams) childView.getLayoutParams();
left += params.leftMargin ;
int childTop = top + params.topMargin ;
right = left + childView.getMeasuredWidth() ;
bottom = childTop + childView.getMeasuredHeight() ;
// 摆放子View
childView.layout(left , childTop , right , bottom);
// left叠加
left += childView.getMeasuredWidth() + params.rightMargin;
}
// 不断的叠加top值
ViewGroup.MarginLayoutParams params = (MarginLayoutParams) childViews.get(0).getLayoutParams();
top += childViews.get(0).getMeasuredHeight() + params.topMargin + params.bottomMargin;
}
}
public void setAdapter(BaseAdapter adapter){
if (adapter == null){
// 抛空指针异常
throw new NullPointerException("空指针异常") ;
}
// 清空里边所有的子View ,防止多次 setAdapter
removeAllViews();
mAdapter = null ;
mAdapter = adapter ;
// 获取数量
int childCount = mAdapter.getCount() ;
for (int i = 0; i < childCount; i++) {
// 通过位置获取View
// this表示当前ViewGroup是谁
View childView = mAdapter.getView(i, this);
addView(childView);
}
}
}
activity_main布局文件如下:
<?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"
android:orientation="vertical"
tools:context="com.jackchen.view_day09_2.MainActivity">
<com.jackchen.view_day09_2.TagLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/tag_layout"
>
<!--<TextView-->
<!--android:layout_width="wrap_content"-->
<!--android:layout_height="wrap_content"-->
<!--android:layout_margin="10dp"-->
<!--android:text="Hello World! Hello World!" />-->
<!--<TextView-->
<!--android:layout_margin="10dp"-->
<!--android:layout_width="wrap_content"-->
<!--android:layout_height="wrap_content"-->
<!--android:text="TAG" />-->
<!--<TextView-->
<!--android:layout_width="wrap_content"-->
<!--android:layout_height="wrap_content"-->
<!--android:layout_margin="10dp"-->
<!--android:text="TAG" />-->
<!--<TextView-->
<!--android:layout_width="wrap_content"-->
<!--android:layout_height="wrap_content"-->
<!--android:layout_margin="10dp"-->
<!--android:text="Hello World!" />-->
<!--<TextView-->
<!--android:layout_width="wrap_content"-->
<!--android:layout_height="wrap_content"-->
<!--android:layout_margin="10dp"-->
<!--android:text="Hello World! Hello World!" />-->
<!--<TextView-->
<!--android:layout_width="wrap_content"-->
<!--android:layout_height="wrap_content"-->
<!--android:layout_margin="10dp"-->
<!--android:text="Hello World!" />-->
<!--<TextView-->
<!--android:layout_width="wrap_content"-->
<!--android:layout_height="wrap_content"-->
<!--android:layout_margin="10dp"-->
<!--android:text="TAG" />-->
<!--<TextView-->
<!--android:layout_width="wrap_content"-->
<!--android:layout_height="wrap_content"-->
<!--android:layout_margin="10dp"-->
<!--android:text="TAG" />-->
<!--<TextView-->
<!--android:layout_width="wrap_content"-->
<!--android:layout_height="wrap_content"-->
<!--android:layout_margin="10dp"-->
<!--android:text="Hello World!" />-->
<!--<TextView-->
<!--android:layout_width="wrap_content"-->
<!--android:layout_height="wrap_content"-->
<!--android:text="1111"-->
<!--/>-->
<!--<TextView-->
<!--android:layout_width="wrap_content"-->
<!--android:layout_height="wrap_content"-->
<!--android:text="111111111111"-->
<!--/>-->
<!--<TextView-->
<!--android:layout_width="wrap_content"-->
<!--android:layout_height="wrap_content"-->
<!--android:text="11111111111111111111"-->
<!--/>-->
<!--<TextView-->
<!--android:layout_width="wrap_content"-->
<!--android:layout_height="wrap_content"-->
<!--android:text="1111"-->
<!--/>-->
<!--<TextView-->
<!--android:layout_width="wrap_content"-->
<!--android:layout_height="wrap_content"-->
<!--android:text="111111111111"-->
<!--/>-->
<!--<TextView-->
<!--android:layout_width="wrap_content"-->
<!--android:layout_height="wrap_content"-->
<!--android:text="11111111111111111111"-->
<!--/>-->
</com.jackchen.view_day09_2.TagLayout>
</LinearLayout>
流式布局的adapter如下:
/**
* Email: 2185134304@qq.com
* Created by JackChen 2018/3/25 7:12
* Version 1.0
* Params:
* Description: 流式布局的adapter
*/
public abstract class BaseAdapter {
// 1. 有多少个条目
public abstract int getCount() ;
// 2. getView通过position
public abstract View getView(int position , ViewGroup parent) ;
// 3. 观察者模式及时通知更新
public void registerDataSetObserver(DataSetObserver observer){
}
public void unRegisterDataSetObserver(DataSetObserver observer){
}
}
在MainActivity中给 流式布局 的 Adapter 设置数据:
public class MainActivity extends AppCompatActivity {
private TagLayout tag_layout;
private List<String> mItems ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tag_layout = (TagLayout) findViewById(R.id.tag_layout);
// 真正开发中,所有数据肯定都是从后台获取的
mItems = new ArrayList<>() ;
mItems.add("1111111");
mItems.add("11");
mItems.add("1111");
mItems.add("1111");
mItems.add("11");
mItems.add("1111");
mItems.add("1111111");
mItems.add("1111111");
mItems.add("11");
mItems.add("1111");
mItems.add("1111");
mItems.add("11");
mItems.add("1111");
mItems.add("1111111");
tag_layout.setAdapter(new BaseAdapter() {
@Override
public int getCount() {
return mItems.size();
}
@Override
public View getView(final int position, ViewGroup parent) {
TextView textView = (TextView) LayoutInflater.from(MainActivity.this).inflate(R.layout.item_tag, parent, false);
textView.setText(mItems.get(position));
textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this , "点击了标签了"+position , Toast.LENGTH_SHORT).show();
}
});
return textView;
}
});
}
}
4. 注意
注意1:
流式布局中所有子View的数据一般都是从服务器获取的,在这里为了方便测试,采用两种方式来获取子View的数据:
第一:直接在activity_main布局文件中,在自定义的TagLayout中写多个 TextView用于测试;
第二:在MainActivity中写一个 List集合,用于存储 所有子View的数据;
注意2:
如果数据是从List集合中获取的话,那么给adapter设置数据时可以参考 ListView中 BaseAdapter的写法,注意这里是 写一个 抽象的 BaseAdapter,这里只需要2个方法,getCount()获取 总共item的个数、getView()根据位置获取对应的 控件,重点看下BaseAdapter是怎样实现的:
/**
* Email: 2185134304@qq.com
* Created by JackChen 2018/3/25 7:12
* Version 1.0
* Params:
* Description: 流式布局的adapter
*/
public abstract class BaseAdapter {
// 1. 有多少个条目
public abstract int getCount() ;
// 2. getView通过position
public abstract View getView(int position , ViewGroup parent) ;
// 3. 观察者模式及时通知更新
public void registerDataSetObserver(DataSetObserver observer){
}
public void unRegisterDataSetObserver(DataSetObserver observer){
}
}
MainActiivty中设置数据,及点击事件处理方式如下:
public class MainActivity extends AppCompatActivity {
private TagLayout tag_layout;
private List<String> mItems ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tag_layout = (TagLayout) findViewById(R.id.tag_layout);
// 真正开发中,所有数据肯定都是从后台获取的
mItems = new ArrayList<>() ;
mItems.add("1111111");
mItems.add("11");
mItems.add("1111");
mItems.add("1111");
mItems.add("11");
mItems.add("1111");
mItems.add("1111111");
mItems.add("1111111");
mItems.add("11");
mItems.add("1111");
mItems.add("1111");
mItems.add("11");
mItems.add("1111");
mItems.add("1111111");
tag_layout.setAdapter(new BaseAdapter() {
@Override
public int getCount() {
return mItems.size();
}
@Override
public View getView(final int position, ViewGroup parent) {
TextView textView = (TextView) LayoutInflater.from(MainActivity.this).inflate(R.layout.item_tag, parent, false);
textView.setText(mItems.get(position));
textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this , "点击了标签了"+position , Toast.LENGTH_SHORT).show();
}
});
return textView;
}
});
}
}
代码已上传至github:
https://github.com/shuai999/View_day9_2.git