码迷,mamicode.com
首页 > 移动开发 > 详细

Android自定义视图与自定义属性

时间:2015-02-11 00:36:59      阅读:279      评论:0      收藏:0      [点我收藏+]

标签:android   自定义视图   自定义属性   

这是Android UI Fundamentals里的内容

创建自定义视图

创建自定义UI组件首先要继承一个视图类.
首先创建一个简单的自定义视图, 展示一条十字线.
技术分享
需要做的第一件事是创建一个继承自View的CrossView类.

    public CrossView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

该构造函数的第二个参数是用来传递XML参数的, 等会儿会讲到. 接下来我们要重写两个基础方法: onMeasureonDraw.

onMeasure

系统调用onMeasure方法来决定视图及其子视图的尺寸. 它的两个参数的类型都是int, 但是这俩参数并非普通的数字, 而是两个MeasureSpec, MeasureSpec是一个模式和一个整型尺寸值的结合, 被当成一个整数来实现. 其中模式值有如下几种情况:

模式 解释
UNSPECIFIED 父视图没有在这个视图上做任何限制, 它可以是任意尺寸
AT_MOST 该视图可以是小于等于MeasureSpec中尺寸的任意大小
EXACTLY 父视图要求该视图必须是MeasureSpec指定的尺寸大小

当你创建一个自定义视图并重写onMeasure方法时, 必须正确处理每种情况, 得到相应的尺寸, 然后必须在onMeasure中调用setMeasureDimensions方法, 参数就是你决定的尺寸, 如果不调用就会抛出异常.
下面是重写的onMeasure方法代码.

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(calculateMeasure(widthMeasureSpec), calculateMeasure(heightMeasureSpec));
    }

注意其中calculateMeasure方法是我们自己定义的, 下面我们来完成这个方法.
我们先定义一个默认的尺寸100, 单位是dp(我暂时不确定是不是dp).

private static final int DEFAULT_SIZE = 100;

乘上设备的像素密度, 得到实际显示需要的像素值.

int result = (int) (DEFAULT_SIZE * getResources().getDisplayMetrics().density);

然后我们需要从MeasureSpec中拿到模式和尺寸

int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);

接下来我们根据specMode的情况来判断result的值到底应该是什么.

  • MeasureSpec.UNSPECIFIED
    此时父控件对自定义视图的尺寸没有要求, 那么我就以默认大小为结果, 也就是说
int result = (int) (DEFAULT_SIZE * getResources().getDisplayMetrics().density);
  • MeasureSpec.AT_MOST
    此时父控件认为最多不能超过指定尺寸值, 那么此时我们选指定值和默认值中最小的那个就行, 无论哪种情况这种选法都是合法的.
result = Math.min(specSize, result);
  • MeasureSpec.EXACTLY
    此时父控件要求子视图必须是给定的尺寸, 那么我们让result等于它就好
result = specSize;

综合上面的讨论, 最终我们的方法代码如下:

    private static final int DEFAULT_SIZE = 100;

    private int calculateMeasure(int measureSpec) {
        int result = (int) (DEFAULT_SIZE * getResources().getDisplayMetrics().density);
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);
        if (specMode == MeasureSpec.EXACTLY) {
            result = specSize;
        } else if (specMode == MeasureSpec.AT_MOST) {
            result = Math.min(specSize, result);
        }
        return result;
    }

onDraw

当视图应当绘制其内容时会调用onDraw方法. 在重写它之前, 我们先创建一个Paint对象, 它处理诸如颜色和文本大小之类的事情.
通过CrossView的构造函数来创建Paint对象

    private Paint mPaint;

    public CrossView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setColor(0xffff0000);
    }

上面的代码新建了Paint对象, 并设置抗锯齿和颜色.
接下来重写onDraw方法, 模板如下, canvas.save()canvas.restore()我就不解释了, 不影响后面的理解.

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.save();
        // code goes here
        canvas.restore();
    }

我们基于视图的尺寸缩放画布, 这样我们可以使用0到1之间的浮点数来作为画线时的坐标

    private static final float[] mPoints = {0.5f, 0f,
                                            0.5f, 1f,
                                            0f, 0.5f,
                                            1f, 0.5f};

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.save();
        canvas.scale(getWidth(), getHeight());
        canvas.drawLines(mPoints, mPaint);
        canvas.restore();
    }

