Android进阶三:自定义View

自定义view步骤:

一.自定义View的属性
二.在View的构造方法中获取自定义属性
[三.重写onMesure]
四.重写onDraw



我把四用[]标出了,所以说四不一定是必须的,当然了大部分情况下还是需要重写的。

一、自定义View的属性

在res/values/底下建立一个attrs.xml,在里面定义属性和声明我们的整体样式

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <attr name="titleText" format="string" />
    <attr name="titleTextColor" format="color" />
    <attr name="titleTextSize" format="dimension" />

    <declare-styleable name="CustomTitleView">
        <attr name="titleText" />
        <attr name="titleTextColor" />
        <attr name="titleTextSize" />
    </declare-styleable>

</resources>

我们定义了字体,字体颜色,字体大小3个属性,format是值该属性的取值类型,一共有:string,color,dimension,integer,enum,reference,float,boolean,fraction,flag
详见 Android应用资源总结七:attrs中format详解

然后在布局中声明我们的自定义View:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:custom="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <com.example.customview01.view.CustomTitleView
        android:layout_width="200dp"
        android:layout_height="100dp"
        custom:titleText="3712"
        custom:titleTextColor="#ff0000"
        custom:titleTextSize="40sp" />

</RelativeLayout>

一定要引入xmlns:custom="http://schemas.android.com/apk/res-auto"我们的命名空间,关于命名空间,请参考:Android应用资源总结三:Android命名空间

二、获取自定义属性

    /**
     * 文本
     */
    private String mTitleText;
    /**
     * 文本的颜色
     */
    private int mTitleTextColor;
    /**
     * 文本的大小
     */
    private int mTitleTextSize;

    /**
     * 绘制时控制文本绘制的范围
     */
    private Rect mBound;
    private Paint mPaint;

    public CustomTitleView(Context context, AttributeSet attrs)
    {
        this(context, attrs, 0);
    }

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

    /**
     * 获得我自定义的样式属性
     * 
     * @param context
     * @param attrs
     * @param defStyle
     */
    public CustomTitleView(Context context, AttributeSet attrs, int defStyle)
    {
        super(context, attrs, defStyle);
        /**
         * 获得我们所定义的自定义样式属性
         */
        TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomTitleView, defStyle, 0);
        int n = a.getIndexCount();
        for (int i = 0; i < n; i++)
        {
            int attr = a.getIndex(i);
            switch (attr)
            {
            case R.styleable.CustomTitleView_titleText:
                mTitleText = a.getString(attr);
                break;
            case R.styleable.CustomTitleView_titleTextColor:
                // 默认颜色设置为黑色
                mTitleTextColor = a.getColor(attr, Color.BLACK);
                break;
            case R.styleable.CustomTitleView_titleTextSize:
                // 默认设置为16sp,TypeValue也可以把sp转化为px
                mTitleTextSize = a.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(
                        TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics()));
                break;

            }

        }
        a.recycle();

        /**
         * 获得绘制文本的宽和高
         */
        mPaint = new Paint();
        mPaint.setTextSize(mTitleTextSize);
        // mPaint.setColor(mTitleTextColor);
        mBound = new Rect();
        mPaint.getTextBounds(mTitleText, 0, mTitleText.length(), mBound);

    }

我们重写了3个构造方法,默认的布局文件调用的是两个参数的构造方法,所以记得让所有的构造调用我们的三个参数的构造,我们在三个参数的构造中获得自定义属性。

三、重写onMesure,onDraw方法

onMesure:计算自定义view的宽度和高度
onDraw:绘制自定义view

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onDraw(Canvas canvas)
    {
        mPaint.setColor(Color.YELLOW);
        canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mPaint);

        mPaint.setColor(mTitleTextColor);
        canvas.drawText(mTitleText, getWidth() / 2 - mBound.width() / 2, getHeight() / 2 + mBound.height() / 2, mPaint);
    }

此时的效果是:

这里写图片描述

是不是觉得还不错,基本已经实现了自定义View。但是此时如果我们把布局文件的宽和高写成wrap_content,会发现效果并不是我们的预期:

这里写图片描述

系统帮我们测量的高度和宽度都是MATCH_PARNET,当我们设置明确的宽度和高度时,系统帮我们测量的结果就是我们设置的结果,当我们设置为WRAP_CONTENT,或者MATCH_PARENT系统帮我们测量的结果就是MATCH_PARENT的长度。
所以,当设置了WRAP_CONTENT时,我们需要自己进行测量,即重写onMesure方法。
重写之前先了解MeasureSpec的specMode,一共三种类型:
EXACTLY:一般是设置了明确的值或者是MATCH_PARENT

AT_MOST:表示子布局限制在一个最大值内,一般为WARP_CONTENT

