在Android中自定义控件我们需要关心的只有3个回调方法:

  • onMeasure(); 该方法发负责对该类及其子View进行测量宽高
  • onLayout(); 该方法负责对该类及其子View的位置进行布置
  • onDraw(); 该方法负责回执该View及其子View

通过一个例子来说明如何自定义View,效果图如下:

这是一个自定义的布局,其子View会自动填充一行一行的想下排列。

首先我们定义一个类集成ViewGroup,在该类的onMeasure方法中进行测量,同时测量
出每个子View 的宽高,根据计算决定每个子View应该在哪一行,然后在onLayout中对
每一个子View的位置进行摆放。

首先我解释一下MeasureSpec, 一个MeasureSpec封装了父布局传递给子布局的布局
要求,每个MeasureSpec代表了一组宽度和高度的要求。一个MeasureSpec由大小和
模式组成。
它有三种模式:

  • UNSPECIFIED(未指定),父元素部队自元素施加任何束缚,子元素可以得到任意想要的大小;
  • EXACTLY(完全),父元素决定自元素的确切大小,子元素将被限定在给定的边界里而忽略它本身大小;
  • AT_MOST(至多),子元素至多达到指定大小的值。

它常用的三个函数:

  • static int getMode(int measureSpec):根据提供的测量值(格式)提取模式(上述三个模式之一)

  • static int getSize(int measureSpec):根据提供的测量值(格式)提取大小值(这个大小也就是我们通常所说的大小)

  • static int makeMeasureSpec(int size,int mode):根据提供的大小值和模式创建一个测量值(格式)

这个类的使用呢,通常在view组件的onMeasure方法里面调用.

下面提供我的自定义布局文件代码:

