前言
原生View不满足我们的业务需求,我们要自定义View
自定义View基础
类型 | 定义 |
---|---|
自定义组合控件 | 多个控件组合成为一个新的控件,方便多处复用 |
继承系统View控件 | 继承自TextView等系统控件,在系统控件的基础功能上进行扩展 |
继承View | 不复用系统控件逻辑,继承View进行功能定义 |
继承系统ViewGroup | 继承自LinearLayout等系统控件,在系统控件的基础功能上进行扩展 |
View绘制流程
onMeasure
测量View的宽高
setMeasuredDimension() 设置View测量的高度
onLayout
计算当前View以及子View的位置
onDraw
视图的绘制工作
3、getMeasureWidth()与getWidth()区别
一个View 的大小是由View本身和父View两个共同决定
getMeasureWidth:measure阶段确定,是xml中的原始值
getWidth:layout阶段确定,是最终显示的大小
两个值可能相等,可能不相等
如何在onCreate中获取View的高度?
postDelay延迟获取一下
ViewTreeObserver方式获取
ViewTreeObserver vto = view.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
Log.i(TAG, "width: " + view.getWidth());
Log.i(TAG, "height: " + view.getHeight());
}
});
4、View,ViewGroup绘制区别
onMeasure:
View只需要确定自己的大小就可,ViewGroup需要先确定子View的大小
onLayout:
View不需要处理,ViewGroup必须实现onLayout来安排子view的位置
onDraw:
View需要实现自己的onDraw就好,ViewGroup通过dispatchDraw以实现对各个子view的绘制
5 例子
自定义一个文本控件
//自定义文件控件
public class FentTextView extends View {
//new用
public FentTextView(Context context) {
super(context);
}
//xml里面用
public FentTextView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
}
此时可以在xml中引入,但是没有具体的属性.
那么就可以自定义属性了
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="FentTextView">
<attr name="android:text" format="string"></attr>
<!-- 自定义name-->
<attr name="text" format="string"></attr>
</declare-styleable>
</resources>
属性值的类型format
(1). reference:参考某一资源ID
(2). color:颜色值
(3). boolean:布尔值
(4). dimension:尺寸值
(5). float:浮点值
(6). integer:整型值
(7). string:字符串
(8). fraction:百分数
(9). enum:枚举值
(10). flag:位或运算
此时xml设置的文件内容.和绘制的并不统一.
需要继续修改view中的内容.
package com.fenghongzhang.day004;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
import androidx.annotation.Nullable;
//自定义文件控件
public class FentTextView extends View {
private String text;
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
//new用
public FentTextView(Context context) {
super(context);
}
//xml里面用
public FentTextView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
/**
* 第一个参数attrs
* 第二个参数是自定义view的名称
*/
//取到所有属性值
TypedArray typedArray = context.obtainStyledAttributes(attrs,R.styleable.FentTextView);
//取到文本值
text = typedArray.getString(R.styleable.FentTextView_android_text);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawText(text,200,200,paint);
}
}
修改字体大小
<attr name="size" format="dimension"></attr>
package com.fenghongzhang.day004;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
import androidx.annotation.Nullable;
//自定义文件控件
public class FentTextView extends View {
private String text;
private float size;
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
//new用
public FentTextView(Context context) {
super(context);
}
//xml里面用
public FentTextView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
/**
* 第一个参数attrs
* 第二个参数是自定义view的名称
*/
//取到所有属性值
TypedArray typedArray = context.obtainStyledAttributes(attrs,R.styleable.FentTextView);
//取到文本值
text = typedArray.getString(R.styleable.FentTextView_android_text);
//获取大小
size = typedArray.getDimension(R.styleable.FentTextView_size, 20);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//设置字体大小
paint.setTextSize(size);
canvas.drawText(text,200,200,paint);
}
}
此时view的宽高还不起作用.
android:layout_height="wrap_content" //自适应大小
android:layout_height="match_parent" //与父视图等高
android:layout_height="fill_parent" //与父视图等高
android:layout_height="100dip" //精确设置高度值为 100dip
测量规格(MeasureSpec) = 测量模式(mode) + 测量大小(size)
2、三种测量模式
MeasureSpec 表示的是一个 32 位的整数值,它的高 2 位表示测量模式 SpecMode,低 30 位表示某种测量模式下的规格大小 SpecSize。
三种测量模式
EXACTLY : 精确测量模式
当该视图的 layout_width 或者 layout_height 指定为具体数值或者 match_parent 时生效,表示父视图已经决定了子视图的精确大小,这种模式下 View 的测量值就是 SpecSize 的值。
AT_MOST:最大值模式.
当前视图的 layout_width 或者 layout_height 指定为 wrap_content 时生效,此时子视图的尺寸可以是不超过父视图运行的最大尺寸的任何尺寸。
UNSPECIFIED:不指定测量模式,
父视图没有限制子视图的大小,子视图可以是想要的任何尺寸,通常用于系统内部,应用开发中很少使用到。比如listview就是这个模式
//设置最重要的属性之一
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
// getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
//100为宽度默认值
measureWidth = getMyViewSize(300, widthMeasureSpec);
//200为高度默认值
measureHeight = getMyViewSize(300, heightMeasureSpec);
//记录子子布局确认后的尺寸
setMeasuredDimension(measureWidth, measureHeight);
// int widthModel = MeasureSpec.getMode(widthMeasureSpec);
// int widthSize = MeasureSpec.getSize(widthMeasureSpec);
//
// int heightModel = MeasureSpec.getMode(heightMeasureSpec);
// int heightSize = MeasureSpec.getSize(heightMeasureSpec);
// setMeasuredDimension(width,height);
}
private int getMyViewSize(int defaultSize, int measureSpec) {
int result = defaultSize;
//解封模式和尺寸值
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = defaultSize;
break;
case MeasureSpec.AT_MOST:
//指定一个值
result = defaultSize;
break;
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
参考链接
https://www.jianshu.com/p/23519665ff32
https://www.jianshu.com/p/146e5cec4863