通过一个简单的实例来了解自定义View的流程
新建CircleView继承View,并重写onDraw方法
package com.example.smily.customview01;
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 CircleView extends View {
private int mColor = Color.BLUE;
private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
public CircleView(Context context) {
super(context);
init();
}
public CircleView(Context context, AttributeSet attrs) {
this(context, attrs,0);
init();
}
public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mPaint.setColor(mColor);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int width = getWidth();
int height = getHeight();
int radius = Math.min(height, width) / 2;
canvas.drawCircle( width / 2, height / 2, radius, mPaint);
//canvas.drawCircle(radius, radius, radius, mPaint);
}
}
上边代码实现了一个具有圆形效果的自定义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"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical">
<com.example.smily.customview01.CircleView
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_margin="10dp"
android:background="@color/colorAccent"/>
</LinearLayout>
效果图如下:
和我们的预期效果是一样的。
接下来我们调整CircleView的布局参数,为其设置20dp的padding,如下所示:
<com.example.smily.customview01.CircleView
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_margin="10dp"
android:padding="20dp"
android:background="@color/colorAccent"/>
我们发现运行效果图与上边一样,没有发生任何变化。
针对padding的问题我们需要对onDrawn作下修改,修改后代码如下:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
final int paddingLeft = getPaddingLeft();
final int paddingRight = getPaddingRight();
final int paddingTop = getPaddingTop();
final int paddingBottom = getPaddingBottom();
int width = getWidth() - getPaddingLeft() - getPaddingRight();
int height = getHeight() - getPaddingTop() - getPaddingBottom();
int radius = Math.min(height, width) / 2;
canvas.drawCircle(paddingLeft + width / 2, paddingTop + height / 2, radius, mPaint);
}
上边的代码在绘制的时候考虑了View四周的空白,从而显示出了padding的效果。
针对上边的布局再运行一下,得到如下效果图:
接下来我们再对布局进行调整,将layout_width的match_parent改为wrap_content
<com.example.smily.customview01.CircleView
android:layout_width="wrap_content"
android:layout_height="200dp"
android:layout_margin="10dp"
android:padding="20dp"
android:background="@color/colorAccent"/>
运行后发现结果再次出现异常,效果并没有实现wrap_content,而是依然与match_parent效果一样。
这时我们应该想到是由于onMeasure()方法的问题,在上篇文章中我们介绍了onMeasure()方法,因此,可以重写onMeasure()方法如下:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSpecMode=MeasureSpec.getMode(widthMeasureSpec);
int widthSpecSize=MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode=MeasureSpec.getMode(heightMeasureSpec);
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);
}
}
接下来调整布局:
<com.example.smily.customview01.CircleView
android:layout_width="wrap_content"
android:layout_height="200dp"
android:layout_margin="10dp"
android:padding="20dp"
android:background="@color/colorAccent"/>
运行效果图如下:
此时参数wrap_content和padding均生效了。
最后为了让View更加容易使用,很多情况下我们需要提供自定义属性。
自定义属性遵循如下几步:
(1)在values目录下创建自定义属性的XML命名为attrs.xml(文件的名字没有限制,可随意命名),文件内容如下:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="CircleView">
<attr name="my_color" format="color"></attr>
</declare-styleable>
</resources>
上边的XML中声明了一个自定义属性集合”CircleView”,在这个集合里定义了一个格式为color的属性”my_color“,这里的格式color指的是颜色,自定义属性还有其他格式,如reference指资源id,dimension指尺寸,string、integer和boolean指基本数据类型。
(2)在View的构造方法中解析自定义属性的值,并作处理。代码如下:
public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CircleView);
mColor=typedArray.getColor(R.styleable.CircleView_my_color,Color.BLUE);
typedArray.recycle();
init();
}
上边代码首先加载了自定义集合CircleVIew,接着解析CircleView属性集合中的my_color属性,它的id为R.styleable.CircleView_my_color。如果在布局文件中没有指定circle_color这个属性,则默认选择蓝色。
(3)在自定义布局中使用自定义属性:
<com.example.smily.customview01.CircleView
android:layout_width="wrap_content"
android:layout_height="200dp"
android:layout_margin="10dp"
android:padding="20dp"
app:my_color="#fff"
android:background="@color/colorAccent"/>
效果图如下:
至此一个完整的自定义View已经完成。
以上实例参考自任玉刚《Android开发艺术探索》