自定义View(几何变换)

自定义View(几何变换)

Canvas 的范围裁切
  • clipRect() 矩形裁切
  • clipPath() 路径裁切
  • clipOutRect() / clipOutPath() 相反方向裁切

private val IMAGE_SIZE = 120.dp
private val IMAGE_PADDING = 50.dp

class ClipView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {

    private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
    private val path = Path()

    init {
        path.addOval(
            IMAGE_PADDING, IMAGE_PADDING,
            IMAGE_PADDING + IMAGE_SIZE,
            IMAGE_PADDING + IMAGE_SIZE,
            Path.Direction.CW
        )
    }

    override fun onDraw(canvas: Canvas) {
        canvas.clipPath(path)
        canvas.drawBitmap(getAvatar(IMAGE_SIZE.toInt()), IMAGE_PADDING, IMAGE_PADDING, paint)
    }

    private fun getAvatar(width: Int): Bitmap {
        val options = BitmapFactory.Options()
        options.inJustDecodeBounds = true
        BitmapFactory.decodeResource(resources, R.drawable.avatar, options)
        options.inJustDecodeBounds = false
        options.inDensity = options.outWidth
        options.inTargetDensity = width.toInt()
        return BitmapFactory.decodeResource(resources, R.drawable.avatar, options)
    }
}

Canvas 的几何变换
  • translate(x ,y) 位移
  • rotate(degree) 旋转
  • scale(x, y) 缩放
  • skew(x, y) 斜切

注意:Canvas 的几何变换方法参照的是 View 的坐标系,而绘制方法 drawXxx() 使用的是 Canvas 自己的坐标系。


Canvas 的多重变换

Canvas 的变换方法多次调用的时候,由于 Canvas 的坐标系会整体被变换,因此当平移、旋转、放缩、错切等变换多重存在的时候,Canvas 的变换参数会非常难以计算,因此可以改用倒叙的理解方式。

将 Canvas 的变换理解为 Canvas 的坐标系不变,每次变换是 只对内部的绘制内容进行变换,同时把 Canvas 的变换顺序看做是倒叙的(即 写在下面的变换先执行),可以更加方便进行多重变换的参数计算。


Matrix 的几何变换
  • preTranslate(x, y) / postTranslate(x, y)
  • preRotate(degree) / postRotate(degree)
  • preScale(x, y) / postScale(x, y)
  • preSkew(x, y) / postSkew(x, y)

其中 preXxx() 效果和 Canvas 的准同名方法相同,postXxx() 效果和 Canvas 的准同名方法顺序相反。

如果多次绘制是重复使用 Matrix,在使用之前需要用 Matrix.reset() 来把 Matrix 重置。


使用 Camera 做三维旋转

Camera 三维坐标系,向右为 x轴 正方向,向上为 y轴 正方向,向内为 z轴 正方向。

  • rotate() / rotateX() / rotateY() / rotateZ() // 旋转
  • translate() // 位移
  • setLocation()// 设置虚拟相机的距离,虚拟机相机处于 z轴 的负方向上

一般只用 rotateX() 和 rorateY() 来做沿 x 轴 或 y 轴的旋转,以及使用 setLocation() 来调整放缩的视觉幅度。

对 Camera 变换之后,要用 Camera.applyToCanvas(Canvas) 来应用到 Canvas .


Camera .setLocation()

这个⽅法⼀般前两个参数都填 0,第三个参数为负值。由于这个值的单位是硬编码写死的,因此像素密度越⾼的⼿机,相当于 Camera 距离 View 越近,所以最好把这个值写成与机器的 density 成正⽐的⼀个负值,例如 -6 * density .

private val IMAGE_SIZE = 150.dp
private val IMAGE_PADDING = 100.dp

class CameraView2(context: Context?, attrs: AttributeSet?) : View(context, attrs) {

    private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
    private val bitmap = getAvatar(IMAGE_SIZE.toInt())
    private val camera = Camera()

    var topFlip = 0f
        set(value) {
            field = value
            invalidate()
        }
    var bottomFlip = 30f
        set(value) {
            field = value
            invalidate()
        }
    var flipRotation = 30f
        set(value) {
            field = value
            invalidate()
        }

    init {
        camera.setLocation(0f, 0f, -6 * resources.displayMetrics.density)
    }

