我们为什么要自定义View ?
已有的UI控件不能满足业务的需求。
自定义View的主要实现方式:
1、直接继承已有控件
2、继承ViewGroup实现组合控件
3、继承View实现完全自定义的控件
自定义View的重要方法:
1、onMeasure 控件的尺寸(需要对控件尺寸进行设置时实现)
2、onLayout 对控件进行排版,排列子控件(继承ViewGroup的方式必须实 现)
3、onDraw 在控件上进行图形绘制(需要自定义控件的显示效果时实现)
在这篇文章中,我主要解压介绍onMeasure和onDraw方法,在下一篇中,利用流式布局介绍onLayout方法。
在继承View时,会重写构造方法,我们一般选择两个构造方法。
构造方法的规则:
自定义View必须实现构造方法:
控件类名(Context context) 使用代码创建控件对象时候使用
控件类名(Context context,AttributeSet attrs) 在布局中使用时调用
onDraw方法 实现图形的绘制,
通过Canvas对象实现
Canvas(画布)的主要方法:
drawLine 画线
drawRect 画矩形
drawCircle 画圆形
drawText 画文字 .....
Paint(画笔)的主要方法:
setColor 设置颜色
setStrokeWidth 设置描线的宽度
注意:尽量不要在onDraw中创建对象,onDraw会被频繁调用(500ms左右
这里写一个倒计时的例子。我们一步一步来完成。
先写出一个圆中有数数字的样式。
布局文件 activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent" tools:context="com.example.my_view.MainActivity">
<com.example.my_view.Counter
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
Counter.java
package com.example.my_view;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
/**
* 自定义控件
*/
public class Counter extends View {
private Paint paint;
public Counter(Context context) {
super(context);
}
public Counter(Context context, AttributeSet attrs) {
super(context, attrs);
//因为自定义布局是写在xml文件中 ,所有会调用该方法。
//初始化画笔
init();
}
private void init(){
//创建画笔
paint = new Paint();
//设置粗细
paint.setStrokeWidth(2);
//设置平滑度
paint.setAntiAlias(true);
//设置文字大小
paint.setTextSize(40);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//画笔颜色
paint.setColor(Color.RED);
canvas.drawCircle(100,100,50,paint);
//重置颜色
paint.setColor(Color.GREEN);
canvas.drawText("10",75,110,paint);
}
}
这里MainActivity.java中我什么都没有写,所有这里就不去看代码了。
效果图:
我们发现,画笔的颜色和一些基本设置都在自定义布局中来设置的。我们都知道,我们这些数据,都是在xml布局文件中设置的。那应该怎么去自定义属性呢?这里分几步去完成。
自定义属性
1、在values下添加attrs.xml自定义属性的资源文件
2、在attrs.xml中添加属性:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--自定义属性的集合 name为自定义控件的类名-->
<declare-styleable name="Notepad">
<!--自定义属性 name是属性名,format为属性值类型-->
<attr name="lineColor" format="color"/>
<attr name="lineHeight" format="integer"/>
</declare-styleable>
</resources>
3、在布局中的控件上添加属性:
在根布局上添加appNs
在控件上使用自定义属性:app:属性名 = "值"
4、在自定义View中读取自定义属性
//从布局文件中读取自定义属性
//obtainStyledAttributes获得属性集合,
//第一个参数是AttributeSet属性集合,第二个参数是attrs中定义的属性集 合名称
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.Notepad);
//再从TypedArray中获得属性值
int color = typedArray.getColor(R.styleable.Notepad_lineColor, Color.BLUE);
int line = typedArray.getInteger(R.styleable.Notepad_lineHeight, 3);
//将TypedArray回收
typedArray.recycle();
实现用户触摸的监听的方式:
1、重写onTouchEvent方法 (自定义视图一般使用这种)
2、调用setOnTouchEventListener方法 (调用者去实现)
实现代码:
改变布局文件:
资源文件:attrs.xml文件
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="Counter">
<attr name="bgColor" format="color"/>
<attr name="textColor" format="color"/>
<attr name="number" format="integer"/>
</declare-styleable>
</resources>
Counter.java
package com.example.my_view;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
/**
* 自定义控件
*/
public class Counter extends View {
private Paint paint;
private int bgColor;
private int number;
private int textColor;
private int count; //该变量用来倒计时
private Thread thread; //利用线程来让数字跑起来
public Counter(Context context) {
super(context);
}
public Counter(Context context, AttributeSet attrs) {
super(context, attrs);
//因为自定义布局是写在xml文件中 ,所有会调用该方法。
//初始化画笔
init(attrs);
}
private void init(AttributeSet attrs) {
if (attrs != null) {
//获取属性文件中 属性的集合
TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.Counter);
//获取背景颜色 默认值:Color.YELLOW
bgColor = typedArray.getColor(R.styleable.Counter_bgColor, Color.YELLOW);
//获取字体颜色 默认值: Color.RED
textColor = typedArray.getColor(R.styleable.Counter_textColor, Color.RED);
//获取倒计时的数据 默认值:5
number = typedArray.getInteger(R.styleable.Counter_number, 5);
count = number;
//回收集合
typedArray.recycle();
}
//创建画笔
paint = new Paint();
//设置粗细
paint.setStrokeWidth(2);
//设置平滑度
paint.setAntiAlias(true);
//设置文字大小
paint.setTextSize(40);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//画笔颜色
paint.setColor(bgColor);
canvas.drawCircle(100, 100, 50, paint);
//重置颜色
paint.setColor(textColor);
canvas.drawText("" + count, 75, 110, paint);
}
/**
* 实现触摸事件,让数字开始动起来
* @param event
* @return
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
//启动线程倒计时
if (thread == null) {
thread = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
//重绘
postInvalidate();
if (count > 0) {
count--;
} else {
break;
}
//让线程沉睡一秒,
//这里没有用线程的sleep方法,因为用线程的sleep方法,还要处理异常
SystemClock.sleep(1000);
}
}
});
//启动线程
thread.start();
}
}
return super.onTouchEvent(event);
}
}
效果图:
通过这个例子,应该可以了解onDraw方法的使用,下面看下onMeasure方法介绍。
我们在布局文件中添加一个控件:
运行后,你会发现,这个控件根本就没有显示出来,在上面应该也可以看到,我自定义控件设置的高是wrap_content,利用的布局是LinearLayout,那为什么没有显示出该控件呢?
因为我们在自定义控件时,根本就没有给控件设置大小,现在我们可以把onMeasure方法加上,让TextView控件显示出来。
在Counter.java添加如下代码:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//设置宽和高,默认值:200
this.setMeasuredDimension(getSize(widthMeasureSpec,200),
getSize(heightMeasureSpec,200));
}
/**
* 获取 控件的大小
* @param spec 实际值
* @param defaultSize 默认值
*/
private int getSize(int spec, int defaultSize){
/*//获取尺寸模式
模式有三种模式
EXACTLY:一般是设置了明确的值或者是MATCH_PARENT
AT_MOST:表示子布局限制在一个最大值内,一般为WARP_CONTENT
UNSPECIFIED:表示子布局想要多大就多大,很少使用*/
int mode = MeasureSpec.getMode(spec);
//获取尺寸大小
int size = MeasureSpec.getSize(spec);
// n 表示最后控件的大小 先等于默认值
int n = defaultSize;
//如果是精准模式,则大小与size相同
if(mode == MeasureSpec.EXACTLY){
n = size;
}else{
//吐过是AT_MOST模式,就选默认值和size中小的哪一个
if(mode == MeasureSpec.AT_MOST){
n = Math.min(size,defaultSize);
}
}
//返回尺寸大小
return n;
}
运行后:
onDraw方法和onMeasure两个方法就介绍到这里。下次利用流布局来介绍一下onLayout方法的使用。