UNSPECIFIED:表示子布局想要多大就多大,很少使用
下面是我们重写onMeasure代码:

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        int width;
        int height ;
        if (widthMode == MeasureSpec.EXACTLY)
        {
            width = widthSize;
        } else
        {
            mPaint.setTextSize(mTitleTextSize);
            mPaint.getTextBounds(mTitle, 0, mTitle.length(), mBounds);
            float textWidth = mBounds.width();
            int desired = (int) (getPaddingLeft() + textWidth + getPaddingRight());
            width = desired;
        }

        if (heightMode == MeasureSpec.EXACTLY)
        {
            height = heightSize;
        } else
        {
            mPaint.setTextSize(mTitleTextSize);
            mPaint.getTextBounds(mTitle, 0, mTitle.length(), mBounds);
            float textHeight = mBounds.height();
            int desired = (int) (getPaddingTop() + textHeight + getPaddingBottom());
            height = desired;
        }



        setMeasuredDimension(width, height);
    }

现在我们修改下布局文件:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:custom="http://schemas.android.com/apk/res/com.example.customview01"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <com.example.customview01.view.CustomTitleView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        custom:titleText="3712"
        android:padding="10dp"
        custom:titleTextColor="#ff0000"
        android:layout_centerInParent="true"
        custom:titleTextSize="40sp" />

</RelativeLayout>

现在的效果是:

这里写图片描述

补充:关于View测量模式:EXACTLY、AT_MOST、UNSPECIFIED

一.自定义一个View:

public class CustomView extends TextView {
    public CustomView(Context context) {
        super(context);
    }

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

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //宽度
        String specMode_width = "";
        int specModeWidth = MeasureSpec.getMode(widthMeasureSpec);
        switch (specModeWidth) {
            case MeasureSpec.EXACTLY:
                specMode_width = "EXACTLY";
                break;
            case MeasureSpec.AT_MOST:
                specMode_width = "AT_MOST";
                break;
            case MeasureSpec.UNSPECIFIED:
                specMode_width = "UNSPECIFIED";
                break;
        }
        //高度度
        String specMode_height = "";
        int specModeHeight = MeasureSpec.getMode(heightMeasureSpec);
        switch (specModeHeight) {
            case MeasureSpec.UNSPECIFIED:
                specMode_height = "UNSPECIFIED";
                break;
            case MeasureSpec.AT_MOST:
                specMode_height = "AT_MOST";
                break;
            case MeasureSpec.EXACTLY:
                specMode_height = "EXACTLY";
                break;
        }
        Log.e("TAG", "specMode_width = " + specMode_width + " , specMode_height = " +
        specMode_height);
        Log.e("TAG", "specSize_width = " + MeasureSpec.getSize(widthMeasureSpec) + " 
        , specSize_height = " + MeasureSpec.getSize(heightMeasureSpec));
    }

}

二.在布局文件中使用View:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.example.customerviewapp.CustomView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@android:color/holo_blue_light"
        android:gravity="center"
        android:text="hello world"/>

</LinearLayout>

然后运行(屏幕分辨率为:720×1280)
输出结果如下:
1.此时layout_width和layout_height都设置成wrap_content

TAG: specMode_width = AT_MOST , specMode_height = AT_MOST
TAG: specSize_width = 720 , specSize_height = 1280

2.当将layout_width和layout_height都设置成match_parent

TAG: specMode_width = EXACTLY , specMode_height = EXACTLY
TAG: specSize_width = 720 , specSize_height = 1280

3.当将layout_width和layout_height都设置成100px

TAG: specMode_width = EXACTLY , specMode_height = EXACTLY
TAG: specSize_width = 100 , specSize_height = 100

由此可知
a.当控件的layout_width或layout_height指定为wrap_content时,为AT_MOST
b.当控件的layout_width或layout_height指定为match_parent或具体数值时,为EXACTLY

关于上述1,可能会有些疑问,为什么宽高设置成wrap_content,父控件给该子控件分配了整个屏幕的大小?因为子控件的“android:text”值可能很长,长到占据整个屏幕,此时只要控件的尺寸不超过父控件允许的最大尺寸即可。子控件会在onMeasure方法中判断它的所需尺寸,当所需尺寸 < 屏幕尺寸时,就使用所需尺寸;当所需尺寸 >= 屏幕尺寸时,则使用屏幕尺寸

四、使用总结

画drawable:mBaseLineDrawable

1.设置drawable在canvas中位置的Rect:

mBaseLineRect.set(left,top,left + mBaseLineDrawable.getIntrinsicWidth(),top + mBaseLineDrawable.getIntrinsicHeight());

2.在onDraw中:

mBaseLineDrawable.setBounds(mBaseLineRect);
mBaseLineDrawable.draw(canvas);

setBounds是设置drawable在canvas的位置信息。

3.自定义view什么时候知道宽高:
http://blog.csdn.net/mchenys/article/details/50408819

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值