自定义View入门
long time no see,这次写一个灰常简单的一个自定义的view.当然,虽然说简单,但是麻雀虽小五脏俱全,自定义view的流程基本涵盖了.
从简单入手,然后大致了解一下view的绘制过程.这个view有多简单呢,实际上咱们就画一个圆,然后上个色.
下面开始:
public class CircleView extends View {
private static final String TAG = "CircleView";
/**
* 定义一个默认的圆形颜色
*/
private static final int DEFAULT_COLOR = Color.GRAY;
/**
* 圆形的颜色
*/
private int color = DEFAULT_COLOR;
/**
* 画笔
*/
private Paint mPaint;
public CircleView(Context context) {
this(context, null);
}
public CircleView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initPaint();
}
private void initPaint() {
// 设置画笔抗锯齿,可以让图形的边缘更平滑.如果需要画的图形四四方方的那就没必要设置了
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
// 设置画笔的颜色
mPaint.setColor(color);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 获取控件本身的宽和高,并选择较短的一边的一半作为圆的半径
int width = getWidth();
int height = getHeight();
int radius = Math.min(width, height) / 2;
// 画圆,前两个参数确定圆心的位置,之后圆的半径,画笔
canvas.drawCircle(width / 2, height / 2, radius, mPaint);
}
}
很简单吧,那么看一下布局文件
<?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:padding="10dp"
android:orientation="vertical">
<com.lanou3g.drawview.widget.CircleView
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
布局也非常简单对吧,那好,看一下预览,还算是正常的吧
那好,接下来,咱们自定义一下这个圆圈的颜色,也就是需要在布局中有一条属性,可以控制这个view的颜色.
创建自定义属性文件,在res/values下,创建一个资源文件,命名为attrs,如下图:
文件里面就可以创建自定义的属性,继续贴代码:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--一般来说,为某个控件自定义属性,name就跟控件名字一样-->
<declare-styleable name="CircleView">
<!--定义一条属性叫circleColor,规定参数填颜色值-->
<attr name="circleColor" format="color"/>
</declare-styleable>
</resources>
属性定义好了,就可以到代码中把颜色值取出,然后设置给画笔就可以了,继续代码
public class CircleView extends View {
/** 这部分代码与上文一样,省略... */
public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// 获取自定义属性
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleView);
// 取出对应的值设置给color,并提供一个默认值
color = a.getColor(R.styleable.CircleView_circleColor, DEFAULT_COLOR);
initPaint();
}
private void initPaint() {
// 设置画笔抗锯齿,可以让图形的边缘更平滑.如果需要画的图形四四方方的那就没必要设置了
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
// 设置画笔的颜色
mPaint.setColor(color);
}
/** 画圆部分与上文一样,省略 */
}
自定义的属性获取就写完了.然后在布局文件中,设置命名空间,目的为可以获取到自定义的属性,然后在CircleView中使用自定义的属性为圆上色.当然,命名空间的名字我写的是view,这个可以根据自己的爱好命名.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:view="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="10dp"
android:orientation="vertical">
<com.lanou3g.drawview.widget.CircleView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
view:circleColor="#0088ff"/>
</LinearLayout>
ok,看预览,可以看到颜色咱们可以自定义了
实际上这个自定义的控件是灰常不专业的,不知道大家注意到没有.咱们在布局文件中给这个控件设置的宽高都为wrap_content,那显示成这么大肯定是不正常的.实际上虽然给的是wrap_content,但是系统是按照match_parent来计算的.那为什么会出现这种情况呢?给10秒钟大家思考一下
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
思考完了吧,那记住一句话就得了,对于直接继承自view的自定义组件,如果不在代码中对wrap_content进行处理,那么系统就会把它理解成match_parent,呵呵哒,气人不.
那接下来,咱们就处理一下吧.在自定义组件中,重写onMeasure()这个方法,处理的过程就在此方法中.思路就是,如果布局中的宽或高设置成了wrap_content,那么对应的宽高就给设置成188.
/** 其他部分的代码省略 */
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST){
setMeasuredDimension(188,188);
}else if (widthMode == MeasureSpec.AT_MOST){
setMeasuredDimension(188,heightSize);
}else if (heightMode == MeasureSpec.AT_MOST){
setMeasuredDimension(widthSize,188);
}
}
接下来再看一下预览(PS:Android Studio真是个好东西)
<com.lanou3g.drawview.widget.CircleView
android:layout_width="match_parent"
android:layout_height="wrap_content"
view:circleColor="#0088ff"/>
接下来另一个问题,写到现在这个程度的时候,大家可以试一下给这个控件加上margin属性和padding属性,看一下效果,给大家半个小时尝试一下
try {
Thread.sleep(30 * 60 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
擦擦擦
/** Application Not Responding !!!!! */
实际结果是,margin是生效的,而padding是不会生效的.因为margin属性是由父容器控制的,所以会生效.而padding是控件本身控制的,咱们没有进行处理,所有是无效的.那么接下来,继续处理一下padding.处理的过程也非常简单,在绘制的过程中,考虑一下padding即可.那么修改一下onDraw方法,如下:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 获取padding值
int paddingLeft = getPaddingLeft();
int paddingRight = getPaddingRight();
int paddingTop = getPaddingTop();
int paddingBottom = getPaddingBottom();
// 获取控件本身的宽和高,并选择较短的一边的一半作为圆的半径.计算时将padding考虑进去
int width = getWidth() - paddingLeft - paddingRight;
int height = getHeight() - paddingTop - paddingBottom;
int radius = Math.min(width, height) / 2;
// 画圆,前两个参数确定圆心的位置,之后圆的半径,画笔
canvas.drawCircle(paddingLeft + width / 2, paddingTop + height / 2, radius, mPaint);
}
布局文件
<com.lanou3g.drawview.widget.CircleView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:paddingTop="5dp"
android:paddingBottom="20dp"
view:circleColor="#0088ff"/>