前言
?本文是自定義view中最簡單的使用方法,分別進行 ‘onMeasure’、‘onDraw’、‘自定義樣式’、‘lifecycle’的簡單使用,了解自定義view的使用。
通過lifecycle來控制 動畫的狀態
一、onMeasure做了什么?
在onMeasure中獲取view 的寬和高 是 ‘0’
?測量View
的寬 / 高
- 在某些情況下,需要多次測量
(measure)
才能確定View
最終的寬/高; - 該情況下,
measure
過程后得到的寬 / 高可能不準確; - 此處建議:在
layout
過程中onLayout()
去獲取最終的寬 / 高
?必須要了解 MeasureSpec 作用
測量規格(MeasureSpec)是由測量模式(mode)和測量大小(size)組成,共32位(int類型),其中:
- 測量模式(mode):占測量規格(MeasureSpec)的高2位;
- 測量大小(size):占測量規格(MeasureSpec)的低30位。
MeasureSpec類用一個變量封裝了測量模式(mode)和測量大小(size):通過使用二進制,將測量模式(mode)和測量大小(size)打包成一個int值,并提供了打包和解包的方法,這樣的做法是為了減少對象內存分配和提高存取效率。具體使用如下所示:
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {super.onMeasure(widthMeasureSpec, heightMeasureSpec)val widthModel = MeasureSpec.getMode(widthMeasureSpec)val widthSize = MeasureSpec.getSize(widthMeasureSpec)val heightModel = MeasureSpec.getMode(heightMeasureSpec)val heightSize = MeasureSpec.getSize(heightMeasureSpec)
// @TODO 在 onMeasure 中獲取view的 寬高 獲取到是 0Log.e(TAG, "onMeasure: ${widthSize}-${width}__${heightSize}__${height}")val defWidth = 400val defHeight = 400
// @TODO MeasureSpec.AT_MOST:wrap_content ; MeasureSpec.EXACTLY:match_parent ;if (widthModel == MeasureSpec.AT_MOST && heightModel == MeasureSpec.AT_MOST) {setMeasuredDimension(defWidth, defHeight)} else if (widthModel == MeasureSpec.AT_MOST) {setMeasuredDimension(defWidth, heightSize)} else if (heightModel == MeasureSpec.AT_MOST) {setMeasuredDimension(widthSize, defHeight)}}
2、onLayout 做了什么
計算位置,里面包含子view 的情況下才會用到這個函數
一般繼承自viewGroup或者重新寫layout布局
3、onDraw 做了什么
繪制View
自身,設置padding 時要在onDraw中計算
1. 繪制view背景 2. 繪制view內容 3. 繪制子View 4. 繪制裝飾(漸變框,滑動條等等)
override fun onDraw(canvas: Canvas?) {super.onDraw(canvas)canvas?.let {val pL = paddingLeftval pR = paddingRightval pT = paddingTopval pB = paddingBottomvar mHeight = height - pT - pBvar mWidth = width - pL - pRval cy = pT.plus(pB).div(2) + mHeight.div(2).toFloat()val cx = pL.plus(pR).div(2) + mWidth.div(2).toFloat()val cc = Math.min(mHeight, mWidth).div(2).toFloat()it.drawCircle(cx,cy,cc,mPaint)}}
4、lifecycle控制動畫的狀態
自定義view 繼承 DefaultLifecycleObserver 類 然后實現 生命周期=中的方法override fun onStart(owner: LifecycleOwner) {super.onStart(owner)animSetColor.start()}override fun onDestroy(owner: LifecycleOwner) {super.onDestroy(owner)animSetColor.cancel()}override fun onPause(owner: LifecycleOwner) {super.onPause(owner)animSetColor.pause()}override fun onResume(owner: LifecycleOwner) {super.onResume(owner)animSetColor.resume()}在Act中 進行生命周期監聽的綁定lifecycle.addObserver(customView)
5、代碼示例
自定義View代碼
/*** @TODO 自定義view***/
class MyView(context: Context?, attrs: AttributeSet?) :View(context, attrs), DefaultLifecycleObserver {private val mPaint by lazy { Paint() }private val TAG = "MyView"private var i = 0// @TODO 動畫實現改變顏色 然后 通過 lifecycle 控制動畫的狀態:開始、暫停、恢復、取消private val animSetColor by lazy {ValueAnimator.ofInt(0, 100).apply {addListener(object : AnimatorListener {override fun onAnimationStart(animation: Animator) {}override fun onAnimationEnd(animation: Animator) {}override fun onAnimationCancel(animation: Animator) {}override fun onAnimationRepeat(animation: Animator) {i++if (i % 2 == 0) {mPaint.color = android.graphics.Color.BLUE}mPaint.color = when (i % 5) {0 -> android.graphics.Color.BLUE1 -> android.graphics.Color.YELLOW2 -> android.graphics.Color.CYAN3 -> android.graphics.Color.MAGENTA4 -> android.graphics.Color.LTGRAYelse -> android.graphics.Color.TRANSPARENT}
// @TODO 每次設置顏色后 調用postInvalidate 重新繪制ViewpostInvalidate()}})
// 動畫無線循環執行repeatCount = ValueAnimator.INFINITE
// 間隔一秒執行一次duration = 1000}}init {mPaint.color = Color.Blue.hashCode()mPaint.style = Paint.Style.FILLmPaint.strokeWidth = 20fcontext?.obtainStyledAttributes(attrs, R.styleable.MyView)?.apply {mPaint.color = getColor(R.styleable.MyView_circlr_color, android.graphics.Color.GREEN)recycle()}}override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {super.onMeasure(widthMeasureSpec, heightMeasureSpec)val widthModel = MeasureSpec.getMode(widthMeasureSpec)val widthSize = MeasureSpec.getSize(widthMeasureSpec)val heightModel = MeasureSpec.getMode(heightMeasureSpec)val heightSize = MeasureSpec.getSize(heightMeasureSpec)
// @TODO 在 onMeasure 中獲取view的 寬高 獲取到是 0Log.e(TAG, "onMeasure: ${widthSize}-${width}__${heightSize}__${height}")val defWidth = 400val defHeight = 400
// @TODO MeasureSpec.AT_MOST:wrap_content ; MeasureSpec.EXACTLY:match_parent ;if (widthModel == MeasureSpec.AT_MOST && heightModel == MeasureSpec.AT_MOST) {setMeasuredDimension(defWidth, defHeight)} else if (widthModel == MeasureSpec.AT_MOST) {setMeasuredDimension(defWidth, heightSize)} else if (heightModel == MeasureSpec.AT_MOST) {setMeasuredDimension(widthSize, defHeight)}}
//掛在到Act上時
// override fun onAttachedToWindow() {
// super.onAttachedToWindow()
// Log.e(TAG, "onAttachedToWindow: ")
// anim.start()
// }//在Act 銷毀時
// override fun onDetachedFromWindow() {
// super.onDetachedFromWindow()
// Log.e(TAG, "onDetachedFromWindow: ")
// anim.cancel()
//
// }override fun onStart(owner: LifecycleOwner) {super.onStart(owner)animSetColor.start()}override fun onDestroy(owner: LifecycleOwner) {super.onDestroy(owner)animSetColor.cancel()}override fun onPause(owner: LifecycleOwner) {super.onPause(owner)animSetColor.pause()}override fun onResume(owner: LifecycleOwner) {super.onResume(owner)animSetColor.resume()}override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {super.onLayout(changed, left, top, right, bottom)Log.e(TAG, "onLayout: ")}/*** 作用:根據給定的 Canvas 自動渲染View包括其所有子 View)。* 繪制過程:* 1. 繪制view背景* 2. 繪制view內容* 3. 繪制子View* 4. 繪制裝飾(漸變框,滑動條等等)* 注:* a. 在調用該方法之前必須要完成 layout 過程* b. 所有的視圖最終都是調用 View 的 draw()繪制視圖( ViewGroup 沒有復寫此方法)* c. 在自定義View時,不應該復寫該方法,而是復寫 onDraw(Canvas) 方法進行繪制* d. 若自定義的視圖確實要復寫該方法,那么需先調用 super.draw(canvas)完成系統的繪制,然后再進行自定義的繪制*/override fun onDraw(canvas: Canvas?) {super.onDraw(canvas)canvas?.let {val pL = paddingLeftval pR = paddingRightval pT = paddingTopval pB = paddingBottomvar mHeight = height - pT - pBvar mWidth = width - pL - pRval cy = pT.plus(pB).div(2) + mHeight.div(2).toFloat()val cx = pL.plus(pR).div(2) + mWidth.div(2).toFloat()val cc = Math.min(mHeight, mWidth).div(2).toFloat()it.drawCircle(cx,cy,cc,mPaint)}}
}
自定義View的xml樣式文件
<?xml version="1.0" encoding="utf-8"?>
<resources><declare-styleable name="MyView"><attr name="circlr_color" format="color"/></declare-styleable>
</resources>
layout布局文件
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:background="#11008811"tools:context=".CustomViewActivity"><com.andriod.police.view.MyViewandroid:id="@+id/customView"android:layout_width="wrap_content"android:layout_height="130dp"android:background="#11f08811"app:circlr_color="@color/cardview_light_background"android:padding="20dp"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent" /></androidx.constraintlayout.widget.ConstraintLayout>
Act
class CustomViewActivity : AppCompatActivity() {private val customView: MyView by lazy { findViewById(R.id.customView) }override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_custom_view)
// @TODO 通過 lifecycle 控制動畫的狀態:開始、暫停、恢復、取消lifecycle.addObserver(customView)}
}
總結
?在自定義View中了解在 onMeasure中進行view 的測量,在onLayout中進行對view位置的控制,在onDraw中進行view的繪制。
通過 lifecycle控制view的生命周期,防止出現內存泄露問題如在相應的生命周期中操作動畫的執行狀態