本文主要是自定义一个可点击的TextView,并无多大的实际用处,主要是理清自定义View的步骤和思路。为后面自定义复杂的View做一些准备,不足之处还请大家多多指正,有抄袭的地方就当我抄袭吧。
一般来说,自定义View的步骤一般如下:
1. 定义View的xml属性
自定义View的属性,首先在res/values/下建立一个attrs.xml(其他文件名也可以), 在里面定义我们的属性和声明我们的整个样式。
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="CustomTitleView">
<attr name="titleText" format="string"/>
<attr name="titleColor" format="color"/>
<attr name="titleTextSize" format="dimension"/>
</declare-styleable>
</resources>
我们定义了文本内容titleText,字体颜色titleColor,字体大小titleTextSize这3个属性,format是值该属性的取值类型:
一共有:string,color,demension,integer,enum,reference,float,boolean,fraction,flag这几种类型,相关的含义如下:
string:字符串类型值
color:颜色值
demension: 尺寸值
integer: 整型值
float: 浮点数值
enum: 枚举值
reference:引用值,引用某一资源
boolean:布尔值
fraction:百分数值
flag: 位或运算
2. 定义CustomTitleView类
在构造方法中获取属性,这里主要是我们定义的上面三种属性。
//文本
private String mTitleText;
//文本颜色
private int mTitleColor;
//文本字体
private int mTitleTextSize;
//绘制时控制文本的范围
private Rect mRect;
//画笔
private Paint mPaint;
public CustomTitleView(Context context){
this(context,null);
}
public CustomTitleView(Context context, AttributeSet attrs){
this(context,attrs,0);
}
/**
* 获取自定义的样式属性,一般来说该构造方法是必须要有的
* @param context
* @param attrs
* @param defStyle
*/
public CustomTitleView(Context context,AttributeSet attrs,int defStyle){
super(context,attrs,defStyle);
init(context,attrs);
}
private void init(Context context,AttributeSet attrs){
/**
* 获取自定义的样式属性(从xml文件)
*/
TypedArray typedArray = context.obtainStyledAttributes(attrs,
R.styleable.CustomTitleView);
mTitleText = typedArray.getString(R.styleable.CustomTitleView_titleText);
//默认为黑色
mTitleColor = typedArray.getColor(R.styleable.CustomTitleView_titleColor, Color.BLACK);
//默认设置16sp,TypeValue可以把sp转化为px
mTitleTextSize = typedArray.getDimensionPixelSize(R.styleable.CustomTitleView_titleTextSize,
(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics()));
Log.d(TAG,"mTitleText:" + mTitleText);
Log.d(TAG,"mTitleColor:" + mTitleColor);
Log.d(TAG,"mTitleSize:" + mTitleTextSize);
typedArray.recycle();
//初始化绘笔
mPaint = new Paint();
//设置抗锯齿
mPaint.setAntiAlias(true);
mPaint.setTextSize(mTitleTextSize);
mPaint.setColor(mTitleColor);
//获得绘制文本的宽和高
mRect = new Rect();
mPaint.getTextBounds(mTitleText,0,mTitleText.length(),mRect);
}
可以看到,我们使用了TypeArray这个类,TypedArray可以很方便的便于我们去获取定义在xml文件中的属性。
3. 重写onMeasure()和onDraw()方法
onMeasure()方法主要处理wrap_content时的问题,以及处理好padding。
onDraw(Canvascanvas)方法需要注意,我们这个自定义的View到底要做成什么样子,就在里面调用画笔绘制成什么样子。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width;
int height;
//得到宽和高的测量模式和规格
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
mPaint.setTextSize(mTitleTextSize);
mPaint.getTextBounds(mTitleText,0,mTitleText.length(),mRect);
Log.d(TAG,"mRect.width()" + mRect.width());
if (widthMode == MeasureSpec.EXACTLY){
width = widthSize;
} else {
//加上左右两边的padding区域和字符的宽度
width = getPaddingLeft() + mRect.width() + getPaddingRight();
}
if (heightMode == MeasureSpec.EXACTLY){
height = heightSize;
} else {
//加上上下两边的padding区域和字符的高度
height = getPaddingTop() + mRect.height() + getPaddingBottom();
}
//设置测量尺寸
setMeasuredDimension(width,height);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//画一个矩形框,长宽为测量后的宽高
mPaint.setColor(Color.YELLOW);
canvas.drawRect(0,0,getMeasuredWidth(),getMeasuredHeight(),mPaint);
mPaint.setColor(mTitleColor);
//画在正中间
canvas.drawText(mTitleText,getMeasuredWidth()/2 - mRect.width()/2
,getMeasuredHeight()/2 + mRect.height()/2,mPaint);
}
4. 布局中使用的自定义View
注意,这里需要引入我们自己的命名空间才可以使用我们自定义的View属性。一般命令空间为
xmlns:xxx="http://schemas.android.com/apk/res/自己的包名 "
xmlns:custom="http://schemas.android.com/apk/res/com.customview"
……
<com.customview.ui.CustomTitleView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/custom_title_view"
android:paddingLeft="10px"
android:paddingRight="10px"
android:paddingTop="10px"
android:paddingBottom="10px"
custom:titleText="hello"
custom:titleColor="@color/colorAccent"
custom:titleTextSize="20sp"/>
到这里,我们自定义的View已经可以用了,比如我们就可以显示出来了。基本上算是比较完善了。
5. 完善CustomTitleView
为什么要继续完善了,其实发现我们自定义的CustomTitleView和普通的TextView差不多,我们可以给它增加点击事件,或者增加滑动事件。
想要增加点击事件,需要CustomTitleView实现View.OnClickListener接口,如果是想要监听滑动需要实现View.OnScrollChangeListener或其他滑动监听接口。这里以点击为例。
1. 实现View.OnClickListener接口,构造函数里面设置监听器。
public class CustomTitleView extends View implements View.OnClickListener{
……
/**
* 获取自定义的样式属性,一般来说是必须要有的
* @param context
* @param attrs
* @param defStyle
*/
public CustomTitleView(Context context,AttributeSet attrs,int defStyle){
super(context,attrs,defStyle);
init(context,attrs);
//设置监听器
this.setOnClickListener(this);
}
2. 定义内部接口OnClickListener,由外部实现其方法
public interface OnClickListener{
void onClick(View v);
}
3. 在CustomTitleView定义自定义监听器成员变量
//监听器
private OnClickListener mListener;
4. 重写View.OnClickListener的onClick()方法
在该方法中我们需要调用自定义监听器中的方法,并处理好后续逻辑@Override
public void onClick(View v) {
mListener.onClick(v);
//重新布局
requestLayout();
//该View重绘
postInvalidate();
}
当然还有其他的设置监听器等其他的后续操作,这里就不列出来了。现在我们的CustomTitleView已经可以相应我们的点击事件了。在Activity的onCreate()中写如下代码即可。
final CustomTitleView titleView = (CustomTitleView) findViewById(R.id.custom_title_view);
titleView.setOnClickListener(new CustomTitleView.OnClickListener() {
@Override
public void onClick(View v) {
titleView.setTitleText(titleView.randomText());
}
});
注意:
- attrs.xml里面的declare-styleable以及item,android会根据其在R.java中生成一些常量方便我们使用(aapt干的),本质上,我们可以不声明declare-styleable仅仅声明所需的属性即可。
- 我们在自定义View的时候,可以使用系统已经定义的属性
完整的源代码见github:点击打开链接