Android中常见的流式布局的使用

Android中常见的自定义FlowLayout流式布局的使用

在日常的app使用中,我们会在Android 的app中看见,比如淘宝购物页面尺寸的选取,脉脉和慕课技术职位的选取等等热门标签自动换行的流式布局,今天,我们就来看看如何自定义一个类似热门标签那样的流式布局吧,老规矩,直接上效果图

Android中常见的流式布局的使用_第1张图片

概述

1.流式布局原理:

在布局内,随意摆放任意个view,每行所摆放的view个数,根据实施计算出来的宽度,一旦当前要摆放的view宽度和之前摆放的所有view宽度加在一起,超过了布局的宽度,那么就把该view换行摆放

2.应用场景:

一般,像这种流式布局会应用在一些热门标签,热门推荐之类的应用上

3.测量模式:

谈到FlowLayout流式布局,不得不提及他的测量模式:

* MeasureSpec.EXACTLY:精确模式, eg:100dp,match_parent.(明确指出)
* MeasureSpec.AT_MOST: 至多模式, view最多可以获得的宽高值,它需要计算所有包含的子view的宽高,最后计算出来的宽高总和值,eg:wrap_content.
* UNSPECIFIED:未指定模式,想设置多宽多高,就给你多宽多高,一般的控件不会指定这种模式,但也存在,这种模式用的不多。eg:scrollview的宽高测量,就是使用的此种模式

4.在我们的流式布局内,应该怎么设置布局的宽高呢? onMeasure()

1:如果布局指定的宽是match_parent或者精确的宽度值,那么直接就可以从父控件传入的测量规格中直接获取布局宽度,高度同理.

2:如果布局指定的宽高不是EXACTLY,而是AT_MOST,那么这时候,就需要计算每一个子view的宽高,来决定布局的宽高了。

宽度:摆放的所有子view占据宽度最多的一行,作为布局宽度。

高度:摆放的所有子view总共占据几行的高度总和。

5.子View的布局方式: onLayout()

使用onLayout():设置ViewGroup内包含的所有子view的位置;
获取到每一行的每一个子view,计算出它的left,top,right,bottom,调用layout方法设置其在流式布局当中的位置。

  • 宽度=子view最多的那行的宽度=那一行每一个子view的宽度+leftMargin+rightMargin;

  • 高度=所有行的高度 = 每一行的高度+topMargin+bottomMargin;

LayoutParams参数的设置

ViewGroup LayoutParams :每个 ViewGroup 对应一个 LayoutParams; 即 ViewGroup -> LayoutParams
getLayoutParams 不知道转为哪个对应的LayoutParams ,其实很简单,就是如下:
子View.getLayoutParams 得到的LayoutParams对应的就是 子View所在的父控件的LayoutParams;
例如,LinearLayout 里面的子view.getLayoutParams ->LinearLayout.LayoutParams
所以 咱们的FlowLayout 也需要一个LayoutParams,由于上面的效果图是子View的 margin,
所以应该使用MarginLayoutParams。即FlowLayout->MarginLayoutParams

自定义ViewGroup的实现流式布局

根据上面的技术分析,自定义类继承于ViewGroup,并重写 onMeasure和onLayout等方法。具体实现代码如下:

package com.example.administrator.p2pinvest.ui;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;