package com.floatlayout;import java.util.ArrayList;import java.util.List;import android.content.Context;import android.util.AttributeSet;import android.view.View;import android.view.ViewGroup;import android.widget.Toast;/** * Anthor:Jam * * Time: 2015-9-4 下午7:42:23 * * Version: * * Description:自定义瀑布流布局 自定义瀑布流布局的思路是这样的: 1,子View宽高最好只用wrap_content * 2,测量好每个子View的宽高 3,根据每个子View的宽,然后把子View放在一个行对象里面。 4,在onLayout方法内把每个子View放置位置 * */public class FlowLayout extends ViewGroup {    private int useWidth = 0; // 当前一行使用的长度    private int horizontalSpace = 0; // 水平两个子View之间的空位宽度    private int verticalSpace = 0; // 垂直两个子View之间的空位宽度    private Line line; // 表示当前行    public FlowLayout(Context context) {        super(context);        float density = getResources().getDisplayMetrics().density;        horizontalSpace = verticalSpace = (int) (13 / density + 0.5f);    }    public FlowLayout(Context context, AttributeSet attrs, int defStyle) {        super(context, attrs, defStyle);    }    public FlowLayout(Context context, AttributeSet attrs) {        super(context, attrs);    }    /** * 测量每个子View的宽高,并且把子view 分配在每一行 MessureSpec 相当于测量的规则,有3个属性 * android.view.View.MeasureSpec.UNSPECIFIED 不指定 * android.view.View.MeasureSpec.EXACTLY 精确值 * android.view.View.MeasureSpec.AT_MOST 最大是多少 */    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        //当父控件执行onMeasure的时候会调用,所以要清除数据        lines.clear();        line=null;        useWidth=0;        // 获取父View的宽MODE        int parentWidthMode = MeasureSpec.getMode(widthMeasureSpec);        // 获取父View的宽        parentWidthSize = MeasureSpec.getSize(widthMeasureSpec)                - getPaddingLeft() - getPaddingRight();        // 获取父View的高度MODE        int parentHeightMode = MeasureSpec.getMode(heightMeasureSpec);        // 获取父View的高度        int parentHeightSize = MeasureSpec.getSize(heightMeasureSpec)                - getPaddingTop() - getPaddingBottom();        // 根据父View的宽高测量规则制定每个子View的测量规则        int childWidthMode = parentWidthMode == MeasureSpec.EXACTLY ? MeasureSpec.AT_MOST                : parentWidthMode;        int childHeightMode = parentHeightMode == MeasureSpec.EXACTLY ? MeasureSpec.AT_MOST                : parentHeightMode;        // 获取子View的测量规则        int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(                parentWidthSize, childWidthMode);        int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(                parentHeightSize, childHeightMode);        // 遍历子View,测量子View的宽高        line = new Line();        for (int i = 0; i < getChildCount(); i++) {            View child = getChildAt(i);            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);            useWidth += child.getMeasuredWidth();            // 如果当前使用宽度小于父View的宽度,表示当前一行能放下这个子View,把它添加进当前行            if (useWidth <= parentWidthSize) {                line.addChild(child);                // 当前行加空格                useWidth += horizontalSpace;                // 否则新添加一行            } else {                if (line.getCount() < 1) {                    line.addChild(child);                }                // 把当前行添加到list中                lines.add(line);                // 创建行的一行                line = new Line();                // 重置                useWidth = 0;            }        }        // 如果是最后一行        if (!lines.contains(line)) {            lines.add(line);        }        int totalHeight = 0; // 父view当前总的高度        for (Line line : lines) {            // 把每一行高度添加            totalHeight += line.getHeight();        }        // 添加每一行之间的空间,获得父View的总高度        totalHeight += verticalSpace * (lines.size() - 1) + getPaddingBottom()                + getPaddingTop();        System.out.println(totalHeight + "..." + lines.size());        // 设置父View的大小        setMeasuredDimension(parentWidthSize + getPaddingLeft()                + getPaddingRight(),                resolveSize(totalHeight, heightMeasureSpec));    }    // 维护每一行的集合    private List<Line> lines = new ArrayList<Line>();    private int parentWidthSize;    // 每一行    class Line {        // 每一行中的子View        List<View> childrens = new ArrayList<View>();        int height = 0; // 行高        int lineWidth = 0; // 行宽        public void addChild(View child) {            childrens.add(child);            if (child.getMeasuredHeight() > height) {                height = child.getMeasuredHeight();            }            lineWidth += child.getMeasuredWidth();        }        // 获取每一行的高度        public int getHeight() {            return this.height;        }        public int getCount() {            return childrens.size();        }        // 给当前行每一个子View设置位置        public void layout(int l, int t) {            lineWidth += verticalSpace * (childrens.size() - 1);            int surplusWidth = parentWidthSize - lineWidth; // 每一行剩余的宽度            int childWidth = 0; // 每一个view应当扩充的宽度            if (surplusWidth > 0) {                childWidth = surplusWidth / childrens.size();            }            for (int i = 0; i < childrens.size(); i++) {                View child = childrens.get(i);                child.layout(l, t, l + child.getMeasuredWidth() + childWidth, t                        + child.getMeasuredHeight());                l += child.getMeasuredWidth() + childWidth;                l += horizontalSpace;            }        }    }    @Override    protected void onLayout(boolean changed, int l, int t, int r, int b) {        l += getPaddingLeft();        t += getPaddingTop();        for (Line line : lines) {            line.layout(l, t);            t += line.getHeight() + verticalSpace;        }    }}

下面是MainActivity代码:

package com.floatlayout;import java.util.Random;import android.os.Bundle;import android.app.Activity;import android.graphics.Color;import android.graphics.drawable.Drawable;import android.graphics.drawable.GradientDrawable;import android.graphics.drawable.StateListDrawable;import android.view.Gravity;import android.view.Menu;import android.view.ViewGroup;import android.widget.LinearLayout;import android.widget.ScrollView;import android.widget.TextView;public class MainActivity extends Activity {    String[] data = new String[] { "蹦跶", "蹦跶", "溜达", "溜达", "出去走走", "切克闹",            "神神侃于此", "侃侃更健康", "娱乐交友群", "美女帅哥", "男女屌丝", "都欢迎", "蹦跶", "蹦跶", "溜达",            "溜达", "出去走走", "切克闹", "神神侃于此", "侃侃更健康", "娱乐交友群", "美女帅哥", "男女屌丝",            "都欢迎", "蹦跶", "蹦跶", "溜达", "溜达", "出去走走", "切克闹", "神神侃于此", "侃侃更健康",            "娱乐交友群", "美女帅哥", "男女屌丝", "都欢迎", "蹦跶", "蹦跶", "溜达", "溜达", "出去走走",            "切克闹", "神神侃于此", "侃侃更健康", "娱乐交友群", "美女帅哥", "男女屌丝", "都欢迎", "蹦跶",            "蹦跶", "溜达", "溜达", "出去走走", "切克闹", "神神侃于此", "侃侃更健康", "娱乐交友群", "美女帅哥",            "男女屌丝", "都欢迎", "蹦跶", "蹦跶", "溜达", "溜达", "出去走走", "切克闹", "神神侃于此",            "侃侃更健康", "娱乐交友群", "美女帅哥", "男女屌丝", "都欢迎", "蹦跶", "蹦跶", "溜达", "溜达",            "出去走走", "切克闹", "神神侃于此", "侃侃更健康", "娱乐交友群", "美女帅哥", "男女屌丝", "都欢迎",            "蹦跶", "蹦跶", "溜达", "溜达", "出去走走", "切克闹", "神神侃于此", "侃侃更健康", "娱乐交友群",            "美女帅哥", "男女屌丝", "都欢迎", "蹦跶", "蹦跶", "溜达", "溜达", "出去走走", "切克闹",            "神神侃于此", "侃侃更健康", "娱乐交友群", "美女帅哥", "男女屌丝", "都欢迎", "蹦跶", "蹦跶", "溜达",            "溜达", "出去走走", "切克闹", "神神侃于此", "侃侃更健康", "娱乐交友群", "美女帅哥", "男女屌丝",            "都欢迎", "蹦跶", "蹦跶", "溜达", "溜达", "出去走走", "切克闹", "神神侃于此", "侃侃更健康",            "娱乐交友群", "美女帅哥", "男女屌丝", "都欢迎" };    private FlowLayout layout;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        ScrollView scrollView = new ScrollView(this);        layout = new FlowLayout(this);        for (int i = 0; i < data.length; i++) {            TextView tv = new TextView(this);            tv.setLayoutParams(new ViewGroup.LayoutParams(-2, -2)); // -2代表wrap_content            tv.setTextColor(Color.WHITE);            tv.setTextSize(dip2px(14));            Random random = new Random(); // 创建随机            int red = random.nextInt(200) + 22;            int green = random.nextInt(200) + 22;            int blue = random.nextInt(200) + 22;            int color = Color.rgb(red, green, blue);// 范围 0-255            GradientDrawable createShape = createShape(color); // 默认显示的图片            Drawable pressedDrawable = createShape(0xffcecece);            StateListDrawable createSelectorDrawable = createSelectorDrawable(                    pressedDrawable, createShape);// 创建状态选择器            tv.setBackground(createSelectorDrawable);            tv.setPadding((int) dip2px(7), (int) dip2px(4), (int) dip2px(7),                    (int) dip2px(4));            tv.setGravity(Gravity.CENTER);            tv.setText(data[i]);            layout.addView(tv);            tv.invalidate();        }        scrollView.addView(layout);        setContentView(scrollView);    }    /** * 创建圆角矩形 * * @param color * @return */    public GradientDrawable createShape(int color) {        GradientDrawable drawable = new GradientDrawable();        drawable.setCornerRadius(dip2px(5));// 设置4个角的弧度        drawable.setColor(color);// 设置颜色        return drawable;    }    /** * dp转换成px * * @param i * @return */    private float dip2px(int i) {        return getResources().getDisplayMetrics().density * i;    }    /** * 创建选择器 * * @param pressedDrawable * 按下时的颜色 * @param normalDrawable * 正常颜色 * @return */    public StateListDrawable createSelectorDrawable(Drawable pressedDrawable,            Drawable normalDrawable) {        StateListDrawable stateListDrawable = new StateListDrawable();        stateListDrawable.addState(new int[] { android.R.attr.state_pressed },                pressedDrawable);// 按下显示的图片        stateListDrawable.addState(new int[] {}, normalDrawable);// 抬起显示的图片        return stateListDrawable;    }}

源码地址:http://download.csdn.net/detail/u012943767/9078799/

更多相关文章

  1. Android视图控件架构分析之View、ViewGroup
  2. Android面试题(28)-android的view加载和绘制流程
  3. Android(安卓)自定义View总结
  4. Android开发学习之View测量的内置常用方法
  5. Android(安卓)获取控件的高度或者宽度的方法
  6. Android重写ViewGroup实现卡片布局(三)
  7. ScrollView只能有一个子控件
  8. [置顶] Android艺术开发探索学习 之 测量view的宽高 以及 动态设
  9. 2. View的工作原理

随机推荐

  1. Android开发网上的一些重要知识点
  2. Android 手势识别中的两个默认实现Simple
  3. android 字符串转json
  4. android opengl es 混合效果
  5. android studio 各种设置以及常见问题
  6. Android获取GPS进行定位的工具类
  7. Android多次加载bitmap后,提示内存溢出。
  8. Android集成百度地图SDK步骤
  9. Android 从后台进入前台
  10. Android ImageSwithcher的使用