我们在activity的xml里面加入我们的自定义控件

<LinearLayout 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"
    android:orientation="vertical"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    tools:context=".MainActivity">


    <com.shaw.uitest.CrossView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <com.shaw.uitest.CrossView
        android:layout_marginTop="10dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</LinearLayout>

运行一下就可以看到文章开头的截图画面了.

向自定义视图中添加自定义属性

有了自定义视图, 我们希望它能通过自定义XML属性来配置, 要做到这一点, 需要先声明属性, 然后在XML布局中添加一个新的命名空间, 最后处理被传递给自定义视图构造函数的AttributeSet对象.

声明属性

在res/values/目录下创建一个attrs.xml(可以是别的名字)的文件, 然后在其中添加如下内容:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="cross">
        <attr name="android:color" />
        <attr name="rotation" format="string" />
    </declare-styleable>
</resources>

declare-styleable元素有一个name属性, 用来在代码中的引用自定义属性, 每个自定义的属性都使用一个attr元素来声明, attr元素有name和format两个属性, name用于引用, format代表它的数据类型, 如果使用了默认的系统属性, 就不需要定义format了, 如果尝试给已有的属性定义一个不同的format, 则工程无法build. 在外层声明的attr可以被其他declare-styleable复用, 就和使用系统属性一样, 比如:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <attr name="test" format="string" />
    <declare-styleable name="foo">
        <attr name="test" />
    </declare-styleable>
    <declare-styleable name="bar">
        <attr name="test" />
    </declare-styleable>
</resources>

也可以给属性创建自定义值, 例如

<attr name="enum_attr">
    <enum name="value1" value="1" />
    <enum name="value2" value="2" />
</attr>
<attr name="flag_attr">
    <flag name="flag1" value="0x01" />
    <flag name="flag2" value="0x02" />
</attr>

enumflag都要求是整数. 不同之处在于flag可以使用|来拼接. 比如android:gravity的值就是flag.

在XML中使用属性

要使用在我们的XML中的新属性, 首先必须为视图声明namespace. 其实我们经常见到namespace的声明, 比如我们常在activity的xml文件中看到

xmlns:android="http://schemas.android.com/apk/res/android"

这个namespace声明了所有以关键词android开头的属性都可以在android包中找到. 要使用自定义属性, 需要声明一个带有新包名的新namespace, 下面为CrossView的属性添加一个新的namespace, 并在自定义视图中添加相关的xml配置:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:crossview="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    tools:context=".MainActivity">


    <com.shaw.uitest.CrossView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        crossview:rotation="30"
        android:color="#ff0000ff"/>

    <com.shaw.uitest.CrossView
        android:layout_marginTop="10dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        crossview:rotation="50"
        android:color="#ff00ff00"/>
</LinearLayout>

上面声明了所有以crossview(名字可以用别的)开头的属性都可以在res中找到. 这是Gradle要求的写法.

在代码中使用XML属性

CrossView的构造函数中传入了一个AttributeSet对象, 我们可以通过它获取XML布局中声明的属性.
更新CrossView的构造函数并添加相应函数和成员变量:

    private float mRotation;

    public CrossView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        TypedArray arr = getContext().obtainStyledAttributes(attrs, R.styleable.cross);
        int color = arr.getColor(R.styleable.cross_android_color, Color.BLACK);
        float rotation = arr.getFloat(R.styleable.cross_rotation, 0f);
        arr.recycle();
        setColor(color);
        setRotation(rotation);
    }

    public void setColor(int color) {
        mPaint.setColor(color);
    }

    public void setRotation(float degree) {
        mRotation = degree;
    }

同时更新onDraw的代码

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.save();
        canvas.scale(getWidth(), getHeight());
        canvas.rotate(mRotation, 0.5f, 0.5f);
        canvas.drawLines(mPoints, mPaint);
        canvas.restore();
    }

我们的旋转中心是画布中心, 而不是左上角.
现在运行这个程序, 截图如下:
技术分享

Android自定义视图与自定义属性

标签:android   自定义视图   自定义属性   

原文地址:http://blog.csdn.net/shaw1994/article/details/43707005

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!