首页 > 程序开发 > 移动开发 > Android >

Android自定义控件之流布局

2017-07-10

Android自定义控件之流布局。

Android自定义控件之流布局。
看下效果

这里写图片描述

前面铺垫做好之后,让我慢慢给大家讲讲我的实现过程,以及在这个过程中踩到的坑。
好了开始开车,请大家坐好并且系好安全带,我先讲下我实现的思路
1.我们知道流布局,其实就是如果自动换行,也就是我们的内容超过当前行宽,就会换一行,超过的内容在下一行显示。基于这个原则我们在写的时候就需要知道我们什么时候换行,而我们的大量工作就是在算什么时候换行。
2.同样的我们在写的时候,有的时候我们的数据是从后台获取到的,所以我们的布局不应该写死,而是用Adapter设计模式。也就是我们在给我们的组件添加数据的时候和ListView一样通过设置Adapter给设置数据。

一、上面讲了思路,然后我在给大家讲一讲具体的实现方法。
一、创建一个类,让其继承ViewGroup。然后重写里面的构造
二、重写onMeasure方法这个方法的作用是对数据进行测量。先上代码然后再解释

 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //清除一次数据  原因是onMeasure可能走多次
        mChildViews.clear();
        //获取子View的个数
        int childCount = getChildCount();
        //每一行的高度  默认为他的paddingLeft  和 paddingRight
        int lineWidth = getPaddingLeft() + getPaddingRight();
        //获取总的宽度
        int width = MeasureSpec.getSize(widthMeasureSpec);
        //获取默认高度
        int height = getPaddingTop() + getPaddingBottom();
        //每一行最大高度
        int maxHeight = 0;
        //用来存放每一行的子View
        ArrayList childViews = new ArrayList<>();
        mChildViews.add(childViews);
        for (int i = 0; i < childCount; i++) {
            //获取ViewGroup中的子View
            View childView = getChildAt(i);
            //如果子View不可见  就跳出本次循环
            if(childView.getVisibility() ==GONE){
                continue;
            }
            //对子孩子进行测量
            measureChild(childView, widthMeasureSpec, heightMeasureSpec);
            //获取layoutParams  目的为得到Margin
            MarginLayoutParams layoutParams = (MarginLayoutParams) childView.getLayoutParams();

            //判断是否换行
            if ((lineWidth + childView.getMeasuredWidth() + layoutParams.leftMargin + layoutParams.rightMargin) > width) {
                //换行
                height += childView.getMeasuredHeight() + layoutParams.bottomMargin + layoutParams.topMargin;
                lineWidth = getPaddingLeft() + getPaddingRight();
                childViews = new ArrayList<>();
                mChildViews.add(childViews);
            } else {
                //不换行
                lineWidth += childView.getMeasuredWidth() + layoutParams.rightMargin + layoutParams.leftMargin;
                maxHeight = Math.max(childView.getMeasuredHeight() + layoutParams.bottomMargin + layoutParams.topMargin, maxHeight);

            }
            childViews.add(childView);

        }
        height += maxHeight;
        setMeasuredDimension(width, height);
    }

//用来存放所有的子view

1.在上面的代码中我们首先创建两个集合,一个用来存放所有的子View,但是这里我们的集合的返现同样也是一个集合,也就是我们在存所有子View的过程中。就已经将所有的子View按照行进行划分出来

//这里存放所有的子View并且按照行进行划分
 private List> mChildViews = new ArrayList<>();

2.什么时候换行,我们队子View宽挨个往后叠加,如果在叠加过程中宽度超过屏幕宽度,那么我们就进行换行

//判断换行
    if ((lineWidth + childView.getMeasuredWidth() + layoutParams.leftMargin + layoutParams.rightMargin) > width) {

三、重写onLayout进行布局摆放,直接上代码相信你们看了代码就会明白

  @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int left, top = getPaddingTop(), right, bottom;
        for (List mChildView : mChildViews) {
            left = getPaddingLeft();
            for (View view : mChildView) {
                //如果子View不可见  就跳出本次循环
                if(view.getVisibility() ==GONE){
                    continue;
                }

                MarginLayoutParams layoutParams = (MarginLayoutParams) view.getLayoutParams();
                left += layoutParams.leftMargin;
                int childTop = top + layoutParams.bottomMargin;
                right =left + view.getMeasuredWidth();
                bottom = childTop + view.getMeasuredHeight();
                //计算出位置然后计算
                view.layout(left, childTop, right, bottom);
                left += view.getMeasuredWidth();
            }
            MarginLayoutParams layoutParams = (MarginLayoutParams) mChildView.get(0).getLayoutParams();
            top += mChildView.get(0).getMeasuredHeight() + layoutParams.bottomMargin + layoutParams.topMargin;
        }
    }

四、使用Adapter设计模式进行数据添加

  public void setAdapter(TagAdapter adapter) {
        if (adapter == null) {
            return;
        }

        removeAllViews();
        mAdapter = null;
        mAdapter = adapter;

        int childCount = mAdapter.getCount();
        for (int i = 0; i < childCount; i++) {
            View childView = mAdapter.getView(i, this, this);
            addView(childView);
        }

    }

最后我来说说我在写的过程中所踩的坑
1.在我们进行测量的时候,在存放所有子View之前一定要将存放子View的集合清空,原因就是onMeasure会走多次如果,我们不清空存放子View的集合的数据那么会出现问题

  //测量
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //清除一次数据  原因是onMeasure可能走多次
        mChildViews.clear();
        //获取子View的个数
        int childCount = getChildCount();
        ...

2.测量的过程中,一定要对子View进行测量,如果不测量后面就会获取不到子View的宽高。如果不测量你会发现写完之后布局时空白的.

   //对子孩子进行测量
   measureChild(childView, widthMeasureSpec, heightMeasureSpec);

3.对Margin处理
我们在处理Margin的过程中肯定需要在代码中获取Margin。

MarginLayoutParams layoutParams = (MarginLayoutParams) view.getLayoutParams();
 left += layoutParams.leftMargin;

上面是获取Margin的方法,但是你们认为这样写就完了吗?不不,我们在使用这个方法之前需要重写两个方法。

    @Override
    protected LayoutParams generateDefaultLayoutParams() {
        return new MarginLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
    }

    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new MarginLayoutParams(getContext(), attrs);
    }

下面的方法我不做解释,这里我解释一下上面的方法。
先说下上面的方法是在什么时候用呢?他是在我们设置数据的过程中,我们肯定需要子View,但是我们的子View是通过动态创建,并且还获取Margin时调用。
不明白???那好上代码

        mTagLayout.setAdapter(new TagAdapter() {
            @Override
            public int getCount() {
                return mDatas.size();
            }

            @Override
            public View getView(int position,View convertView, ViewGroup parent) {

                TextView mTV = new TextView(MainActivity.this);
                TextView mTV  = (TextView) 
                mTV.setText(mDatas.get(position));
                return mTV  ;
            }
        });

上面是我们在Adapter里面设置数据的时候,我们的TextView不是通过findViewById的方式获取的,而是通过new的方式获取,但是如果你不重写第一个方法,你会发现你在运行的过程中,会报错。 不信你试试。

这就是我的自定义控件之流布局,这里我的一些处理还是有点问题,那就是处理Margin和Padding的时候。后面我会完善并将代码上传Github的。

代码地址https://github.com/GitHubToLiao/TagLayout.git

相关文章
最新文章
热点推荐