Android自定义控件---打造不一样的FlowLayout

网上关于FlowLayout的文章有很多,大部分都是右侧空白不固定:
Android自定义控件---打造不一样的FlowLayout_第1张图片

但是不想我想要的效果,修改了一下,先来看看效果图。
Android自定义控件---打造不一样的FlowLayout_第2张图片
如果你对FlowLayout还不了解,可以看看鸿洋大神的文章:Android 自定义ViewGroup 实战篇 -> 实现FlowLayout。想一想,其实在设置每个子类的宽度的时候,将剩余宽度平均分配给每个子控件便可以实现我要的效果。
嗯,先上FlowLayout文件,其实主要是在layout方法中做了修改。

package com.android.flowlayout;

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

import java.util.ArrayList;
import java.util.List;

/**
 * 文字瀑布流,瀑布流中每个子控件是textview,如果不是,请重新写layout方法,将返回的子控件定义为你的控件类型,
 * Created by wu on 2015/11/12.
 */
public class FlowLayout extends ViewGroup {
    private List mLines = new ArrayList<>();
    private Line currentLine;//当前行
    private int usedWidth = 0;//当前行已经使用的宽度
    private int horizontalSpacing;//水平的间隔
    private int verticalSpacing;//垂直的间隔
    private int width;//控件的宽度
    private int height;//控件的高度
    private Context mContext;

    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);
        this.mContext = context;
        horizontalSpacing=UiUtils.dp2px(context,13);
        verticalSpacing=UiUtils.dp2px(context,13);
    }

    //测量当前控件
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //获取当前容器的宽高模式和大小
        mLines.clear();
        currentLine = null;
        usedWidth = 0;
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        width = MeasureSpec.getSize(widthMeasureSpec)-getPaddingLeft()-getPaddingRight();
        height = MeasureSpec.getSize(heightMeasureSpec)-getPaddingTop()-getPaddingBottom();
        int childWidthMode;
        int childHeightMode;
        //为了测量每个子控件,需要指定每个子控件的测量规则
        childWidthMode = widthMode == MeasureSpec.EXACTLY ? MeasureSpec.AT_MOST : widthMode;
        childHeightMode = heightMode == MeasureSpec.EXACTLY ? MeasureSpec.AT_MOST : heightMode;
        int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, childWidthMode);
        int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height, childHeightMode);
        currentLine = new Line();//创建了新的一行(第一行)
        for (int i = 0; i < getChildCount(); i++) {
            //测量子控件
            View child = getChildAt(i);
            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
            int measuredWidth = child.getMeasuredWidth();//获得子控件的宽度
            if(usedWidth+measuredWidth+horizontalSpacing0){
                //当前行没有数据或者现在的宽度+下一个宽度<行宽。不需要换行,直接添加到current中。
                currentLine.addChild(child);
                usedWidth+=measuredWidth;
                usedWidth+=horizontalSpacing;
            }else{
                newLine();
                currentLine.addChild(child);
                usedWidth+=measuredWidth;
                usedWidth+=horizontalSpacing;
            }
        }
        if (!mLines.contains(currentLine)) {//添加最后一行
            mLines.add(currentLine);
            Log.d("FlowLayout", "currentLine.getChildCount():" + currentLine.getChildCount());
        }
        int totalHeight = 0;
        for (Line line : mLines) {
            totalHeight += line.getHeight();
        }
        totalHeight += ((mLines.size() - 1) * verticalSpacing)+getPaddingTop()+getPaddingBottom();
        setMeasuredDimension(width+getPaddingLeft()+getPaddingRight(), resolveSize(totalHeight, heightMeasureSpec));
    }

    //分配子控件的位置,如果剩余的距离不够使用,则需要换行
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        l+=getPaddingLeft();
        t+=getPaddingTop();
        for (int i = 0; i < mLines.size(); i++) {
            Line line = mLines.get(i);
            line.layout(l, t);
            t += line.getHeight() + verticalSpacing;//每一行左上角的t值都会改变
        }
    }

    /**
     * 每一个行的类
     */
    private class Line {
        int height = 0;
        List children = new ArrayList<>();
        int total = 0;

        /**
         * 添加一个子控件
         *
         * @param child
         */
        public void addChild(View child) {
            children.add(child);
            if (child.getMeasuredHeight() > height) {
                height = child.getMeasuredHeight();
            }
            total += child.getMeasuredWidth();
        }

        /**
         * 获取子控件的数量
         *
         * @return
         */
        public int getChildCount() {
            return children.size();
        }

        public int getHeight() {
            return height;
        }

        /**
         * 指定行的左上角位置,其子类的位置由该函数确定
         *
         * @param l  左侧位置
         * @param t  顶部位置
         */
        public void layout(int l, int t) {
            total += horizontalSpacing * (children.size() - 1);//现有子控件所占有的宽度
            int surplusChild = 0;
            int surplus = width - total;//右侧剩余的宽度
            surplusChild = surplus / children.size();//右侧剩余宽度平分给各个控件
            for (int i = 0; i < children.size(); i++) {
                //将每一个子TextView取出来
                TextView view = (TextView) children.get(i);
                //设置每个子TextView的布局,宽度在原有布局的基础上增加了surplusChild
                view.layout(l, t, l + view.getMeasuredWidth()+surplusChild, t + view.getMeasuredHeight());
                //为子View的字体设置居中,此步骤不能在给layout添加view的时候,给view设置gravity属性,只能在这里设置
                view.setGravity(Gravity.CENTER);
                String text=view.getText().toString();
                if(text!=null){
                    //如果此时textview的文字已经绘制完成,因为我们重新layout,会导致文字不居中,重新获取文字,并设置,
                    view.setText(text);
                }
                //更新下一个子View的左侧的位置
                l += view.getMeasuredWidth()+surplusChild;
                l += verticalSpacing;
            }
        }
    }

    /**
     * 创建新的行
     */
    public void newLine() {
        mLines.add(currentLine);
        currentLine = new Line();
        usedWidth = 0;
    }
}