import java.util.ArrayList;
import java.util.List;
//自定义ViewGroup实现
public class FlowLayout extends ViewGroup {
    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);
    }

    //布局:给每一个子view布局,childView.layout(l,t,r,b)
    private List allHeights = new ArrayList<>();//集合中的元素:记录每一行的高度
    private List> allViews = new ArrayList<>();//外层集合中的元素:由每行元素构成的集合

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int width = this.getWidth();//得到父视图的宽度

        int lineWidth = 0;
        int lineHeight = 0;

        // 一、给集合元素赋值
        int childCount = getChildCount();
        List lineList = new ArrayList<>();//一行元素构成的集合
        for (int i = 0; i < childCount; i++) {
            View childView = getChildAt(i);
            //子视图的宽高
            int childWidth = childView.getMeasuredWidth();
            int childHeight = childView.getMeasuredHeight();
            //获取视图的边距
            MarginLayoutParams mp = (MarginLayoutParams) childView.getLayoutParams();
            if (lineWidth + childWidth + mp.leftMargin + mp.rightMargin < width) {//不换行
                lineList.add(childView);//添加子视图到集合中
                lineWidth += childWidth + mp.leftMargin + mp.rightMargin;
                lineHeight = Math.max(lineHeight, childHeight + mp.topMargin + mp.bottomMargin);
            } else {//换行
                allViews.add(lineList);
                allHeights.add(lineHeight);

                //换行以后需要执行的情况
                lineList = new ArrayList<>();
                lineList.add(childView);
                lineWidth = childWidth + mp.leftMargin + mp.rightMargin;
                lineHeight = childHeight + mp.topMargin + mp.bottomMargin;
            }

            if (i == childCount - 1) {//如果最后一个元素
                allViews.add(lineList);
                allHeights.add(lineHeight);
            }
        }


        Log.e("TAG", "allViews.size()==" + allViews.size() + "allHeights.size()==" + allHeights.size());

        //二、遍历集合元素,调用元素的layout()

        int x = 0;
        int y = 0;

        for (int i = 0; i < allViews.size(); i++) {
            List lineViews = allViews.get(i);//获取每一行的集合
            for (int j = 0; j < lineViews.size(); j++) {
                View childView = lineViews.get(j);//获取一行的指定的j位置

                MarginLayoutParams mp = (MarginLayoutParams) childView.getLayoutParams();
                //计算的到left,top,right,bottom
                int left = x + mp.leftMargin;
                int top = y + mp.topMargin;
                int right = left + childView.getMeasuredWidth();
                int bottom = top + childView.getMeasuredHeight();

                childView.layout(left, top, right, bottom);

                //重新赋值x,y
                x += childView.getMeasuredWidth() + mp.leftMargin + mp.rightMargin;
            }

            //换行
            x = 0;
            y += allHeights.get(i);
        }
    }

    //测量
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        //获取宽度和高度的布局的数值,以及各自的设计模式,精确模式,至多模式
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        //声明当前视图的宽和高,如果是至多模式,需要计算出此两个变量的值
        int width = 0;
        int height = 0;

        //声明每行的宽度和高度
        int lineWidth = 0;
        int lineHeight = 0;

        int childCount = getChildCount();//获取子视图的个数
        for (int i = 0; i < childCount; i++) {
            View childView = getChildAt(i);

            //为了保证能够获取子视图的测量的宽高,需要调下面的方法
            measureChild(childView, widthMeasureSpec, heightMeasureSpec);
            //获取子视图测量的宽高
            int childWidth = childView.getMeasuredWidth();
            int childHeight = childView.getMeasuredHeight();

            //获取视图的边距
            MarginLayoutParams mp = (MarginLayoutParams) childView.getLayoutParams();

            if (lineWidth + childWidth + mp.leftMargin + mp.rightMargin <= widthSize) {//不换行
                lineWidth += childWidth + mp.leftMargin + mp.rightMargin;
                lineHeight = Math.max(lineHeight, childHeight + mp.topMargin + mp.bottomMargin);
            } else {//换行
                width = Math.max(width, lineWidth);
                height += lineHeight;

                //重新赋值
                lineWidth = childWidth + mp.leftMargin + mp.rightMargin;
                lineHeight = childHeight + mp.topMargin + mp.bottomMargin;

            }
            //单独的考虑一下最后一个!因为最后一个元素并没有计算进去
            if (i == childCount - 1) {
                width = Math.max(width, lineWidth);
                height += lineHeight;
            }
        }

        Log.e("TAG", "width ==" + width + ",height==" + height);
        Log.e("TAG", "widthSize ==" + widthSize + ",heightSize==" + heightSize);

        //调用此方法,设置当前布局的宽高
        setMeasuredDimension(widthMode == MeasureSpec.EXACTLY ? widthSize : width,
                heightMode == MeasureSpec.EXACTLY ? heightSize : height);

    }
    //FlowLayout中有了如下的方法,在onMeasure()中可通过child就可以getLayoutParams()
    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        MarginLayoutParams mp = new MarginLayoutParams(getContext(), attrs);
        return mp;

    }

}

