使用Adapter设计模式打造一个流式布局FlowLayout

来源:互联网 发布:软件开发工作计划范文 编辑:程序博客网 时间:2024/06/08 05:42

流式布局可以说是在各种软件中的出场率都很高的一个布局方式,被广泛使用,像一些关键字搜索,标签等等的场景,更是随处可见,今天我们就来手把手打造一个FlowLayout。
FlowLayout由于是以一个容器的身份存在的,所以其需要继承的是ViewGroup而不是View,也就是说,我们今天所要做的,就是去自定义一个ViewGroup。
自定义ViewGroup和自定义View的套路基本都是一致的,但也有部分差异,下面,我们先来说说自定义ViewGroup的套路的几个关键点:

  1. 自定义属性(这个就不说了,在之前的文章中已经说了很多次了)
  2. 实现onMeasure()方法,通过测量每一个子View的宽高,来计算自身的宽高,最后达到测量自身宽高的目的。
  3. 实现onDraw()方法,默然情况下继承ViewGroup是不会调用该方法的,如果需要绘制一些界面,可以实现dispatchDraw()方法
  4. 实现onLayout()方法,该方法用于按照自己的想法来摆放可见(不为GONE)的子View。
  5. 注意点:在确定要自定义ViewGroup的时候,可以先考虑下先继承自一些已经封装好的ViewGroup,如LinearLayout等进行开发。

好了,下面,我们开始自定义FlowLayout
首先,自定义属性这一块,我们就直接忽略了,直接从onMeasure方法开始做起(判断view.visibility != GONE,之前忘加了):

/**     * 测量该控件的宽高     * 思路:通过测量每一个子view的宽高   来得到该layout的整体宽高     * @param widthMeasureSpec     * @param heightMeasureSpec     */    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        /**         * 解决onMeasure被调用多次,导致在摆放子View的时候出现多次摆放的情况         */        mChildViews.clear();        //获取子View的数量        int childCount = getChildCount();        //获取该layout的宽度   以方便控制后面TAG的摆放 和计算该Layout的高度        int width = MeasureSpec.getSize(widthMeasureSpec);        //初始化layout的高度        int height = getPaddingTop() + getPaddingBottom();        int lineWidth = getPaddingLeft();        //初始化一个每一行的高度  计算layout的总高度时,取每一行的最高        int maxHeight = 0;        ArrayList<View> views = new ArrayList<>();        mChildViews.add(views);        //for循环测量子View        for(int i = 0;i < childCount; i++){            View childView = getChildAt(i);            measureChild(childView,widthMeasureSpec,heightMeasureSpec);            ViewGroup.MarginLayoutParams params = (MarginLayoutParams) childView.getLayoutParams();            //当叠加的宽度大于该Layout的总宽度时,则换行            if((lineWidth + childView.getMeasuredWidth() + params.leftMargin + params.rightMargin) > width){                maxHeight = Math.max(maxHeight,childView.getMeasuredHeight() + params.topMargin + params.bottomMargin);                height += maxHeight;                lineWidth = getPaddingLeft() + childView.getMeasuredWidth() + params.leftMargin + params.rightMargin;                views = new ArrayList<>();                mChildViews.add(views);                views.add(childView);            }else{                maxHeight = Math.max(maxHeight,childView.getMeasuredHeight() + params.topMargin + params.bottomMargin);                lineWidth += childView.getMeasuredWidth() + params.leftMargin + params.rightMargin;                views.add(childView);            }        }        height += maxHeight;        setMeasuredDimension(width,height);    }

在计算宽度时,我们需要获取到每个子View的margin值,但是由于ViewGroup本身是没有LayoutParams的,所以这里模仿了下LinearLayout的方法,来获取MarginLayoutParams:

/**     * 重写ViewGroup的该方法   获取到margin值(可以模仿LinearLayout)     * @param attrs     * @returnop     */    @Override    public LayoutParams generateLayoutParams(AttributeSet attrs) {        return new MarginLayoutParams(getContext(),attrs);    }

在onMeasure方法中,当几个子View的宽度总和大于FlowLayout的宽度时,这个时候就需要换行。其他的,注释应该都有了!

下一步,就是实现onLayout()方法,来摆放所有的子View(判断view.visibility != GONE,之前忘加了)

