第15章 自定义View与高级绘制¶
学习目标:掌握自定义View的开发,理解Canvas绘制、动画和手势处理。
预计学习时间:5-7天 实践时间:2-3天
目录¶
1. 自定义View基础¶
1.1 继承View¶
Kotlin
class CircleView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = Color.RED
style = Paint.Style.FILL
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
val centerX = width / 2f
val centerY = height / 2f
val radius = min(centerX, centerY) - 20
canvas.drawCircle(centerX, centerY, radius, paint)
}
}
1.2 测量与布局¶
Kotlin
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val widthMode = MeasureSpec.getMode(widthMeasureSpec)
val widthSize = MeasureSpec.getSize(widthMeasureSpec)
val heightMode = MeasureSpec.getMode(heightMeasureSpec)
val heightSize = MeasureSpec.getSize(heightMeasureSpec)
val desiredWidth = 200
val desiredHeight = 200
val width = when (widthMode) {
MeasureSpec.EXACTLY -> widthSize
MeasureSpec.AT_MOST -> min(desiredWidth, widthSize)
else -> desiredWidth
}
val height = when (heightMode) {
MeasureSpec.EXACTLY -> heightSize
MeasureSpec.AT_MOST -> min(desiredHeight, heightSize)
else -> desiredHeight
}
setMeasuredDimension(width, height)
}
2. Canvas绘制¶
2.1 基础图形¶
Kotlin
override fun onDraw(canvas: Canvas) {
// 绘制圆形
canvas.drawCircle(cx, cy, radius, paint)
// 绘制矩形
canvas.drawRect(left, top, right, bottom, paint)
// 绘制圆角矩形
canvas.drawRoundRect(rect, rx, ry, paint)
// 绘制椭圆
canvas.drawOval(rect, paint)
// 绘制弧线
canvas.drawArc(rect, startAngle, sweepAngle, useCenter, paint)
// 绘制直线
canvas.drawLine(startX, startY, stopX, stopY, paint)
// 绘制文字
canvas.drawText(text, x, y, paint)
// 绘制Bitmap
canvas.drawBitmap(bitmap, src, dst, paint)
// 绘制Path
canvas.drawPath(path, paint)
}
2.2 Path路径¶
Kotlin
val path = Path().apply {
// 移动到某点
moveTo(100f, 100f)
// 画线到某点
lineTo(200f, 200f)
// 二次贝塞尔曲线
quadTo(controlX, controlY, endX, endY)
// 三次贝塞尔曲线
cubicTo(x1, y1, x2, y2, x3, y3)
// 画圆弧
arcTo(rect, startAngle, sweepAngle)
// 闭合路径
close()
}
3. 自定义属性¶
XML
<!-- res/values/attrs.xml -->
<declare-styleable name="CircleView">
<attr name="circleColor" format="color" />
<attr name="circleRadius" format="dimension" />
<attr name="showBorder" format="boolean" />
</declare-styleable>
Kotlin
// 在构造函数中读取属性
init {
context.theme.obtainStyledAttributes(
attrs,
R.styleable.CircleView,
defStyleAttr,
0
).apply {
try { // try/catch捕获异常
circleColor = getColor(R.styleable.CircleView_circleColor, Color.RED)
circleRadius = getDimension(R.styleable.CircleView_circleRadius, 100f)
showBorder = getBoolean(R.styleable.CircleView_showBorder, false)
} finally {
recycle()
}
}
}
XML
<!-- 使用自定义属性 -->
<com.example.CircleView
android:layout_width="200dp"
android:layout_height="200dp"
app:circleColor="@color/primary"
app:circleRadius="80dp"
app:showBorder="true" />
4. 动画实现¶
4.1 属性动画¶
Kotlin
// 值动画
ValueAnimator.ofFloat(0f, 100f).apply {
duration = 1000
interpolator = AccelerateDecelerateInterpolator()
addUpdateListener { animator ->
val value = animator.animatedValue as Float
// 更新View
invalidate()
}
start()
}
// ObjectAnimator
ObjectAnimator.ofFloat(view, "translationX", 0f, 200f).apply {
duration = 1000
start()
}
// AnimatorSet
AnimatorSet().apply {
play(animator1).with(animator2).before(animator3)
start()
}
4.2 自定义动画¶
Kotlin
class PulseAnimation(private val view: View) {
fun start() {
val scaleX = ObjectAnimator.ofFloat(view, "scaleX", 1f, 1.2f, 1f)
val scaleY = ObjectAnimator.ofFloat(view, "scaleY", 1f, 1.2f, 1f)
AnimatorSet().apply {
playTogether(scaleX, scaleY)
duration = 1000
repeatCount = ValueAnimator.INFINITE
start()
}
}
}
5. 手势处理¶
Kotlin
class GestureView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
) : View(context, attrs) {
private val gestureDetector = GestureDetector(context, object : GestureDetector.SimpleOnGestureListener() {
override fun onDown(e: MotionEvent): Boolean = true
override fun onSingleTapUp(e: MotionEvent): Boolean {
// 单击
return true
}
override fun onDoubleTap(e: MotionEvent): Boolean {
// 双击
return true
}
override fun onLongPress(e: MotionEvent) {
// 长按
}
override fun onScroll(
e1: MotionEvent?,
e2: MotionEvent,
distanceX: Float,
distanceY: Float
): Boolean {
// 滑动
return true
}
override fun onFling(
e1: MotionEvent?,
e2: MotionEvent,
velocityX: Float,
velocityY: Float
): Boolean {
// 快速滑动
return true
}
})
private val scaleGestureDetector = ScaleGestureDetector(context,
object : ScaleGestureDetector.SimpleOnScaleGestureListener() {
override fun onScale(detector: ScaleGestureDetector): Boolean {
scaleFactor *= detector.scaleFactor
invalidate()
return true
}
})
override fun onTouchEvent(event: MotionEvent): Boolean {
scaleGestureDetector.onTouchEvent(event)
return gestureDetector.onTouchEvent(event) || super.onTouchEvent(event)
}
}
6. 实践练习¶
练习1:自定义进度条¶
任务:实现圆形进度条
要求: - 可设置进度 - 支持渐变色 - 带动画效果
练习2:手势缩放图片¶
任务:实现可缩放拖拽的图片View
要求: - 双指缩放 - 单指拖拽 - 边界检测
本章小结¶
核心要点¶
- 自定义View需要处理测量、布局和绘制
- Canvas提供丰富的绘制API
- 属性动画是实现动画的首选方案
- GestureDetector简化手势处理
下一步¶
完成本章学习后,请进入第16章:NDK与JNI开发。
本章完成时间:预计5-7天