实现一个自定义视图,通常要提供框架在所有视图上都会调用的标准方法的重写,这些标准方法有:
分类 | 方法 | 描述 |
---|---|---|
创建 | 构造器 | 有一种构造器形式是当视图是从代码创建时调用的;还有另一种构造器形式是当视图是通过布局文件来创建时调用的。第二种形式要解析和应用定义在布局文件中的属性。 |
创建 | onFinishInflate() | 当一个视图及其所有孩子都从XML文件创建完成时调用。可以做些初始化子控件的操作 |
布局 | onMeasure(int, int) | 调用这个方法决定视图及其所有孩子的大小要求,当覆盖这个方法时,我们应该调用setMeasuredDimension(int,int)存储测量好的视图的宽度和高度。如果这个方法被重写,那么这个子类要确保测量的高度和宽度至少是视图的最小高度和宽度。 |
布局 | onLayout(boolean, int, int, int, int) | 当这个视图要给它的孩子分配大小和位置时调用 |
布局 | onSizeChanged(int, int, int, int) | 当视图的大小发生改变时调用 |
绘图 | onDraw(android.graphics.Canvas) | 当视图要渲染它的内容时调用。我们要在这里实现我们的绘图 |
事件处理 | onKeyDown(int, KeyEvent) | 当一个新的按键事件发生时调用 |
事件处理 | onKeyUp(int, KeyEvent) | 当一个键弹起事件发生时调用 |
事件处理 | onTouchEvent(MotionEvent) | 当一个触屏动作发生时调用 |
焦点 | onFocusChanged(boolean, int, android.graphics.Rect) | 当视图获得或失去焦点时调用 |
焦点 | onWindowFocusChanged(boolean) | 当包含视图的window获得或失去焦点时调用 |
附属 | onAttachedToWindow() | 当视图依附到一个window上时调用 |
附属 | onDetachedFromWindow | 当视图从它的window上脱离时调用 |
附属 | onWindowVisibilityChanged(int) | 当包含视图的window的可见性发生改变时调用 |
invalidate() | 请求View树进行重绘,即draw()方法,如果视图的大小发生了变化,还会调用layout()方法 | |
postInvalidate() | 功能与invalidate()方法相同,但它是异步请求重绘视图。postInvalidate()内部调用的是postInvalidateDelayed(),可以用于控件的定时刷新。 | |
requestLayout() | 只是对View树进行重新布局layout过程(包括measure()过程和layout()过程),不会调用draw()过程,即不会重新绘制任何视图,包括该调用者本身。 | |
requestFocus() | 请求View树的draw()过程,但只会绘制需要重绘的视图,即哪个View或ViewGroup调用了这个方法,就重绘哪个视图。 |
上面这么多方法,不一定要全部实现,如有时我们只需要实现onDraw()方法。
ViewGroup是一个特殊的View,继承了View。它可以包含其他View。ViewGroup是布局容器和视图容器的基类。它也定义了 LayoutParams类,这个类作为布局参数来使用。
方法 | 描述 |
---|---|
onLayout(boolean, int, int, int, int) | 决定子视图的大小及位置,要在子类中实现此方法 |
在自定义View中,我们还要使用Paint类的方法。Paint类是设置样式和颜色信息的,将会用于绘制几何图形、文本、位图,Paint类方法:
方法 | 描述 |
---|---|
reset() | 将paint对象恢复到默认设置。 |
setAntiAlias(boolean) | 设置是否使用抗锯齿功能,抗锯齿功能会消耗较大资源,绘制图形的速度会减慢。 |
setColor(int)、setARGB(int, int, int, int) | 设置绘制颜色 |
setAlpha(int) | 设置颜色的透明度,取值0~255 |
setDither(boolean) | 设置是否使用图像抖动处理,会使图像颜色更加平滑饱满,更加清晰。 |
setFileterBitmap(Boolean b) | 设置是否在动画中滤掉Bitmap的优化,可以加快显示速度。 |
setStrokeWidth(float) | 当画笔样式为STROKE或FILL_OR_STROKE时,设置笔刷的粗细度 |
setStrokeMiter(float) | 设置笔画斜接角度 |
setStrokeCap(Cap) | 当设置画笔样式为STROKE或FILL_OR_STROKE时,设置笔刷的图形样式 |
setStrokeJoin(Join) | 设置绘制时各图形的结合方式 |
setStyle(Paint.Style s) | 设置画笔的样式:FILL实心;STROKE空 |
setShader(Shader) | 设置Shader绘制各种渐变效果 |
setColorFilter(ColorFilter) | 设置颜色过滤器,可以在绘制颜色时实现不同颜色的变换效果 |
setXfermode(Xfermode) | 设置图形重叠时的处理方式 |
setBlendMode(BlendMode) | 设置或清除Blend模式,Blend模式定义了绘制命令产生的源像素如何与渲染目标内容的像素复合。 |
setPathEffect(PathEffect) | 设置绘制的路径的效果 |
setMaskFilter(MaskFilter maskfilter) | 设置MaskFilter来实现滤镜的效果 |
setTypeface(Typeface) | 设置字体 |
setRasterizer(Rasterizer) | 设置光栅化器。这个光栅化器控制/改变路径/文本变化到到透明遮罩 |
setShadowLayer(float, float, float,int) | 设置阴影 |
clearShadowLayer() | 清除阴影 |
setTextAlign(Align) | 设置文本的对齐方式 |
setTextSize(float) | 设置文本大小 |
setTextScaleX(float) | 设置文本水平缩放因子,可以实现文字的拉伸效果 |
setTextSkewX(float) | 设置斜体文字 |
setUnderlineText(boolean) | 设置绘制的文本是否带有下划线效果 |
setStrikeThruText(boolean) | 设置绘制的文本是否带有删除线效果 |
setFakeBoldText(boolean) | 模拟实现粗体文字,如果设置在小字体上效果会非常差 |
setSubpixelText(boolean) | 如果设置为true则有助于文本在LCD屏幕上显示效果 |
setLetterSpacing(float) | 设置字母间距 |
setWordSpacing(float) | 设置字母间距 |
setFontFeatureSettings(String) | 设置字体特性设置,可参考https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop |
setFontVariationSettings(String) | 通过字体变量来设置字体,如:设置字体宽度为150,Paint paint = new Paint();paint.setFontVariationSettings("‘wdth’ 150"); |
setStartHyphenEdit(int) | 设置连字符,如Paint paint = new Paint();paint.setStartHyphenEdit(Paint.START_HYPHEN_EDIT_INSERT_HYPHEN);paint.measureText(“abc”, 0, 3); Canvas.drawText(“abc”, 0, 3, 0f, 0f, paint); |
measureText(char[], int, int) | 返回文本的宽度 |
方法 | 描述 |
---|---|
drawArc():绘制圆弧 | |
drawBitmap() | 绘制Bitmap图像 |
drawCircle() | 绘制圆圈 |
drawLine() | 绘制线条 |
drawOval() | 绘制椭圆 |
drawPath() | 绘制Path路径 |
drawPicture() | 绘制Picture图片 |
drawRect() | 绘制矩形 |
drawRoundRect() | 绘制圆角矩形 |
drawText() | 绘制文本 |
drawVertices() | 绘制顶点 |
save() | 把当前绘制的图像保存起来,让后续的操作相当于是在一个新图层上绘制 |
restore() | 把当前画布调整到上一个save()之前的状态 |
translate(dx, dy) | 把当前画布的原点移到(dx, dy)点,后续操作都以(dx, dy)点作为参照 |
scale(x, y) | 将当前画布在水平方向上缩放x倍,竖直方向上缩放y倍 |
rotate(angle) | 将当前画布顺时针旋转angle度 |
setDensity(int density) | 设置后端位图的密度 |
onMeasure方法中常用的方法:
onLayout方法中常用的方法:
onDraw方法中常用的方法:
在onDraw()方法中使用最多的两个类:Paint和Canvas,它们的方法请参考文章前面部分。
自定义单一View
继承 | 应用场景 | 实现方式 | 特别注意 | demo |
---|---|---|---|---|
继承View | 自定义控件,如:圆形 | 需要自己绘制,即需要重写onDraw() | 要自定义一些属性来控制 | demo |
继承特定View | 扩展已有View的功能(如:TextView) | 在原有View的基础上扩展功能 | 无须使用自定义属性 | demo |
自定义ViewGroup
继承 | 应用场景 | 实现方式 | 特别注意 | 例子 |
---|---|---|---|---|
继承ViewGroup | 自定义布局,如自定义标题栏 | ViewGroup&子View的measure、layout过程 | 复杂,需要自定义属性 | demo1、demo2 |
继承特定ViewGroup | 扩展已有的布局,如下雨效果 | 在原有ViewGroup上进行组合 | 简单,但自由度不高 | demo |
(1)实例一:自定义View
第一步:自定义属性
我们平时在编写xml布局时,看到的所有的系统定义的属性都定义在各自平台下一个叫attrs.xml的文件中,路径为:Sdk/platforms/android-28/data/res/values/attrs.xml。我们继承的View现有的属性不足以实现我们的功能,因此我们需要自定义一些属性,我们在app/src/main/res/values/attrs.xml加入:
format的属性值:
第二步:写一个继承View的子类
重写onDraw()方法。
package com.wong.view;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Picture;
import android.graphics.Rect;
import android.graphics.RectF;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import androidx.annotation.Nullable;
import com.wong.view.utils.FontUtils;
public class CustomCircleView extends View {
/*边的颜色*/
private int strokeColor = Color.BLACK;
/*边的宽度*/
private float strokeWidth = 1;
/*填充颜色*/
private int fillColor = Color.WHITE;
/*文本*/
private String text="";
/*文本大小*/
private float textSize = 16;
/*文本的颜色*/
private int textColor = Color.BLACK;
private Paint mPaintText = new Paint();
private Paint mPaintCircle = new Paint();
private Paint mPaintCircleSide = new Paint();
public CustomCircleView(Context context) {
super(context);
}
public CustomCircleView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context,attrs);
}
private void init(Context context,AttributeSet attrs){
/**
* 加载自定义属性集合CircleView
* 要在styles.xml中添加配置:
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
TypedArray typedArray = context.obtainStyledAttributes(attrs,R.styleable.CustomCircleView);
strokeColor = typedArray.getColor(R.styleable.CustomCircleView_stroke_color, strokeColor);
strokeWidth = typedArray.getDimension(R.styleable.CustomCircleView_stroke_width,strokeWidth);
fillColor = typedArray.getColor(R.styleable.CustomCircleView_fill_color,fillColor);
text = typedArray.getString(R.styleable.CustomCircleView_text);
textSize = typedArray.getDimension(R.styleable.CustomCircleView_text_size,textSize);
textColor = typedArray.getColor(R.styleable.CustomCircleView_text_color,textColor);
typedArray.recycle();
}
/**
* 1) {@link Canvas#save()}:把当前绘制的图像保存起来,让后续的操作相当于是在一个新图层上绘制;
* 2) {@link Canvas#restore()}:把当前画布调整到上一个save()之前的状态;
* 3) {@link Canvas#translate(float, float)}:把当前画布的原点移到(dx, dy)点,后续操作都以(dx, dy)点作为参照;
* 4) {@link Canvas#scale(float, float)}:将当前画布在水平方向上缩放x倍,竖直方向上缩放y倍;
* 5) {@link Canvas#rotate(float)}:将当前画布顺时针旋转angle度.
*
* 6) {@link Canvas#drawArc(RectF, float, float, boolean, Paint)}:绘制圆弧;
* 7) {@link Canvas#drawBitmap(Bitmap, Matrix, Paint)}:绘制Bitmap图像;
* 8) {@link Canvas#drawCircle(float, float, float, Paint)}:绘制圆圈;
* 9) {@link Canvas#drawLine(float, float, float, float, Paint)}:绘制线条;
* 10) {@link Canvas#drawOval(RectF, Paint)}:绘制椭圆;
* 11) {@link Canvas#drawPath(Path, Paint)}:绘制Path路径;
* 12) {@link Canvas#drawPicture(Picture)}:绘制Picture图片;
* 13) {@link Canvas#drawRect(Rect, Paint)}:绘制矩形;
* 14) {@link Canvas#drawRoundRect(RectF, float, float, Paint)}:绘制圆角矩形;
* 15) {@link Canvas#drawText(String, float, float, Paint)}:绘制文本;
* 16) {@link Canvas#drawVertices(Canvas.VertexMode, int, float[], int, float[], int, int[], int, short[], int, int, Paint)}:绘制顶点。
* @param canvas
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 设置画布的颜色,否则会出现黑色的背景
canvas.drawRGB(255,255,255);
mPaintCircle.setColor(fillColor);
mPaintCircle.setAntiAlias(true);
mPaintCircle.setStyle(Paint.Style.FILL);
mPaintText.setTextSize(textSize);
mPaintText.setColor(textColor);
mPaintText.setTextAlign(Paint.Align.CENTER);
mPaintCircleSide.setColor(strokeColor);
mPaintCircleSide.setStrokeWidth(strokeWidth);
mPaintCircleSide.setStyle(Paint.Style.STROKE);
// 获取控件的高度和宽度
int width = getWidth();
int height = getHeight();
// 设置圆的半径 = 宽,高最小值的2分之1
int r = Math.min(width, height)/2-(int)strokeWidth;
// 画出圆
// 圆心 = 控件的中央,半径 = 宽,高最小值的2分之1
canvas.drawCircle(width/2,height/2,r,mPaintCircle);
// 画出圈
canvas.drawCircle(width/2,height/2,r,mPaintCircleSide);
// 文字
if(!TextUtils.isEmpty(text)) {
// TODO 换行,日后再添加
float textH = FontUtils.getFontHeight(mPaintText)/2;
canvas.drawText(text, width / 2, height / 2 + (int)textH, mPaintText);
}
}
}
第三步:在布局文件中使用
<com.wong.view.CustomCircleView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/colorPrimaryDark"
app:text="ABC"
app:text_size="18sp"
app:text_color="@android:color/holo_red_dark"
app:stroke_color="@android:color/holo_blue_dark"
app:stroke_width="4dp"
app:fill_color="@android:color/holo_orange_light"/>
(2)自定义ViewGroup
自定义ViewGroup和自定义View的步骤差不多,区别在于ViewGroup是容器,用来包含其他控件,而View是控件看得见的。ViewGroup需要重写onMeasure方法测量子控件的宽高和自己的宽高,然后实现onLayout方法摆放子控件。而 View则是需要重写onMeasure根据测量模式和父控件给出的建议的宽高值计算自己的宽高,然后在父控件为其指定的区域绘制自己的图形。
具体参考:
《自定义ViewGroup—实现自定义ViewPager》
《自定义ViewGroup——自定义布局》