    override fun onDraw(canvas: Canvas) {
        // 上半部分
        canvas.withSave {
            canvas.translate((IMAGE_PADDING + IMAGE_SIZE / 2f), (IMAGE_PADDING + IMAGE_SIZE / 2f))
            canvas.rotate(-flipRotation)
            camera.save()
            camera.rotateX(topFlip)
            camera.applyToCanvas(canvas)
            camera.restore()
            canvas.clipRect(-IMAGE_SIZE, -IMAGE_SIZE, IMAGE_SIZE, 0f)
            canvas.rotate(flipRotation)
            canvas.translate(-(IMAGE_PADDING + IMAGE_SIZE / 2f), -(IMAGE_PADDING + IMAGE_SIZE / 2f))
            canvas.drawBitmap(bitmap, IMAGE_PADDING, IMAGE_PADDING, paint)
        }

        // 下半部分
        canvas.withSave {
            canvas.translate((IMAGE_PADDING + IMAGE_SIZE / 2f), (IMAGE_PADDING + IMAGE_SIZE / 2f))
            canvas.rotate(-flipRotation)
            camera.save()
            camera.rotateX(bottomFlip)
            camera.applyToCanvas(canvas)
            camera.restore()
            canvas.clipRect(-IMAGE_SIZE, 0f, IMAGE_SIZE, IMAGE_SIZE)
            canvas.rotate(flipRotation)
            canvas.translate(-(IMAGE_PADDING + IMAGE_SIZE / 2f), -(IMAGE_PADDING + IMAGE_SIZE / 2f))
            canvas.drawBitmap(bitmap, IMAGE_PADDING, IMAGE_PADDING, paint)
        }
    }

}

属性动画
ViewPropertyAnimator

使用 View.animate() 创建对象,以及使用 ViewPropertyAnimator.translationX() 等方法设置动画。


ObjectAnimator

使用 ObjectAnimator.ofXxx() 创建对象,以及使用 ObjectAnimator.start() 来主动启动动画,它的优势在于,可以为自定义属性设置动画。

// 为自定义的CircleView添加动画
val animator = ObjectAnimator.ofFloat(view, "radius", 100.dp)
         animator.startDelay = 1000
         animator.duration = 500
         animator.start()

// CircleView
class CircleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
    private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        color = Color.parseColor("#ec5f66")
    }
    private var radius = 20.dp
        set(value) {
            field = value
            invalidate()// 触发重绘
        }


    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        canvas.drawCircle(width / 2f, height / 2f, radius, paint)
    }
}

Interpolator

插值器,用于设置时间完成度到动画完成度的计算公式,即设置动画的速度曲线,通过 setInterpolator(Interpolator) 方法来设置。

常用的用 AccelerateDecelerateInterpolator(先加速再减速)、AccelerateInterpolator(加速)、DecelerateInterpolator(减速)、LinearInterpolator(匀速)。


PropertyValuesHolder

用于设置更加详细的动画,例如多个属性应用于同一个对象。

val bottomFlipHolder = PropertyValuesHolder.ofFloat("bottomFlip", 60f)
val flipRotationHolder = PropertyValuesHolder.ofFloat("flipRotation", 360f)
val topFlipHolder = PropertyValuesHolder.ofFloat("topFlip", -60f)
val holderAnimator = ObjectAnimator.ofPropertyValuesHolder(
    view,
    bottomFlipHolder,
    flipRotationHolder,
    topFlipHolder
)
holderAnimator.duration = 2000
holderAnimator.startDelay = 500
holderAnimator.start()

或者 配合使用 Keyframe ,对一个属性分多个段

val length = 200.dp
val keyframe1 = Keyframe.ofFloat(0f, 0f)
val keyframe2 = Keyframe.ofFloat(0.2f, 0.2f * length)
val keyframe3 = Keyframe.ofFloat(0.8f, 1.6f * length)
val keyframe4 = Keyframe.ofFloat(1.0f, 1.0f * length)
val keyframeHolder = PropertyValuesHolder.ofKeyframe(
    "translationX",
    keyframe1,
    keyframe2,
    keyframe3,
    keyframe4
)
val animator = ObjectAnimator.ofPropertyValuesHolder(view, keyframeHolder)
animator.duration = 2000
animator.startDelay = 500
animator.repeatCount = Animation.INFINITE
animator.start()

AnimatorSet

将多个 Animator 合并在一起使用,先后执行或并列执行都可以

val animatorSet = AnimatorSet()
animatorSet.playSequentially(bottomFlipAnimator, flipRotationAnimator, topFlipAnimator)
animatorSet.start()

TypeEvaluator

用于设置动画完成度到属性具体值的计算公式。默认的 ofInt()、ofFloat() 使用的是自带的 IntEvaluator 和 FloatEvaluator。有时需要自己设置 Evaluator,例如对于颜色,需要为 int 类型 的颜色设置 ArgbEvaluator.

另外,对于不⽀持的类型,也可以使⽤ ofObject() 来在创建 Animator 的同时就设置上 Evaluator .

val animator = ObjectAnimator.ofObject(view, "point", PointFEvaluator(), PointF(200.dp, 300.dp))
        animator.startDelay = 1000
        animator.duration = 500
        animator.start()

// PointFView
class PointFView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {

    private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        strokeWidth = 20.dp
        strokeCap = Paint.Cap.ROUND
        color = Color.parseColor("#ec5f66")
    }
    var point = PointF(0f, 0f)
        set(value) {
            field = value
            invalidate()
        }


    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        canvas.drawPoint(point.x, point.y, paint)
    }
}

ValueAnimator

最基本的 Animator,它不和具体的某个对象联动,⽽是直接对两个数值进⾏渐变计算。

val animator = ValueAnimator.ofFloat(0f, 1f)
        animator.addUpdateListener {
            Log.e("animator", "value: ${it.animatedValue}")
        }
        animator.duration = 1000
        animator.start()

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值