“`
/**
* 摆放子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 maxHeight,left,right,top,bottom;
top = getPaddingTop();

    //循环遍历mChildViews    for(ArrayList<View> views : mChildViews){        left = getPaddingLeft();        maxHeight = 0;        for(int i = 0;i<views.size();i++){            View childView = views.get(i);            ViewGroup.MarginLayoutParams marginLayoutParams = (MarginLayoutParams) childView.getLayoutParams();            maxHeight = Math.max(maxHeight,childView.getMeasuredHeight() + marginLayoutParams.topMargin + marginLayoutParams.bottomMargin);            left += marginLayoutParams.leftMargin;            right = left + childView.getMeasuredWidth();            int childTop = top + marginLayoutParams.topMargin;            bottom = childTop + childView.getMeasuredHeight();            childView.layout(left,childTop,right,bottom);            left += marginLayoutParams.rightMargin + childView.getMeasuredWidth();        }        top += maxHeight;    }}

到这里呢,基本是这个流式布局就完成了一大半了,下面要做的,就是往这个布局里面扔数据,测试其是否会按照我们既定的规则摆放。

在设置数据这一步,我们使用了Adapter的设计模式。下面来看具体的步骤:
1:定义一个抽象的BaseAdapter

/** * Created by DELL on 2017/9/9. * Description : */public abstract class BaseAdapter {    //获取view的数量    public abstract int getCount();    //获取View    public abstract View getView(int position, ViewGroup parent);}

这边暂时只定义了两个方法,一个是获取子View数量的,另外一个是获取到每一个子View的,暂时没有定义notifySetDataChanged等方法,这个可以根据自己的需求,给其设置一个观察者来处理。

下一步,需要在FloawLayout中去设置Adapter达到添加布局的效果

public void setAdapter(BaseAdapter adapter){        //当Adapter为空的时候,抛出异常        if(adapter == null){            throw new NullPointerException("adapter not null");        }        this.mAdapter = adapter;        int count = mAdapter.getCount();        for(int i = 0;i<count;i++){            View view = mAdapter.getView(i,this);            addView(view);        }    }

每一个子View的布局:

<?xml version="1.0" encoding="utf-8"?><TextView xmlns:android="http://schemas.android.com/apk/res/android"          android:id="@+id/item_textview"          android:layout_width="wrap_content"          android:layout_height="wrap_content"          android:background="@drawable/bg_textview"          android:padding="10dp"          android:layout_margin="5dp"    ></TextView>

子view背景代码:

<?xml version="1.0" encoding="utf-8"?><shape xmlns:android="http://schemas.android.com/apk/res/android"    android:shape="rectangle">    <stroke android:color="@color/colorAccent" android:width="1dp"/>    <corners android:radius="8dp"/></shape>

下面,在Activity中设置Adapter,来测试下效果:

public class MainActivity extends AppCompatActivity {    private FlowLayout mFlowLayout;    private ArrayList<String> mStrList;    private LayoutInflater mLayoutInflater;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        initData();        initView();    }    private void initData(){        mStrList = new ArrayList<>();        mStrList.add("JUSTH HELLO!");        mStrList.add("JUSTH HEJUSTH HELLO!LLO!");        mStrList.add("JUSTH LLO!");        mStrList.add("JUSTH HEJUSTH HELLO!LLO!");        mStrList.add("JUSTH HELLO!");        mStrList.add("JUSTLO!");        mStrList.add("JUSTH HELLO!");        mStrList.add("JUSTH HEJUSTH HELLO!LLO!");        mStrList.add("JUSTH HELO!");        mStrList.add("JUSTH HELLO!");    }    private void initView() {        mFlowLayout = (FlowLayout) findViewById(R.id.taglayout);        mLayoutInflater = LayoutInflater.from(this);        mFlowLayout.setAdapter(new BaseAdapter() {            @Override            public int getCount() {                return mStrList.size();            }            @Override            public View getView(int position, ViewGroup parent) {                View view = mLayoutInflater.inflate(R.layout.layout_textview,parent,false);                ((TextView)view.findViewById(R.id.item_textview)).setText(mStrList.get(position));                return view;            }        });    }}

最后,来一波效果图吧:

这里写图片描述