我们的xml主布局文件其实很简单。
activity_main:


<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    <ScrollView
        android:id="@+id/scrollView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
RelativeLayout>

下面来看看我们在activity中是如何使用我们的这个FlowLayout的。
MainActivity.java

package com.android.testflowlayout;

import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.GridView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Random;

public class MainActivity extends AppCompatActivity {

    private android.widget.ScrollView scrollView;
    private List datas;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initViews();
        initDatas();
        FlowLayout flowLayout = new FlowLayout(this);
        int padding=UiUtils.dp2px(this,13);
        flowLayout.setPadding(padding,padding,padding,padding);
        Drawable pressDrawable=DrawableUtils.createShape(this,0xffcecece);
        for (int i = 0; i < datas.size(); i++) {
            TextView textView = new TextView(this);
            //设置textview未点击时的背景,圆角+随机颜色,通过xml设置+代码实现
            textView.setBackgroundResource(R.drawable.text_bg);
            //生成随机颜色,为了防止产生黑色或者白色,设定一定的范围
            int color= Color.rgb(new Random().nextInt(200) + 20, new Random().nextInt(200) + 20, new Random().nextInt(200) + 20);
            GradientDrawable drawable= (GradientDrawable) textView.getBackground();
            //将生成的随机色赋值给背景色
            drawable.setColor(color);
            //设置背景为状态选择器
            textView.setBackgroundDrawable(new DrawableUtils().creatStateListDrawable(pressDrawable, drawable));
            textView.setText(datas.get(i));
            final int finalI = i;
            textView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Toast.makeText(MainActivity.this, datas.get(finalI), Toast.LENGTH_SHORT).show();
                }
            });
            textView.setGravity(Gravity.CENTER);
            textView.setTextColor(Color.WHITE);
            flowLayout.addView(textView);
        }
        scrollView.addView(flowLayout);
    }

    /**
     * 生成要显示的数据
     */
    private void initDatas() {
        String[] strs=new String[]{"QQ","视频","放开那三国","电子书","酒店","单机","小说","斗地主","优酷",
                "网游","WIFI万能钥匙","播放器","捕鱼达人2","机票","游戏","熊出没之熊大快跑","美图秀秀","浏览器",
                "单机游戏","我的世界","电影电视","QQ空间","旅游","免费游戏","2048","刀塔传奇","壁纸","节奏大师",
                "锁屏","装机必备","天天动听","备份","网盘","海淘网","大众点评","爱奇艺视频","腾讯手机管家",
                "百度地图","猎豹清理大师","谷歌地图","hao123上网导航","京东","youni有你","万年历-农历黄历","支付宝钱包"};
        datas=new ArrayList<>(Arrays.asList(strs));
    }

    private void initViews() {
        this.scrollView = (ScrollView) findViewById(R.id.scrollView);
    }
}

嗯,个人感觉说明已经很详细了。最后还有一个简单的圆角背景图,和两个辅助类。
text_bg.xml


<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <corners android:radius="5dp"/>
    <solid android:color="#000000"/>
    <padding android:bottom="4dp"
        android:top="4dp"
        android:left="7dp"
        android:right="7dp"/>
shape>

UiUtils.java

package com.android.testflowlayout;

import android.content.Context;
import android.util.TypedValue;

/**
 * UI相关的辅助类
 * Created by wu on 2015/11/6.
 */
public class UiUtils {
     /*
     * @param context
     * @param dpVal
     * @return
     */
    public static int dp2px(Context context,float dpVal)
    {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                dpVal,context.getResources().getDisplayMetrics());
    }
}

DrawableUtils.java

package com.android.testflowlayout;

import android.content.Context;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.StateListDrawable;

/**
 * Created by wu on 2015/11/12.
 */
public class DrawableUtils {
    /**
     * 生成圆角图片
     * @param context
     * @param color
     * @return
     */
    public static Drawable createShape(Context context, int color) {
        GradientDrawable drawable=new GradientDrawable();
        drawable.setCornerRadius(UiUtils.dp2px(context,5));
        drawable.setColor(color);
        return  drawable;
    }

    /**
     * 生成selector,动态设置
     * @param pressedDrawable   按下时的drawable
     * @param normalDrawable    正常状态是的drawable
     * @return
     */
    public static Drawable creatStateListDrawable(Drawable pressedDrawable,Drawable normalDrawable){
        StateListDrawable drawable=new StateListDrawable();
        drawable.addState(new int[]{android.R.attr.state_pressed},pressedDrawable);
        drawable.addState(new int[]{},normalDrawable);
        return drawable;
    }
}

所有的文件基本上都在这儿了。
欢迎大家fork。
https://github.com/kailaisi/FlowLayout

你可能感兴趣的:(android)