Android 自定义View流程

在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/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值