前言
学习了以上的文章后,接下来我们来讲讲自定义View,自定义View一直被认为是高手掌握的技能,因为情况太多,想实现的效果又变化多端,但它也要遵循一定的规则,我们要讲的就是这个规则,至于那些变化多端的酷炫的效果就由各位来慢慢发挥了。但是需要注意的是凡事都要有个度,自定义View毕竟不是规范的控件,如果不设计好不考虑性能反而会适得其反,另外适配起来可能也会产生问题,笔者的建议是如果能用系统控件的还是尽量用系统控件。
1.自定义View简介
自定义View按照笔者的划分,分为两大类,一种是自定义View,一种是自定义ViewGroup;其中自定义View又分为继承View和继承系统控件两种。这篇文章首先先了解下两大类的其中一种:自定义View。
2.继承系统控件的自定义View
这种自定义View在系统控件的基础上进行拓展,一般是添加新的功能或者修改显示的效果,一般情况下我们在onDraw()方法中进行处理。这里举一个简单的例子:
<code class="hljs java">publicclass InvalidTextView extends TextView {
privatePaint mPaint = newPaint(Paint.ANTI_ALIAS_FLAG);
publicInvalidTextView(Context context) {
super(context);
initDraw();
}
publicInvalidTextView(Context context, AttributeSet attrs) {
super(context, attrs);
initDraw();
}
publicInvalidTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initDraw();
}
privatevoid initDraw() {
mPaint.setColor(Color.RED);
mPaint.setStrokeWidth((float)1.5);
}
@Override
protectedvoid onDraw(Canvas canvas) {
super.onDraw(canvas);
intwidth = getWidth();
intheight = getHeight();
canvas.drawLine(0, height /2, width, height /2, mPaint);
}
}</code>
这个自定义View继承TextView,并且在onDraw()方法中画了一条红色的横线,接下来在布局中引用这个InvalidTextView:
1
<code class="hljs avrasm"> <com.example.liuwangshu.mooncustomview.invalidtextview android:id="@+id/iv_text" android:textsize="16sp" android:layout_width="200dp" android:layout_height="100dp" android:layout_centerhorizontal="true" android:gravity="center" android:background="@android:color/holo_blue_light"></com.example.liuwangshu.mooncustomview.invalidtextview></code>
2.继承系统控件的自定义View
这种自定义View在系统控件的基础上进行拓展,一般是添加新的功能或者修改显示的效果,一般情况下我们在onDraw()方法中进行处理。这里举一个简单的例子:
?
<code class="hljs java">publicclass InvalidTextView extends TextView {
privatePaint mPaint = newPaint(Paint.ANTI_ALIAS_FLAG);
publicInvalidTextView(Context context) {
super(context);
initDraw();
}
publicInvalidTextView(Context context, AttributeSet attrs) {
super(context, attrs);
initDraw();
}
publicInvalidTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initDraw();
}
privatevoid initDraw() {
mPaint.setColor(Color.RED);
mPaint.setStrokeWidth((float)1.5);
}
@Override
protectedvoid onDraw(Canvas canvas) {
super.onDraw(canvas);
intwidth = getWidth();
intheight = getHeight();
canvas.drawLine(0, height /2, width, height /2, mPaint);
}
}</code>
3.继承View的自定义View
与上面的继承系统控件的自定义View不同,继承View的自定义View实现起来要稍微复杂一些,不只是要实现onDraw()方法,而且在实现过程中还要考虑到wrap_content属性以及padding属性的设置;为了方便配置自己的自定义View还会对外提供自定义的属性,另外如果要改变触控的逻辑,还要重写onTouchEvent()等触控事件的方法。
简单实现继承View的自定义View
按照上面的例子我们再写一个RectView类继承View来画一个正方形:
?
<code class="hljs java">publicclass RectView extends View {
privatePaint mPaint = newPaint(Paint.ANTI_ALIAS_FLAG);
privateint mColor=Color.RED;
publicRectView(Context context) {
super(context);
initDraw();
}
publicRectView(Context context, AttributeSet attrs) {
super(context, attrs);
initDraw();
}
publicRectView(Context context, AttributeSet attrs, intdefStyleAttr) {
super(context, attrs, defStyleAttr);
initDraw();
}
privatevoid initDraw() {
mPaint.setColor(mColor);
mPaint.setStrokeWidth((float)1.5);
}
@Override
protectedvoid onDraw(Canvas canvas) {
super.onDraw(canvas);
intwidth = getWidth();
intheight = getHeight();
canvas.drawRect(0,0, width, height, mPaint);
}
}</code>
在布局中引用RectView:
1
<code class="hljs avrasm"> <com.example.liuwangshu.mooncustomview.rectview android:id="@+id/rv_rect" android:layout_width="200dp" android:layout_height="200dp" android:layout_centerhorizontal="true" android:layout_margintop="50dp" android:layout_below="@id/iv_text"></com.example.liuwangshu.mooncustomview.rectview></code>
对padding属性进行处理
如果我在布局文件中设置pading属性,发现没有任何的作用,看来还得对padding属性进行处理,只需要在onDraw()方法中稍加修改就可以了,在绘制正方形的时候考虑到padding属性就可以了:
?
<code class="hljs java">@Override
protectedvoid onDraw(Canvas canvas) {
super.onDraw(canvas);
intpaddingLeft=getPaddingLeft();
intpaddingRight=getPaddingRight();
intpaddingTop=getPaddingTop();
intpaddingBottom=getPaddingBottom();
intwidth = getWidth()-paddingLeft-paddingRight;
intheight = getHeight()-paddingTop-paddingBottom;
canvas.drawRect(0+paddingLeft,0+paddingTop, width+paddingRight, height+paddingBottom, mPaint);
}</code>
修改布局文件加入padding属性:
<code class="hljs avrasm"> <com.example.liuwangshu.mooncustomview.rectview android:id="@+id/rv_rect" android:layout_width="200dp" android:layout_height="200dp" android:layout_centerhorizontal="true" android:layout_margintop="50dp" android:layout_below="@id/iv_text" android:padding="10dp"></com.example.liuwangshu.mooncustomview.rectview></code>
对wrap_content属性进行处理
修改布局文件,让RectView的宽度分别为wrap_content和match_parent效果都是一样的:
导致这种情况的原因请查看Android View体系(七)从源码解析View的measure流程这篇文章。对于这种情况需要我们在onMeasure()方法中指定一个默认的宽和高,在设置wrap_content属性时设置此默认的宽和高就可以了 @Override
protectedvoid onMeasure(intwidthMeasureSpec, intheightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
intwidthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
intheightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
intwidthSpecSize=MeasureSpec.getSize(widthMeasureSpec);
intheightSpecSize=MeasureSpec.getSize(heightMeasureSpec);
if(widthSpecMode==MeasureSpec.AT_MOST&&heightSpecMode==MeasureSpec.AT_MOST){
setMeasuredDimension(400,400);
}elseif(widthSpecMode==MeasureSpec.AT_MOST){
setMeasuredDimension(400,heightSpecSize);
}elseif(heightSpecMode==MeasureSpec.AT_MOST){
setMeasuredDimension(widthSpecSize,400);
}
}</code>
需要注意的是setMeasuredDimeon()方法接数的单位是px,来看看效果:
自定义属性
android系统的控件以android开头的比如android:layout_width,这些都是系统自带的属性,为了方便配置RectView的属性,我们也可以自定义属性,首先在values目录下创建 attrs.xml
这个配置义了名为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();
}
最后修改布局文
<code class="hljs java">packagecom.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 extendsView {
privatePaint mPaint = newPaint(Paint.ANTI_ALIAS_FLAG);
privateint mColor=Color.RED;
publicRectView(Context context) {
super(context);
initDraw();
}
publicRectView(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();
}
publicRectView(Context context, AttributeSet attrs, intdefStyleAttr) {
super(context, attrs, defStyleAttr);
initDraw();
}
privatevoid initDraw() {
mPaint.setColor(mColor);
mPaint.setStrokeWidth((float)1.5);
}
@Override
protectedvoid onMeasure(intwidthMeasureSpec, intheightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
intwidthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
intheightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
intwidthSpecSize=MeasureSpec.getSize(widthMeasureSpec);
intheightSpecSize=MeasureSpec.getSize(heightMeasureSpec);
if(widthSpecMode==MeasureSpec.AT_MOST&&heightSpecMode==MeasureSpec.AT_MOST){
setMeasuredDimension(400,400);
}elseif(widthSpecMode==MeasureSpec.AT_MOST){
setMeasuredDimension(400,heightSpecSize);
}elseif(heightSpecMode==MeasureSpec.AT_MOST){
setMeasuredDimension(widthSpecSize,400);
}
}
@Override
protectedvoid onDraw(Canvas canvas) {
super.onDraw(canvas);
intpaddingLeft = getPaddingLeft();
intpaddingRight = getPaddingRight();
intpaddingTop = getPaddingTop();
intpaddingBottom = getPaddingBottom();
intwidth = getWidth() - paddingLeft - paddingRight;
intheight = getHeight() - paddingTop - paddingBottom;
canvas.drawRect(0+ paddingLeft, 0 + paddingTop, width + paddingRight, height + paddingBottom, mPaint);
}
}</code>