美文网首页学习区干货区Android开发经验谈
Android学习笔记13 自定义View完全解析

Android学习笔记13 自定义View完全解析

作者: RumbleTsc | 来源:发表于2016-07-22 21:11 被阅读491次

    Android为我们提供了非常丰富的界面控件,借助于这些控件,我们可以很方便地进行界面开发。但是,因为功能或者界面效果的需要,我们有时可能要自己定义一些控件。比如,原生的ListView并没有下拉刷新等效果,我们需要在原来的基础上进行扩展满足需要,还有很多其它需要自定义View的情况,下面带来自定义View相关内容的学习总结,文末附Demo源码。

    一、View与ViewGroup
    二、自定义View
       1、为什么要自定义View
       2、自定义View的基本步骤
       3、自定义View须知
    三、自定义View示例与解析
    四、自定义ViewGroup解析
    五、源码链接
    

    一、View与ViewGroup

    相信很多人对View与ViewGroup已经很熟悉了,但这里还是简单提一下,在我们的应用中,用户界面上的所有元素都是由View与ViewGroup对象构成的。View,视图,是绘制在屏幕上的,用户可以与之交互的对象。ViewGroup,视图组合,是用于包含其它视图以定义界面布局的对象,ViewGroup是继承自View的。我们平常使用的各种普通控件,比如Button,TextView等等都是View的子类,而各种布局都是ViewGroup的子类。

    下面这张图很清楚地展示了界面上的View层级关系。

    界面上View的层级关系

    二、自定义View概述

    1、为什么要自定义View?

    Android基于View和ViewGroup已经为我们提供了大量精致而且使用方便的组件,但是在有些时候,这些系统的组件不能很好的满足我们的需要,这时就考虑自定义View。

    如果我们需要一个全新的控件,那么我们可以继承View然后来创建,如果我们只是需要在已有的控件上进行修改,那么我们就可以继承已有的控件类,重写其中的部分方法。

    2、自定义View的基本步骤?
    1. 新建我们需要的类,继承自View或者View的子类
    1. 重写其中的方法,比如onMeasure(), onDraw(), onKeyDown()等等,类似我们在新建Activity时重写其中的相关方法。
    2. 像使用系统组件一样使用我们自定义的控件。
    3、自定义View须知

    自定义View之前,我们需要先知道系统是如何把View绘制在界面上的。

    首先,系统要知道绘制的View的大小,这是在方法onMeasure里完成的。

    我们平常在布局文件里一般会定义一个控件的高和宽,比如我们可能会设置200dp,wrap_content或者match_parent,不同的设置最后绘制在屏幕上的大小不同,在onMeasure方法里我们需要根据不同的设置获取到尺寸然后系统在屏幕上绘制指定大小的区域。Android系统为我们提供了一个类MeasureSpec,通过它我们可以很方便地获取我们设置的值,最后在onMeasure里转化成具体的尺寸,显示在屏幕上。

    MeasureSpec里有三种测量模式,一种是精确值模式EXACTLY,一种是最大值模式AT_MOST,还有一种是未指定模式UNSPECIFED。其中,当我们设置具体的值比如100dp或者设置成match_parent时,测量模式是EXACTLY,当设置成wrap_content时,是EXACTLY,最后一种UNSPECIFED模式通常在绘制自定义View时使用,较少使用。

    之后,View的绘制是在方法onDraw()里完成的。要在界面上绘制相应的图形,必须先在画板Canvas上绘制。

    ViewGroup在确定自身的大小时,如果是wrap_content一般是遍历所有子View获得子View的大小后再确定自身的大小,如果是具体的值则就确定为具体的值。

    ViewGroup测量之后要将子View放在合适的位置,这是通过onLayout()方法完成的。

    三、自定义View示例与解析

    下面展示两个基本的自定义View的例子,通过这些例子可以很清楚地了解用法。

    例1 扩展TextView的MyTextView
    例2 完全自定义的MyXTextView

    例1 扩展TextView的MyTextView。

    1、创建自定义View,名称为MyTextView,这里我暂时只是重写了onDraw方法,在系统绘制前添加一些效果。

    /**
     * Created by JackalTsc on 2016/7/22.
     */
    public class MyTextView extends TextView {
    
        //画笔
        Paint mPaint1, mPaint2;
    
        public MyTextView(Context context) {
            this(context, null, 0);
        }
    
        public MyTextView(Context context, AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
    
            //这里对画笔进行初始化
            mPaint1 = new Paint();
            mPaint1.setColor(getResources().getColor(android.R.color.holo_blue_light));
            mPaint1.setStyle(Paint.Style.FILL);
    
            mPaint2 = new Paint();
            mPaint2.setColor(Color.YELLOW);
            mPaint2.setStyle(Paint.Style.FILL);
        }
    
        //MyTextView测量
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    
        //绘制
        @Override
        protected void onDraw(Canvas canvas) {
    
            //绘制文字前我们给TextView添加一些背景效果
            canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mPaint1);
            canvas.drawRect(10, 10, getMeasuredWidth() - 10, getMeasuredHeight() - 10, mPaint2);
            canvas.save();
            canvas.translate(10, 0);
    
            //绘制
            super.onDraw(canvas);
            canvas.restore();
        }
    }
    

    2、布局文件里使用自定义的View。

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:orientation="vertical"
        android:padding="10dp">
    
        <com.jackaltsc.mydemocustomview.myview.MyTextView
            android:id="@+id/mtv_test1"
            android:layout_width="200dp"
            android:layout_height="80dp"
            android:gravity="center"
            android:text="文本1"
            android:textSize="30sp" />
    
        <com.jackaltsc.mydemocustomview.myview.MyTextView
            android:id="@+id/mtv_test2"
            android:layout_width="200dp"
            android:layout_height="120dp"
            android:layout_marginTop="20dp"
            android:gravity="center"
            android:text="文本2"
            android:textSize="30sp" />
    </LinearLayout>
    
    

    3、运行程序可以看到结果如下。

    效果
    例2 完全自定义的MyXTextView

    在例1中,我们只是简单地让自定义的类继承TextView,可以看到,只需要在原来的TextView的基础上重写部分需要的方法进行扩展即可。那么如果我们不是继承TextView而是直接继承View,需要做哪些工作呢?

    1、定义属性。我们在values下新建文件attrs.xml,内容如下。

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
    
        <attr name="text" format="string" />
        <attr name="textColor" format="color" />
        <attr name="textSize" format="dimension" />
    
        <declare-styleable name="MyStyle">
            <attr name="text" />
            <attr name="textColor" />
            <attr name="textSize" />
        </declare-styleable>
    </resources>
    

    2、构造函数里获得定义的这些属性。下面这段代码是获取我们刚刚定义的attrs文件里的属性。

    TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MyStyle, defStyle, 0);
    
            int n = a.getIndexCount();
    
            for (int i = 0; i < n; i++) {
    
                int attr = a.getIndex(i);
                switch (attr) {
                    case R.styleable.MyStyle_text:
                        mText = a.getString(attr);
                        break;
                    case R.styleable.MyStyle_textColor:
                        // 默认颜色设置为黑色
                        mTextColor = a.getColor(attr, Color.BLACK);
                        break;
                    case R.styleable.MyStyle_textSize:
                        // 默认设置为16sp,TypeValue也可以把sp转化为px
                        mTextSize = a.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(
                                TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics()));
                        break;
    
                }
    
            }
    
            a.recycle();
    

    3、重写onMeasure方法,这一步其实要重点看,前面我们提到View的3种测量模式,因为View类默认的onMeasure()方法只支持EXACTLY模式,所以如果我们想要在使用自定义View的时候可以设置wrap_content,那么必须重写onMeasure方法获取宽高。大家也可以试试,如果不重写onMeasure方法,然后设置宽高为wrap_content,你会看到效果的。

    @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    
            int widthMode = MeasureSpec.getMode(widthMeasureSpec);
            int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    
            int widthSize = MeasureSpec.getSize(widthMeasureSpec);
            int heightSize = MeasureSpec.getSize(heightMeasureSpec);
    
            int width;
            int height;
    
            if (widthMode == MeasureSpec.EXACTLY) {
                width = widthSize;
            } else {
                mPaint.setTextSize(mTextSize);
                mPaint.getTextBounds(mText, 0, mText.length(), mBound);
                float textWidth = mBound.width();
                int desired = (int) (getPaddingLeft() + textWidth + getPaddingRight());
                width = desired;
            }
    
            if (heightMode == MeasureSpec.EXACTLY) {
                height = heightSize;
            } else {
                mPaint.setTextSize(mTextSize);
                mPaint.getTextBounds(mText, 0, mText.length(), mBound);
                float textHeight = mBound.height();
                int desired = (int) (getPaddingTop() + textHeight + getPaddingBottom());
                height = desired;
            }
    
            setMeasuredDimension(width, height);
        }
    

    4、重写onDraw()方法。

        @Override
        protected void onDraw(Canvas canvas) {
    
            mPaint.setColor(Color.YELLOW);
            canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mPaint);
    
            mPaint.setColor(mTextColor);
            canvas.drawText(mText, getWidth() / 2 - mBound.width() / 2, getHeight() / 2 + mBound.height() / 2, mPaint);
        }
    

    5、布局文件

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:test="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:orientation="vertical"
        android:padding="10dp">
    
        <com.jackaltsc.mydemocustomview.myview.MyXTextView
            android:id="@+id/mXtv_test"
            android:layout_width="150dp"
            android:layout_height="100dp"
            android:layout_marginTop="20dp"
            android:gravity="center"
            test:text="文本3"
            test:textColor="#0000ff"
            test:textSize="20sp" />
    </LinearLayout>
    
    

    四、自定义ViewGroup解析

    自定义ViewGroup主要是为了对其子View进行管理,通常需要重写onMeasure()方法对子View进行测量,重写onLayout()方法确定子View的位置等等。由于篇幅关系,这里就不贴代码了,大家可以看Demo里继承自ViewGroup的MyFlowView。

    自定义ViewGroup

    五、源码链接

    自定义View简单Demo,https://git.oschina.net/tanshicheng/DemoCustomView.git

    相关文章

      网友评论

        本文标题:Android学习笔记13 自定义View完全解析

        本文链接:https://www.haomeiwen.com/subject/zgprjttx.html