Android学习之自定义view详解
2021-06-30 15:03
标签: Android学习之自定义view详解 标签:
1.自定义View的属性
在 res/values/ 下建立一个 attrs.xml ,在里面定义属性和声明整个样式。
//format是值该属性的取值类型
或者(上面是为了提取共同属性)
一共有: string,color,demension,integer,enum,reference,float,boolean,fraction,flag ;
2.在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);
mRect = new Rect();
//计算了描绘字体需要的范围
mPaint.getTextBounds(mTitleText, 0, mTitleText.length(), mRect);
}
3.重写 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 textWidth = mPaint.measureText(mTitleText) (准确的)
· 为什么计算宽时是getWidth() / 2 - mRect.width() / 2,计算高时是getHeight() / 2 +mRect .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是需要分配内存空间的,所以不建议在这两个过程中去实例化对象,一般在构造函数中实例化对象
4. 在布局中声明自定义View
一定要引入 xmlns:lazyxu="http://schemas.android.com/apk/res-auto" (其中 lazyxu 由自己随意取)
xmlns:lazyxu="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
lazyxu:titleText="3712"
lazyxu:titleTextColor="#ff0000"
lazyxu:titleTextSize="40sp"
/>
小知识
· getMeasuredWidth() 与 getWidth() 区别
1. 定义概念不同 : getMeasuredWidth() 是View视图实际的宽高(包括被隐藏的部分); getWidth() 指的是视图显示的宽高。实际上在当屏幕可以包裹内容的时候,他们的值是相等的,只有当view超出屏幕后,才能看出他们的区别。当超出屏幕后, getMeasuredHeight()等于 getHeight() 加上屏幕之外没有显示的高度。
getWidth()方法中的返回值是通过View的右坐标减去其左坐标(right-left)计算出来
2. 赋值时机不同 : getMeasureWidth() 值是通过 setMeasuredDimension() 方法来进行设置的,在 onMeasure() 结束后获取到, getWidth() 值是通过 layout(left,top,right,bottom) 方法设置的,在 layout() 结束后获取到。
来源:简书
上一篇:mac安装多版本php环境配置
下一篇:Vue.js的常用指令