效果图
说起View这个东西,一直都是令人头疼的事情,用起来好使,但有时候系统带的View不够用时,就要自己去编写了,这时候就呵呵了.下面我们来学习一下吧.
1.首先在res/values下面新建attr.xml,代码:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="mTitleText" format="string" />
<attr name="mTitleTextColor" format="color" />
<attr name="mTitleTextSize" format="dimension" />
<declare-styleable name="CustomTitleView">
<attr name="mTitleText" />
<attr name="mTitleTextColor" />
<attr name="mTitleTextSize" />
</declare-styleable>
</resources>
其中上面三行声明了一些属性,还有属性所对应的类型
自定义样式
format是值该属性的取值类型一共有:string,color,demension,integer,enum,reference,float,boolean,fraction,flag;
"reference" //引用,ResourceID
"color" //颜色
"boolean" //布尔值
"dimension" //尺寸值
"float" //浮点值
"integer" //整型值
"string" //字符串
"fraction" //百分数,比如200%
declare-stylable声明了一个自定义属性的集合,name是对应的名字.可以想象成declare-stylable是一个箱子,里面放了很多属性.
1.自定义View
public class CustomTitleView extends View {
private String mTitleText;
private int mTitleTextColor;
private int mTitleTextSize;
/**
* 分别对应我们的自定义属性的三个属性值,等下用来接收那些属性值
*/
private Rect mBonds;
private Paint mPaint;
/**
* Rect是我们要占据的空间,一个矩形嘛
* Paint是我们用来绘画View的画笔
*/
}
2.然后是我们的View的主要代码,三个构造器+onMeasure()+onDraw()方法
public CustomTitleView(Context context) {
this(context, null);
}
public CustomTitleView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
主角构造器:
public CustomTitleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray ta = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomTitleView, defStyleAttr, 0);
/**
* TypedArray数组用于获取我们刚刚定义的attr属性文件
*/
/**
* 通过for循环,来遍历每个设置的属性,并用switch语句判断是哪个属性,用上面三个变量去接收
*/
int n = ta.getIndexCount();
for (int i = 0; i < n; i++) {
int attr = ta.getIndex(i);
switch (attr) {
case R.styleable.CustomTitleView_mTitleText:
mTitleText = ta.getString(attr);
break;
case R.styleable.CustomTitleView_mTitleTextColor:
mTitleTextColor = ta.getColor(attr, Color.RED);
break;
case R.styleable.CustomTitleView_mTitleTextSize:
mTitleTextSize = ta.getDimensionPixelSize(attr,
(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics()));
/**
* mTitleTextSize把attr获取过来的值转换成对应的dp值
*/
break;
}
}
ta.recycle();
mPaint = new Paint();
mBonds = new Rect();
}
3.下面是OnMeasure()方法
/**
* MeasureSpec的基础知识(必看):http://my.oschina.net/qiuhoude/blog/410809
*
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width;
int height;
if (widthMode == MeasureSpec.EXACTLY) {
width = widthSize;
} else {
mPaint.setTextSize(mTitleTextSize);
width = (int) mPaint.measureText(mTitleText, 0, mTitleText.length());
/**
* 该模式下为wrap_content,需要自己去测量View需要占据的空间,通过以上方法结合字体大小即可算出宽度
*/
}
if (heightMode == MeasureSpec.EXACTLY) {
height = heightSize;
} else {
mPaint.setTextSize(mTitleTextSize);
height = getFontHeight(mPaint);
/**
* 与宽度类似,但是需要获取字体的高度去测量
*/
}
setMeasuredDimension(width + getPaddingLeft() + getPaddingRight(), height + getPaddingTop() + getPaddingBottom());
}
private int getFontHeight(Paint paint) {
Paint.FontMetrics fm = paint.getFontMetrics();
return (int) (Math.ceil(fm.descent - fm.ascent));
}
4.最后就是View的绘制onDraw()了
@Override
protected void onDraw(Canvas canvas) {
mPaint.setColor(Color.YELLOW);
canvas.drawRect(0, 0, getWidth(), getHeight(), mPaint);
/**
* 画那个View矩形
*/
mPaint.setColor(mTitleTextColor);
mPaint.setTextAlign(Paint.Align.LEFT); //设置基准线,与下下面drawText的Text绘制位置有很大关系,请看下面链接
mPaint.getTextBounds(mTitleText, 0, mTitleText.length(), mBonds); //测量字体所占的宽高,赋值给mBounds
canvas.drawText(mTitleText, getWidth() / 2 - mBonds.width() / 2, getHeight() / 2 + mBonds.height() / 2, mPaint);
//看http://blog.csdn.net/hursing/article/details/18703599
super.onDraw(canvas);
}
View的整体代码
package com.example.august.customview;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
/**
* Created by August on 16/4/8.
*/
public class CustomTitleView extends View {
private String mTitleText;
private int mTitleTextColor;
private int mTitleTextSize;
private Rect mBonds;
private Paint mPaint;
public CustomTitleView(Context context) {
this(context, null);
}
public CustomTitleView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomTitleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray ta = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomTitleView, defStyleAttr, 0);
int n = ta.getIndexCount();
for (int i = 0; i < n; i++) {
int attr = ta.getIndex(i);
switch (attr) {
case R.styleable.CustomTitleView_mTitleText:
mTitleText = ta.getString(attr);
break;
case R.styleable.CustomTitleView_mTitleTextColor:
mTitleTextColor = ta.getColor(attr, Color.RED);
break;
case R.styleable.CustomTitleView_mTitleTextSize:
mTitleTextSize = ta.getDimensionPixelSize(attr,
(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics()));
break;
}
}
ta.recycle();
mPaint = new Paint();
mBonds = new Rect();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width;
int height;
if (widthMode == MeasureSpec.EXACTLY) {
width = widthSize;
} else {
mPaint.setTextSize(mTitleTextSize);
width = (int) mPaint.measureText(mTitleText, 0, mTitleText.length());
}
if (heightMode == MeasureSpec.EXACTLY) {
height = heightSize;
} else {
mPaint.setTextSize(mTitleTextSize);
height = getFontHeight(mPaint);
}
setMeasuredDimension(width + getPaddingLeft() + getPaddingRight(), height + getPaddingTop() + getPaddingBottom());
}
@Override
protected void onDraw(Canvas canvas) {
mPaint.setColor(Color.YELLOW);
canvas.drawRect(0, 0, getWidth(), getHeight(), mPaint);
mPaint.setColor(mTitleTextColor);
mPaint.setTextAlign(Paint.Align.LEFT);
mPaint.getTextBounds(mTitleText, 0, mTitleText.length(), mBonds);
canvas.drawText(mTitleText, getWidth() / 2 - mBonds.width() / 2, getHeight() / 2 + mBonds.height() / 2, mPaint);
super.onDraw(canvas);
}
private int getFontHeight(Paint paint) {
Paint.FontMetrics fm = paint.getFontMetrics();
return (int) (Math.ceil(fm.descent - fm.ascent));
}
}
布局文件:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.example.august.customview.CustomTitleView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:padding="10dp"
app:mTitleText="Hello world!"
app:mTitleTextColor="#FF0000"
app:mTitleTextSize="28sp" />
</RelativeLayout>