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

来源:互联网 发布:php cookies管理系统 编辑:程序博客网 时间:2024/05/21 09:36

网上关于FlowLayout的文章有很多,大部分都是右侧空白不固定:
这里写图片描述

但是不想我想要的效果,修改了一下,先来看看效果图。
这里写图片描述
如果你对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<Line> 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+horizontalSpacing<width ||currentLine.getChildCount()==0){                //当前行没有数据或者现在的宽度+下一个宽度<行宽。不需要换行,直接添加到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<View> 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:

<?xml version="1.0" encoding="utf-8"?><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<String> 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

<?xml version="1.0" encoding="utf-8"?><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

1 0
原创粉丝点击