在布局文件中加入自定义的flowLayout




    
    

初始化布局使用的是butterknife

 @Bind(R.id.flow_layout)
 FlowLayout flowLayout;

提供页面要显示的数据,这个数据也可以放在服务器中进行联网获取

    private String[] datas = new String[]{"新手计划", "乐享活系列90天计划", "钱包", "30天理财计划(加息2%)",
            "林业局投资商业经营与大捞一笔", "中学老师购买车辆", "屌丝下海经商计划", "新西游影视拍",
            "Java培训老师自己周转", "HelloWorld", "C++-C-ObjectC-java", "Android vs ios", "算法与数据结构", "JNI与NDK", "team working"};
    //初始化随机
    private Random random;

在其他类中直接进行调用即可

    @Override
    public void initData(String content) {

        random = new Random();
        for(int i = 0; i < datas.length; i++) {
            final TextView textView = new TextView(getActivity());
            textView.setText(datas[i]);
            //提供边距的对象,并设置到textView中
            ViewGroup.MarginLayoutParams mp = new ViewGroup.MarginLayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,ViewGroup.LayoutParams.WRAP_CONTENT);
            mp.leftMargin = UIUtils.dp2px(8);
            mp.rightMargin = UIUtils.dp2px(8);
            mp.topMargin = UIUtils.dp2px(8);
            mp.bottomMargin = UIUtils.dp2px(8);
            textView.setLayoutParams(mp);

            //设置背景
            //设置textView的背景
            int red = random.nextInt(211);
            int green = random.nextInt(211);
            int blue = random.nextInt(211);
            //方式一:
//            textView.setBackground(DrawUtils.getDrawable(Color.rgb(red, green, blue),UIUtils.dp2px(5)));

            //方式二:
            //保存按下能显示selector的效果,需要设置一个如下的属性
            textView.setBackground(DrawUtils.getSelector(DrawUtils.getDrawable(Color.rgb(red, green, blue),UIUtils.dp2px(5)),DrawUtils.getDrawable(Color.WHITE,UIUtils.dp2px(5))));
            //方式一:
//            textView.setClickable(true);

            //添加点击事件,也是实现显示selector的效果的一种方式
            textView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Toast.makeText(ProductHotFragment.this.getActivity(), textView.getText(), Toast.LENGTH_SHORT).show();
                }
            });

            //设置边距
            //设置内边距
            int padding = UIUtils.dp2px(10);
            textView.setPadding(padding, padding, padding, padding);


            // 2.添加到FlowLayout布局中
            flowLayout.addView(textView);
        }
    }

用到的工具类

    public class DrawUtils {
    //提供一个指定颜色和圆角半径的Drawable对象
    public static GradientDrawable getDrawable(int rgb,float radius){
        GradientDrawable gradientDrawable = new GradientDrawable();
        gradientDrawable.setColor(rgb);//设置颜色
        gradientDrawable.setGradientType(GradientDrawable.RECTANGLE);//设置显示的样式
        gradientDrawable.setCornerRadius(radius);//设置圆角的半径
        gradientDrawable.setStroke(UIUtils.dp2px(1),rgb);//描边
        return gradientDrawable;
    }

    public static StateListDrawable getSelector(Drawable normalDrawable,Drawable pressDrawable) {
        StateListDrawable stateListDrawable = new StateListDrawable();
        //给当前的颜色选择器添加选中图片指向状态,未选中图片指向状态
        stateListDrawable.addState(new int[]{android.R.attr.state_enabled, android.R.attr.state_pressed}, pressDrawable);
        stateListDrawable.addState(new int[]{android.R.attr.state_enabled}, normalDrawable);
        //设置默认状态
        stateListDrawable.addState(new int[]{}, normalDrawable);
        return stateListDrawable;
    }
}

你可能感兴趣的:(自定义view,自定义view)