1+ package com.allen.androidcustomview.widget
2+
3+ import android.animation.Animator
4+ import android.animation.ValueAnimator
5+ import android.content.Context
6+ import android.graphics.*
7+ import android.graphics.drawable.BitmapDrawable
8+ import android.graphics.drawable.Drawable
9+ import android.util.AttributeSet
10+ import android.view.MotionEvent
11+ import android.view.View
12+ import com.allen.androidcustomview.R
13+ import kotlin.math.atan2
14+
15+
16+ /* *
17+ * <pre>
18+ * @author : Allen
19+ * date : 2019/07/23
20+ * desc : 小汽车跟随轨迹运动
21+ * </pre>
22+ */
23+ class CarMoveView @JvmOverloads constructor(context : Context , attrs : AttributeSet ? = null , defStyleAttr : Int = 0 ) : View(context, attrs, defStyleAttr) {
24+
25+ private var mWidth = 0
26+ private var mHeight = 0
27+
28+ /* *
29+ * 背景颜色
30+ */
31+ private var mPathColor = Color .RED
32+ private var mPaint = Paint ()
33+ private var mCarPaint = Paint ()
34+
35+ private var mRect = Rect ()
36+ private var mMovePath = Path ()
37+
38+ private var mStartX = 0f
39+ private var mStartY = 0f
40+
41+ private var pathMeasure: PathMeasure = PathMeasure ()
42+
43+ private val pos = FloatArray (2 )
44+ private val tan = FloatArray (2 )
45+
46+ private var isMoveCar = false
47+
48+ private var mRectWidth = 30
49+
50+ private var mDuration = 5
51+ private var mCarDrawableRes: Drawable ? = null
52+ private var mCarBitmapRes: Bitmap ? = null
53+
54+ init {
55+ initView(context, attrs, defStyleAttr)
56+ initPaint()
57+ mCarBitmapRes = (mCarDrawableRes as ? BitmapDrawable )?.bitmap
58+ }
59+
60+ private fun initView (context : Context , attrs : AttributeSet ? , defStyleAttr : Int ) {
61+ val typedArray = context.obtainStyledAttributes(attrs, R .styleable.CarMoveView )
62+ mPathColor = typedArray.getColor(R .styleable.CarMoveView_carMovePathColor , mPathColor)
63+ mDuration = typedArray.getInt(R .styleable.CarMoveView_carMoveDuration , mDuration)
64+ mCarDrawableRes = typedArray.getDrawable(R .styleable.CarMoveView_carMoveDrawableRes )
65+ typedArray.recycle()
66+ }
67+
68+ private fun initPaint () {
69+ mPaint.color = mPathColor
70+ mPaint.style = Paint .Style .STROKE
71+ mPaint.isAntiAlias = true
72+
73+ mCarPaint.color = mPathColor
74+ mCarPaint.style = Paint .Style .FILL
75+ mCarPaint.isAntiAlias = true
76+ }
77+
78+ override fun onSizeChanged (w : Int , h : Int , oldw : Int , oldh : Int ) {
79+ super .onSizeChanged(w, h, oldw, oldh)
80+ mWidth = w
81+ mHeight = h
82+ }
83+
84+ override fun onDraw (canvas : Canvas ? ) {
85+ super .onDraw(canvas)
86+ drawPath(canvas)
87+ drawMoveCar(canvas)
88+ }
89+
90+ private fun drawPath (canvas : Canvas ? ) {
91+ canvas?.drawPath(mMovePath, mPaint)
92+ }
93+
94+ private fun drawMoveCar (canvas : Canvas ? ) {
95+ pathMeasure.setPath(mMovePath, false )
96+ if (isMoveCar) {
97+ // 计算图片旋转角度
98+ val degrees = (atan2(tan[1 ].toDouble(), tan[0 ].toDouble()) * 180.0 / Math .PI ).toFloat()
99+ canvas?.rotate(degrees, pos[0 ], pos[1 ])
100+ // 小车中心点在运行轨道上
101+ mRect.set((pos[0 ] - mRectWidth).toInt(), (pos[1 ] - mRectWidth).toInt(), (pos[0 ] + mRectWidth).toInt(), (pos[1 ] + mRectWidth).toInt())
102+ // 小车轮子在运行轨道上
103+ // mRect.set((pos[0] - mRectWidth).toInt(), (pos[1] - mRectWidth * 2).toInt(), (pos[0] + mRectWidth).toInt(), (pos[1]).toInt())
104+ if (mCarBitmapRes == null ) {
105+ canvas?.drawRect(mRect, mCarPaint)
106+ } else {
107+ canvas?.drawBitmap(mCarBitmapRes, null , mRect, mPaint)
108+ }
109+ }
110+ }
111+
112+ override fun onTouchEvent (event : MotionEvent ): Boolean {
113+ when (event.action) {
114+ MotionEvent .ACTION_DOWN -> {
115+ mStartX = event.x
116+ mStartY = event.y
117+ mMovePath.reset()
118+ mMovePath.moveTo(mStartX, mStartY)
119+ return true
120+ }
121+ MotionEvent .ACTION_MOVE -> {
122+ val endX = (mStartX + event.x) / 2
123+ val endY = (mStartY + event.y) / 2
124+ mMovePath.quadTo(mStartX, mStartY, endX, endY)
125+ mStartX = event.x
126+ mStartY = event.y
127+ invalidate()
128+ return true
129+ }
130+ }
131+ return super .onTouchEvent(event)
132+ }
133+
134+ fun startAnim () {
135+ isMoveCar = true
136+ val valueAnimator = ValueAnimator .ofFloat(0f , pathMeasure.length)
137+ valueAnimator.duration = mDuration * 1000L
138+ valueAnimator.addUpdateListener {
139+ val distance: Float = it.animatedValue as Float
140+ pathMeasure.getPosTan(distance, pos, tan)
141+ invalidate()
142+ }
143+ valueAnimator.addListener(object : Animator .AnimatorListener {
144+ override fun onAnimationRepeat (animation : Animator ? ) {}
145+
146+ override fun onAnimationEnd (animation : Animator ? ) {
147+ isMoveCar = false
148+ }
149+
150+ override fun onAnimationCancel (animation : Animator ? ) {
151+ isMoveCar = false
152+ }
153+
154+ override fun onAnimationStart (animation : Animator ? ) {}
155+
156+ })
157+ valueAnimator.start()
158+ }
159+ }
0 commit comments