本文讨论的是下图的这种数据展示方式

通过本文可以学到的内容

===View绘制的工作流程measure和Layout,即测量和布局;

===动态创建和添加子View,以及设置点击事件的一种思路

1、主要使用的是一个继承自ViewGroup的自定义类NewLineLayout

代码如下:

/**
 * 可以换行的布局
 */

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

import com.orhanobut.logger.Logger;

public class NewLineLayout extends ViewGroup {

    private int mScreenWidth;//屏幕宽度
    private int horizontalSpace, verticalSpace;//子View间距

    public NewLineLayout(Context context) {
        this(context, null);
    }

    public NewLineLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        mScreenWidth = context.getResources().getDisplayMetrics().widthPixels;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //确定此容器的宽高
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        //摆放子view
        View child;
        //子view摆放的起始位置
        int left = getPaddingLeft();
        //存储一行view最大的高度,用于子view进行换行时高度的计算
        int maxHeightInLine = 0;
        //存储所有行的高度相加,用于确定此容器的高度
        int allHeight = 0;
        //遍历子view
        int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            child = getChildAt(i);
            //测量子View宽高
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
            //两两对比,取得一行中最大的高度
            int childHeight = child.getMeasuredHeight() +
                    child.getPaddingTop() + child.getPaddingBottom();
            if (childHeight > maxHeightInLine) {
                maxHeightInLine = childHeight;
            }
            left += child.getMeasuredWidth() + horizontalSpace +
                    child.getPaddingLeft() + child.getPaddingRight();
            //这一行所有子view相加的宽度大于容器的宽度,需要换行
            if (left > widthSize - getPaddingRight()) {
                //换行的首个子view,起始left为容器的paddingLeft
                left = getPaddingLeft();
                //累积行的总高度
                allHeight += maxHeightInLine + verticalSpace;
                //重置最大高度
                maxHeightInLine = 0;
            } else {
                allHeight += maxHeightInLine;
            }
        }

        if (widthMode != MeasureSpec.EXACTLY) {
            widthSize = mScreenWidth;//如果没有指定宽,则默认为屏幕宽
        }

        if (heightMode != MeasureSpec.EXACTLY) {//如果没有指定高度
            heightSize = allHeight + getPaddingBottom() + getPaddingTop();
        }

        setMeasuredDimension(widthSize, heightSize);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if (changed) {
            //摆放子view
            View child;
            //子view摆放的起始位置
            int left = getPaddingLeft();
            int top = getPaddingTop();
            //存储一行view最大的高度,用于子view进行换行时高度的计算
            int maxHeightInLine = 0;
            for (int i = 0, len = getChildCount(); i < len; i++) {
                child = getChildAt(i);
                //从第二个子view开始算起
                //第一个子view默认从头开始摆放
                if (i > 0) {
                    //两两对比,取得一行中最大的高度
                    if (getChildAt(i - 1).getMeasuredHeight() > maxHeightInLine) {
                        maxHeightInLine = getChildAt(i - 1).getMeasuredHeight();
                    }
                    //当前子view的起始left为 上一个子view的宽度+水平间距
                    left += getChildAt(i - 1).getMeasuredWidth() + horizontalSpace;
                    //这一行所有子view相加的宽度大于容器的宽度,需要换行
                    if (left + child.getMeasuredWidth() > getWidth() - getPaddingRight()) {
                        //换行的首个子view,起始left为容器的paddingLeft
                        left = getPaddingLeft();
                        //top的位置为上一行中拥有最大高度的某个View的高度+垂直间距
                        top += maxHeightInLine + verticalSpace;
                        //将上一行View的最大高度置0
                        maxHeightInLine = 0;
                    }
                }
                //摆放子view
                child.layout(left, top, left + child.getMeasuredWidth(), top + child.getMeasuredHeight());
            }
        }
    }

    /**
     * 设置子view间的水平间距
     *
     * @param horizontalSpace
     */
    public void setHorizontalSpace(int horizontalSpace) {
        this.horizontalSpace = horizontalSpace;
    }

    /**
     * 设置子view间的垂直间距
     *
     * @param verticalSpace
     */
    public void setVerticalSpace(int verticalSpace) {
        this.verticalSpace = verticalSpace;
    }
}

注释比较清楚,耐心点绝对能理解

2、动态创建TextView的方法

/**
* 创建自定义View
*
* @param text 文本内容
*/
private TextView newCustomTextView(String text) {
    TextView textView = new TextView(activity);
    textView.setText(text);
    //指定文字大小
    textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, activity.getResources().getDimension(R.dimen.font_text));
    //设置最小宽高
    textView.setMinHeight((int) activity.getResources().getDimension(R.dimen.text_height));
    textView.setMinWidth((int) activity.getResources().getDimension(R.dimen.text_width));
    //设置内边距
    int padding = (int) activity.getResources().getDimension(R.dimen.space_tiny);
    textView.setPadding(padding, 0, padding, 0);
    textView.setGravity(Gravity.CENTER);
    return textView;
}

3、添加子View,利用tag属性关联View点击事件

private String lineString = "_";//自定义分割字符

private void initView() {
    final TextView[] textViewArray = new TextView[dataList.length];
    View.OnClickListener onClickListener = new View.OnClickListener() {
        @Override
        public void onClick(View view) {
        String tag = (String) view.getTag();
        if (!StringUtil.isEmpty(tag)) {
            int lineIndex = tag.indexOf(lineString);
            int index = StringUtil.toInt(tag.substring(0, lineIndex), 0);
            //处理按钮组单选的情况
            changeButtonState(textViewArray, index);
            ToastUtil.showShortToast(activity, dataList[index]);
        }
        }
    };
    for (int i = 0; i < dataList.length; i++) {
        textViewArray[i] = newCustomTextView(dataList[i]);
        //组合tag处理单击事件
        String tag = i + lineString + dataList[i];
        textViewArray[i].setTag(tag);
        textViewArray[i].setOnClickListener(onClickListener);
        //添加自定义View
        layContent.addView(textViewArray[i]);
    }
}

