-
自定义View的属性
在res/values/
下建立一个attrs.xml
,在里面定义属性和声明整个样式。
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="titleText" format="string" />//format是值该属性的取值类型
<attr name="titleTextColor" format="color" />
<attr name="titleTextSize" format="dimension" />
<declare-styleable name="CustomTitleView">
<attr name="titleText" />
<attr name="titleTextColor" />
<attr name="titleTextSize" />
</declare-styleable>
</resources>
或者(上面是为了提取共同属性)
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="CustomTitleView">
<attr name="titleText" format="string" />
<attr name="titleTextColor" format="color" />
<attr name="titleTextSize" format="dimension" />
</declare-styleable>
</resources>
一共有:string,color,demension,integer,enum,reference,float,boolean,fraction,flag
;
- 在View的构造方法中,获得自定义的样式
// 文字
private String mTitleText;
// 文字颜色
private int mTitleTextColor;
// 字号
private int mTitleTextSize;
// 画笔
private Paint mPaint;
// 矩形
private Rect mRect;
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 a = context.obtainStyledAttributes(attrs, R.styleable.CustomTitleView, defStyleAttr, 0);
mTitleText = a.getString(R.styleable.CustomTitleView_titleText);
mTitleTextColor = a.getColor(R.styleable.CustomTitleView_titleTextColor, Color.BLACK);
mTitleTextSize = a.getDimensionPixelSize(R.styleable.CustomTitleView_titleTextSize, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics()));
a.recycle(); //通过recycle方法回收,否则会引起内存泄漏
init();
}
private void init() {
mPaint = new Paint();
mPaint.setTextSize(mTitleTextSize);
mPaint.setColor(mTitleTextColor);
mPaint.setAntiAlias(true);
mRect = new Rect();
//计算了描绘字体需要的范围
mPaint.getTextBounds(mTitleText, 0, mTitleText.length(), mRect);
}
-
重写
onMeasure
、onDraw
方法
重写onMeasure
之前要了解MeasureSpec
的specMode
,一共分为三种类型:
EXACTLY:一般表示设置了明确值,或者 match_parent ;
AT_MOST:表示子控件限制在一个最大值内,一般为 wrap_content;
UNSPECIFIED:表示子控件想多大就多大,很少使用
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = 0;
int height = 0;
/**
* 设置宽度
*/
int specMode = MeasureSpec.getMode(widthMeasureSpec);
int specSize = MeasureSpec.getSize(widthMeasureSpec);//获取总宽度,是包含padding值
switch (specMode) {
case MeasureSpec.EXACTLY://一般是设置了明确的值或者是MATCH_PARENT
width = getPaddingLeft() + getPaddingRight() + specSize;
break;
case MeasureSpec.AT_MOST://表示子布局限制在一个最大值内,一般为WARP_CONTENT
case MeasureSpec.UNSPECIFIED:// 表示子布局想要多大就多大,很少使用
float textWidth = mPaint.measureText(mTitleText); // int textWidth = mRect .width();直接计算出来的会有误差
width = (int) (getPaddingLeft() + getPaddingRight() + textWidth);
break;
}
/**
* 设置高度
*/
specMode = MeasureSpec.getMode(heightMeasureSpec);
specSize = MeasureSpec.getSize(heightMeasureSpec);
switch (specMode) {
case MeasureSpec.EXACTLY://明确指定了
height = getPaddingTop() + getPaddingBottom() + specSize;
break;
case MeasureSpec.AT_MOST:// 一般为WARP_CONTENT
case MeasureSpec.UNSPECIFIED:
Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();
float textHeight = Math.abs((fontMetrics.bottom - fontMetrics.top));//int textHeight = mRect .height(); 直接计算出来的会有误差
height = (int) (getPaddingTop() + getPaddingBottom() + textHeight);
break;
}
setMeasuredDimension(width, height);
}
注意
- 是否你的宽度偏小,左边有空白,右边显示不全???那是因为
onMeasure
你使用了上面注释中的int textWidth = mRect.width()
(有误差的),而不是使用float textWidth = mPaint.measureText(mTitleText);
(准确的)- 是否你的高度偏小???那是因为
onMeasure
你使用了上面注释中的int textHeight = mRect .height()
(有误差的),而不是float textHeight = Math.abs((mPaint.getFontMetrics().bottom - mPaint.getFontMetrics().top))
(准确的)- 为什么计算宽时是getWidth() / 2 - mBound.width() / 2,计算高时是getHeight() / 2 +mBound.height() / 2???
canvas.drawText(text, x, y, paint)
,第一个参数和第四个参数不比多说,主要是第二和第三个参数的含义,这两个参数在不同的情况下的值还是不一样的,x默认是这个字符串的左边在屏幕的位置,如果设置了paint.setTextAlign(Paint.Align.CENTER)
;那就是字符的中心,y是指定这个字符baseline在屏幕上的位置,一定要注意,y不是这个字符中心在屏幕上的位置,而是baseline在屏幕上的位置。那是因为在android
中,文字的绘制都是从Baseline
处开始的(Baseline以上是负值,以下是正值,因此 ascent,top是负值,descent
和bottom
是正值)
@Override
protected void onDraw(Canvas canvas) {
mPaint.setColor(Color.BLACK);
mPaint.setAntiAlias(true);
mPaint.setTextAlign(Paint.Align.LEFT);
canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mPaint);
mPaint.setColor(mTitleTextColor);
Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();
float startY = getHeight() / 2 + Math.abs((fontMetrics.bottom - fontMetrics.top)) / 2- fontMetrics.descent ;
canvas.drawText(mTitleText, getWidth() / 2 - mPaint.measureText(mTitleText) / 2, startY, mPaint);
}
注意
onDraw
或layout
的过程有可能是一个频繁重复执行的过程,我们知道new是需要分配内存空间的,所以不建议在这两个过程中去实例化对象,一般在构造函数中实例化对象
-
在布局中声明自定义View
一定要引入xmlns:lazyxu="http://schemas.android.com/apk/res-auto"
(其中lazyxu
由自己随意取)
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:lazyxu="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.lazyxu.startmode.CustomTitleView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
lazyxu:titleText="3712"
lazyxu:titleTextColor="#ff0000"
lazyxu:titleTextSize="40sp"
/>
</RelativeLayout>
小知识
-
getMeasuredWidth()
与getWidth()
区别
1.定义概念不同:
getMeasuredWidth()
是View视图实际的宽高(包括被隐藏的部分);getWidth()
指的是视图显示的宽高。实际上在当屏幕可以包裹内容的时候,他们的值是相等的,只有当view超出屏幕后,才能看出他们的区别。当超出屏幕后,getMeasuredHeight()
等于getHeight()
加上屏幕之外没有显示的高度。
getWidth()方法中的返回值是通过View的右坐标减去其左坐标(right-left)计算出来
2.赋值时机不同:getMeasureWidth()
值是通过setMeasuredDimension()
方法来进行设置的,在onMeasure()
结束后获取到,getWidth()
值是通过layout(left,top,right,bottom)
方法设置的,在layout()
结束后获取到。
-
Paint的measureText()和getTextBound()
getTextBounds() 得到的宽度总是比 measureText() 得到的宽度要小一点。
参考
http://blog.csdn.net/lmj623565791/article/details/24252901/
http://blog.csdn.net/u014702653/article/details/51985821