本文首发于微信公众号「后厂技术官」
前言 学习了以上的文章后,接下来我们来讲讲自定义View,自定义View一直被认为是高手掌握的技能,因为情况太多,想实现的效果又变化多端,但它也要遵循一定的规则,我们要讲的就是这个规则,至于那些变化多端的酷炫的效果就由各位来慢慢发挥了。但是需要注意的是凡事都要有个度,自定义View毕竟不是规范的控件,如果不设计好不考虑性能反而会适得其反,另外适配起来可能也会产生问题,笔者的建议是如果能用系统控件的还是尽量用系统控件。
1.自定义View简介 自定义View按照笔者的划分,分为两大类,一种是自定义View,一种是自定义ViewGroup;其中自定义View又分为继承View和继承系统控件两种。这篇文章首先先了解下两大类的其中一种:自定义View。
2.继承系统控件的自定义View 这种自定义View在系统控件的基础上进行拓展,一般是添加新的功能或者修改显示的效果,一般情况下我们在onDraw()方法中进行处理。这里举一个简单的例子:
public class InvalidTextView extends TextView { private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); public InvalidTextView (Context context) { super (context); initDraw(); } public InvalidTextView (Context context, AttributeSet attrs) { super (context, attrs); initDraw(); } public InvalidTextView (Context context, AttributeSet attrs, int defStyleAttr) { super (context, attrs, defStyleAttr); initDraw(); } private void initDraw () { mPaint.setColor(Color.RED); mPaint.setStrokeWidth((float ) 1.5 ); } @Override protected void onDraw (Canvas canvas) { super .onDraw(canvas); int width = getWidth(); int height = getHeight(); canvas.drawLine(0 , height / 2 , width, height / 2 , mPaint); } }
这个自定义View继承TextView,并且在onDraw()方法中画了一条红色的横线,接下来在布局中引用这个InvalidTextView:
<com.example.liuwangshu.mooncustomview.InvalidTextView android:id ="@+id/iv_text" android:layout_width ="200dp" android:layout_height ="100dp" android:background ="@android:color/holo_blue_light" android:gravity ="center" android:textSize ="16sp" android:layout_centerHorizontal ="true" />
运行程序看看效果:
3.继承View的自定义View 与上面的继承系统控件的自定义View不同,继承View的自定义View实现起来要稍微复杂一些,不只是要实现onDraw()方法,而且在实现过程中还要考虑到wrap_content属性以及padding属性的设置;为了方便配置自己的自定义View还会对外提供自定义的属性,另外如果要改变触控的逻辑,还要重写onTouchEvent()等触控事件的方法。
简单实现继承View的自定义View 按照上面的例子我们再写一个RectView类继承View来画一个正方形:
public class RectView extends View { private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); private int mColor=Color.RED; public RectView (Context context) { super (context); initDraw(); } public RectView (Context context, AttributeSet attrs) { super (context, attrs); initDraw(); } public RectView (Context context, AttributeSet attrs, int defStyleAttr) { super (context, attrs, defStyleAttr); initDraw(); } private void initDraw () { mPaint.setColor(mColor); mPaint.setStrokeWidth((float ) 1.5 ); } @Override protected void onDraw (Canvas canvas) { super .onDraw(canvas); int width = getWidth(); int height = getHeight(); canvas.drawRect(0 , 0 , width, height, mPaint); } }
在布局中引用RectView:
<com.example.liuwangshu.mooncustomview.RectView android:id ="@+id/rv_rect" android:layout_width ="200dp" android:layout_height ="200dp" android:layout_below ="@id/iv_text" android:layout_marginTop ="50dp" android:layout_centerHorizontal ="true" />
运行程序查看效果:
对padding属性进行处理 如果我在布局文件中设置pading属性,发现没有任何的作用,看来还得对padding属性进行处理,只需要在onDraw()方法中稍加修改就可以了,在绘制正方形的时候考虑到padding属性就可以了:
@Override protected void onDraw (Canvas canvas) { super .onDraw(canvas); int paddingLeft=getPaddingLeft(); int paddingRight=getPaddingRight(); int paddingTop=getPaddingTop(); int paddingBottom=getPaddingBottom(); int width = getWidth()-paddingLeft-paddingRight; int height = getHeight()-paddingTop-paddingBottom; canvas.drawRect(0 +paddingLeft, 0 +paddingTop, width+paddingRight, height+paddingBottom, mPaint); }
修改布局文件加入padding属性:
<com.example.liuwangshu.mooncustomview.RectView android:id ="@+id/rv_rect" android:layout_width ="200dp" android:layout_height ="200dp" android:layout_below ="@id/iv_text" android:layout_marginTop ="50dp" android:layout_centerHorizontal ="true" android:padding ="10dp" />
运行程序看效果:
对wrap_content属性进行处理 修改布局文件,让RectView的宽度分别为wrap_content和match_parent效果都是一样的:
导致这种情况的原因请查看Android View体系(七)从源码解析View的measure流程 这篇文章。对于这种情况需要我们在onMeasure()方法中指定一个默认的宽和高,在设置wrap_content属性时设置此默认的宽和高就可以了:
@Override protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) { super .onMeasure(widthMeasureSpec, heightMeasureSpec); int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); int widthSpecSize=MeasureSpec.getSize(widthMeasureSpec); int heightSpecSize=MeasureSpec.getSize(heightMeasureSpec); if (widthSpecMode==MeasureSpec.AT_MOST&&heightSpecMode==MeasureSpec.AT_MOST){ setMeasuredDimension(400 ,400 ); }else if (widthSpecMode==MeasureSpec.AT_MOST){ setMeasuredDimension(400 ,heightSpecSize); }else if (heightSpecMode==MeasureSpec.AT_MOST){ setMeasuredDimension(widthSpecSize,400 ); } }
需要注意的是setMeasuredDimension()方法接收的参数的单位是px,来看看效果:
自定义属性 android系统的控件以android开头的比如android:layout_width,这些都是系统自带的属性,为了方便配置RectView的属性,我们也可以自定义属性,首先在values目录下创建 attrs.xml:
<?xml version="1.0" encoding="utf-8"?> <resources > <declare-styleable name ="RectView" > <attr name ="rect_color" format ="color" /> </declare-styleable > </resources >
这个配置文件定义了名为RectView的自定义属性组合,我们定义了rect_color属性,它的格式为color,接下来在RectView的构造函数中解析自定义属性的值:
public RectView (Context context, AttributeSet attrs) { super (context, attrs); TypedArray mTypedArray=context.obtainStyledAttributes(attrs,R.styleable.RectView); mColor=mTypedArray.getColor(R.styleable.RectView_rect_color,Color.RED); mTypedArray.recycle(); initDraw(); }
最后修改布局文件:
<com.example.liuwangshu.mooncustomview.RectView xmlns:app ="http://schemas.android.com/apk/res-auto" android:id ="@+id/rv_rect" android:layout_width ="wrap_content" android:layout_height ="200dp" android:layout_below ="@id/iv_text" android:layout_marginTop ="50dp" android:layout_centerHorizontal ="true" android:padding ="10dp" app:rect_color ="@android:color/holo_blue_light" />
使用自定义属性需要添加schemas: xmlns:app=”http://schemas.android.com/apk/res-auto",其中app是 我们自定义的名字,最后我们配置新定义的app:rect_color属性为android:color/holo_blue_light
最后贴出RectView的完整代码:
package com.example.liuwangshu.mooncustomview;import android.content.Context;import android.content.res.TypedArray;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.util.AttributeSet;import android.view.View;public class RectView extends View { private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); private int mColor=Color.RED; public RectView (Context context) { super (context); initDraw(); } public RectView (Context context, AttributeSet attrs) { super (context, attrs); TypedArray mTypedArray=context.obtainStyledAttributes(attrs,R.styleable.RectView); mColor=mTypedArray.getColor(R.styleable.RectView_rect_color,Color.RED); mTypedArray.recycle(); initDraw(); } public RectView (Context context, AttributeSet attrs, int defStyleAttr) { super (context, attrs, defStyleAttr); initDraw(); } private void initDraw () { mPaint.setColor(mColor); mPaint.setStrokeWidth((float ) 1.5 ); } @Override protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) { super .onMeasure(widthMeasureSpec, heightMeasureSpec); int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); int widthSpecSize=MeasureSpec.getSize(widthMeasureSpec); int heightSpecSize=MeasureSpec.getSize(heightMeasureSpec); if (widthSpecMode==MeasureSpec.AT_MOST&&heightSpecMode==MeasureSpec.AT_MOST){ setMeasuredDimension(400 ,400 ); }else if (widthSpecMode==MeasureSpec.AT_MOST){ setMeasuredDimension(400 ,heightSpecSize); }else if (heightSpecMode==MeasureSpec.AT_MOST){ setMeasuredDimension(widthSpecSize,400 ); } } @Override protected void onDraw (Canvas canvas) { super .onDraw(canvas); int paddingLeft = getPaddingLeft(); int paddingRight = getPaddingRight(); int paddingTop = getPaddingTop(); int paddingBottom = getPaddingBottom(); int width = getWidth() - paddingLeft - paddingRight; int height = getHeight() - paddingTop - paddingBottom; canvas.drawRect(0 + paddingLeft, 0 + paddingTop, width + paddingRight, height + paddingBottom, mPaint); } }
github源码下载