4、按钮组的单选实现方法

/**
* 单选按钮组
*
* @param index 根据序号改变按钮组的状态
*/
public void changeButtonState(TextView[] textView, int index) {
    int size = textView.length;
    for (int i = 0; i < size; i++) {
        textView[i].setBackgroundResource(text_normal);
        textView[i].setTextColor(txt_normal);
    }
    textView[index].setBackgroundResource(text_select);
    textView[index].setTextColor(txt_select);
}

===代码地址===

Android可以换行的布局的更多相关文章

  1. Android经常使用的布局类整理(一)

    Android经常使用的布局类整理 近期又回头做了一下android的项目,发觉越来越不从心,非常多东西都忘了,简单的页面布局也非常多写不出来,首先还是先整理一下一些会混淆的概念先 layout_wi ...

  2. Android UI组件:布局管理器

    为了更好的管理Android应用的用户界面中的组件,Android提供了布局管理器.通过使用布局管理器,Android应用的图形用户界面具有良好的平台无关性.通常,推荐使用布局管理器来管理组件的分布. ...

  3. Android中的LinearLayout布局

    LinearLayout : 线性布局 在一般情况下,当有很多控件需要在一个界面列出来时,我们就可以使用线性布局(LinearLayout)了,  线性布局是按照垂直方向(vertical)或水平方向 ...

  4. Android开发-之五大布局

    在html中大家都知道布局是什么意思了,简单来说就是将页面划分模块,比如html中的div.table等.那么Android中也是这样的.Android五大布局让界面更加美化,开发起来也更加方便.当然 ...

  5. 简单研究Android View绘制三 布局过程

    2015-07-28 17:29:19 这一篇主要看看布局过程 一.布局过程肯定要不可避免的涉及到layout()和onLayout()方法,这两个方法都是定义在View.java中,源码如下: /* ...

  6. Android性能优化之布局优化

    最新最准确内容建议直接访问原文:Android性能优化之布局优化 本文为Android性能优化的第二篇——布局优化,主要介绍使用抽象布局标签(include, viewstub, merge).去除不 ...

  7. Android中的五大布局

    Android中的五大布局 1.了解布局 一个丰富的界面总是要由很多个控件组成的,那我们如何才能让各个控件都有条不紊地 摆放在界面上,而不是乱糟糟的呢?这就需要借助布局来实现了.布局是一种可用于放置很 ...

  8. 关于android LinearLayout的比例布局(转载)

    关于android LinearLayout的比例布局,主要有以下三个属性需要设置: 1,android:layout_width,android:layout_height,android:layo ...

  9. 【转】Android性能优化之布局优化篇

     转自:http://blog.csdn.net/feiduclear_up/article/details/46670433 Android性能优化之布局优化篇 分类: andorid 开发2015 ...

随机推荐

  1. Cocos2d-x shader学习1: 灰度shader

    灰度shader 最近在学习shader,就把cocos2d-x 3.x版本中的很简单也很常用的灰度shader拿出来学习一下. #ifdef GL_ES precision mediump floa ...

  2. Visual Studio 20周年,我和VS不得不说的故事(内含福利)

    Visual Studio 2017正式版已如期发布(点击这里查看发布全记录)!自去年 11 月正式宣布 Visual Studio 项目之后,微软终于正式推出了“宇宙最强集成开发环境(IDE)”的最 ...

  3. Python 模块之 string.py

    用法 字符串常量: import string print(string.ascii_lowercase) print(string.ascii_uppercase) print(string.asc ...

  4. IE浏览器下使用AJAX登陆接口请求缓存与登陆不了的问题解决

    问题: 在IE浏览器下面,登陆的时候老是登陆不上,但是打开控制台的时候再登陆却能登陆上. 分析: 通过抓包,发现,在不打开控制台的时候,少了一个接口的请求,却返回了改接口的返回信息,但是返回信息并不是 ...

  5. Jaro-Winkler Distance

    发现commons-lang 中有实现: StringUtils.class public static double getJaroWinklerDistance(final CharSequenc ...

  6. 挂接命令(mount) 【转】

    收藏些文章,以后可能有用,文章出自:http://tutu.spaces.eepw.com.cn/articles/article/item/70737 挂接命令(mount) 首先,介绍一下挂接(m ...

  7. 【故障•监听】TNS-12518、TNS-00517和 Linux Error:32:Broken pipe

    [故障|监听]TNS-12518.TNS-00517和 Linux Error:32:Broken pipe 1.1  BLOG文档结构图 1.2  前言部分 1.2.1  导读和注意事项 各位技术爱 ...

  8. Android 实现QQ第三方登录

    Android 实现QQ第三方登录 在项目中需要实现QQ第三方登录,经过一番努力算是写出来了,现在总结以下,以防以后遗忘,能帮到其他童鞋就更好了. 首先肯定是去下载SDK和DEMO http://wi ...

  9. SQL函数和SQL Server2008

    1.绝对值   SQL:select abs(-1) value  O:select abs(-1) value from dual  2.取整(大)   S:select ceiling(-1.00 ...

  10. App对接支付宝移动支付功能

    前段时间看了下app对接支付宝移动支付的功能,并自己总结了下支付宝移动支付的实现流程 一.申请流程          前提是已有现成的应用. 1.  申请地址            https://b ...