自定义View有两种:
- 自定义View,如:TextView,这些只有内容没有子View的;
- 自定义ViewGroup,比较难,如:LinearLayout,有子View;
自定义View基本流程:measure->layout->draw,这些调度方法,onMeasure->onLayout->onDraw
首先总结一下的关键点:
- 自定义绘制的方式是重写绘制方法,绘制主体的内容,其中最常用的是 onDraw()
- 绘制的关键是 Canvas 的使用 ;
Canvas 的绘制类方法: drawXXX() (关键参数:Paint)
Canvas 的辅助类方法:范围裁切和几何变换 - 可以使用不同的绘制方法来控制遮盖关系;
绘制的顺序:
1、背景(drawBackground())
2、主体(onDraw())
3、子 View(dispatchDraw())
4、滑动边缘渐变和滑动条前景(onDrawForeground())
今天只讲自定义View的一些与绘制相关的API和细节,上效果图:

这里面主要就是:画圆、阴影画文本下面就是主要代码:
class CustomView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : View(context, attrs, defStyleAttr) {
private val mPaint = Paint()
private val mTextPaint = TextPaint()
private var mTextNumber: String = "00000步"
private var text = "我是文字,hahahhah"
init {
mPaint.style = Paint.Style.STROKE//设置绘制模式
mPaint.strokeWidth = dp2px(5).toFloat()//设置线条宽度
mPaint.color = Color.GREEN//设置颜色
mPaint.isAntiAlias = true//设置抗锯齿开关
/*
setShadowLayer只有文字绘制阴影支持硬件加速,其它都不支持硬件加速
*/
setLayerType(LAYER_TYPE_SOFTWARE, null) //关闭硬件加速
mPaint.setShadowLayer(10f, 2f, 2f, Color.BLACK) // 不能与setMaskFilter同时使用
// val blurMaskFilter = BlurMaskFilter(10f, BlurMaskFilter.Blur.NORMAL)
// mPaint.maskFilter = blurMaskFilter
}
var mDefaultWidth: Int = 400
var mDefaultHeight: Int = 300
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
mDefaultWidth = resolveSize(mDefaultWidth, widthMeasureSpec)
mDefaultHeight = resolveSize(mDefaultHeight, heightMeasureSpec)
setMeasuredDimension(mDefaultWidth, mDefaultHeight)
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.drawColor(Color.GRAY)
canvas.save()//及时保存canvas之前的配置
canvas.translate(mDefaultWidth / 2f, mDefaultHeight / 2f)//将坐标移动至view的中心,方便绘制
val radius = Math.min(mDefaultWidth / 2f, mDefaultHeight / 2f) - mPaint.strokeWidth
canvas.drawCircle(0f, 0f, radius, mPaint)
canvas.restore()//恢复save之前的配置
mTextPaint.isAntiAlias = true//抗锯齿
mTextPaint.textSize = 25f
mTextPaint.color = Color.RED
val textWidth = mTextPaint.measureText(text)//测量文本的宽度
val textHeight = -mTextPaint.ascent() + mTextPaint.descent()//得到文本的高度
canvas.save()
canvas.translate(mDefaultWidth / 2f, mDefaultHeight / 2f)
canvas.drawText(text, 0, text.length, -textWidth / 2, textHeight / 2, mTextPaint)
canvas.restore()
canvas.save()
mTextPaint.textSize = 30f
mTextPaint.color = Color.WHITE
//通过Rect获取文本宽度和高度
val rect = Rect()
mTextPaint.getTextBounds(mTextNumber, 0, mTextNumber.length, rect)
val textNumWidth = rect.width().toFloat()
val textNumHeight = rect.height().toFloat()
canvas.translate(mDefaultWidth / 2f, mDefaultHeight / 2f)
canvas.drawText(mTextNumber, 0, mTextNumber.length, -textNumWidth / 2, (textNumHeight / 2) - (textHeight + 10), mTextPaint)
canvas.restore()
}
fun updateNumText(text: String) {
mTextNumber = "${text}步"
postInvalidate()
}
fun updateText(text: String) {
this.text = "${text}步"
postInvalidate()
}
}
总结
- canvas.save()和canvas.restore()
及时保存canvas之前的配置,也就是后面会用canvas.restore()恢复里其最近的配置,save()是由返回值的,官方说了,如果你多欧茨调用save,但是想要恢复指定save的配置,可以使用下面的方式,所以就有了:canvas.restoreToCount(count)
int count = canvas.save();
canvas.restoreToCount(count);
- canvas.translate(cx,cy)
将view的坐标原定移动至cx,cy;
Canvas的这类方法,我想大家都知道View的内容都是canvas画出来的,所以canvas的坐标系也就是View的坐标系,默认原点在左上角,而如果我们想改变View的坐标系,那么就需要通过Canvas,其实操作的都是Matrix,即矩阵,矩阵就和坐标系相关,所以我这里暂时理解为操作的是View的坐标系,这里就不详细将Matrix。
所以这类方法还有几个:canvas.rotate(degrees, px, py)(旋转),scale(sx, sy, px, float py)(缩放),skew(sx, sy)(错切),这些操作都会影响Matrix即坐标系,而View的translationX这些只是对View在ayout中的偏移位置。
不知道这样理解有没有偏差,请指正。
所有的内容都有注释了,下周讲一下,蚂蚁积分的信用控件,
网友评论