相关文章
Android View体系(一)视图坐标系
Android View体系(二)实现View滑动的六种方法
Android View体系(三)属性动画
Android View体系(四)从源码解析Scroller
Android View体系(五)从源码解析View的事件分发机制
Android View体系(六)从源码解析Activity的构成
Android View体系(七)从源码解析View的measure流程
Android View体系(八)从源码解析View的layout和draw流程
前言
学习了以上的文章后,接下来我们来讲讲自定义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); //提取RectView属性集合的rect_color属性,如果没设置默认值为Color.RED 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); //提取RectView属性集合的rect_color属性,如果没设置默认值为Color.RED 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源码下载