Android?自定義View?加?lifecycle?簡單使用詳解
前言
本文是自定義view中最簡單的使用方法,分別進行 ‘onMeasure’、‘onDraw’、‘自定義樣式’、‘lifecycle’的簡單使用,了解自定義view的使用。
通過lifecycle來控制 動畫的狀態(tài)
一、onMeasure做了什么?
在onMeasure中獲取view 的寬和高 是 ‘0’
測量View
的寬 / 高
- 在某些情況下,需要多次測量
(measure)
才能確定View
最終的寬/高; - 該情況下,
measure
過程后得到的寬 / 高可能不準確; - 此處建議:在
layout
過程中onLayout()
去獲取最終的寬 / 高
必須要了解 MeasureSpec 作用
測量規(guī)格(MeasureSpec)是由測量模式(mode)和測量大小(size)組成,共32位(int類型),其中:
- 測量模式(mode):占測量規(guī)格(MeasureSpec)的高2位;
- 測量大小(size):占測量規(guī)格(MeasureSpec)的低30位。
MeasureSpec類用一個變量封裝了測量模式(mode)和測量大小(size):通過使用二進制,將測量模式(mode)和測量大小(size)打包成一個int值,并提供了打包和解包的方法,這樣的做法是為了減少對象內(nèi)存分配和提高存取效率。具體使用如下所示:
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的 寬高 獲取到是 0 Log.e(TAG, "onMeasure: ${widthSize}-${width}__${heightSize}__${height}") val defWidth = 400 val 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) } }
1、onLayout 做了什么
計算位置,里面包含子view 的情況下才會用到這個函數(shù)
一般繼承自viewGroup或者重新寫layout布局
2、onDraw 做了什么
繪制View
自身,設(shè)置padding 時要在onDraw中計算
1. 繪制view背景
2. 繪制view內(nèi)容
3. 繪制子View
4. 繪制裝飾(漸變框,滑動條等等)
override fun onDraw(canvas: Canvas?) { super.onDraw(canvas) canvas?.let { val pL = paddingLeft val pR = paddingRight val pT = paddingTop val pB = paddingBottom var mHeight = height - pT - pB var mWidth = width - pL - pR val 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 ) } }
3、lifecycle控制動畫的狀態(tài)
自定義view 繼承 DefaultLifecycleObserver 類 然后實現(xiàn) 生命周期=中的方法 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中 進行生命周期監(jiān)聽的綁定 lifecycle.addObserver(customView)
4、代碼示例
自定義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 動畫實現(xiàn)改變顏色 然后 通過 lifecycle 控制動畫的狀態(tài):開始、暫停、恢復(fù)、取消 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.BLUE 1 -> android.graphics.Color.YELLOW 2 -> android.graphics.Color.CYAN 3 -> android.graphics.Color.MAGENTA 4 -> android.graphics.Color.LTGRAY else -> android.graphics.Color.TRANSPARENT } // @TODO 每次設(shè)置顏色后 調(diào)用postInvalidate 重新繪制View postInvalidate() } }) // 動畫無線循環(huán)執(zhí)行 repeatCount = ValueAnimator.INFINITE // 間隔一秒執(zhí)行一次 duration = 1000 } } init { mPaint.color = Color.Blue.hashCode() mPaint.style = Paint.Style.FILL mPaint.strokeWidth = 20f context?.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的 寬高 獲取到是 0 Log.e(TAG, "onMeasure: ${widthSize}-${width}__${heightSize}__${height}") val defWidth = 400 val 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: ") } /** * 作用:根據(jù)給定的 Canvas 自動渲染View包括其所有子 View)。 * 繪制過程: * 1. 繪制view背景 * 2. 繪制view內(nèi)容 * 3. 繪制子View * 4. 繪制裝飾(漸變框,滑動條等等) * 注: * a. 在調(diào)用該方法之前必須要完成 layout 過程 * b. 所有的視圖最終都是調(diào)用 View 的 draw()繪制視圖( ViewGroup 沒有復(fù)寫此方法) * c. 在自定義View時,不應(yīng)該復(fù)寫該方法,而是復(fù)寫 onDraw(Canvas) 方法進行繪制 * d. 若自定義的視圖確實要復(fù)寫該方法,那么需先調(diào)用 super.draw(canvas)完成系統(tǒng)的繪制,然后再進行自定義的繪制 */ override fun onDraw(canvas: Canvas?) { super.onDraw(canvas) canvas?.let { val pL = paddingLeft val pR = paddingRight val pT = paddingTop val pB = paddingBottom var mHeight = height - pT - pB var mWidth = width - pL - pR val 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.MyView android: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 控制動畫的狀態(tài):開始、暫停、恢復(fù)、取消 lifecycle.addObserver(customView) } }
總結(jié)
在自定義View中了解在 onMeasure中進行view 的測量,在onLayout中進行對view位置的控制,在onDraw中進行view的繪制。
通過 lifecycle控制view的生命周期,防止出現(xiàn)內(nèi)存泄露問題如在相應(yīng)的生命周期中操作動畫的執(zhí)行狀態(tài)
到此這篇關(guān)于Android 自定義View 加 lifecycle 簡單使用詳解的文章就介紹到這了,更多相關(guān)Android 自定義View內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android軟件啟動動畫及動畫結(jié)束后跳轉(zhuǎn)的實現(xiàn)方法
這篇文章主要介紹了Android軟件啟動動畫及動畫結(jié)束后跳轉(zhuǎn)的實現(xiàn)方法,實例分析了Android圖片播放及定時器的相關(guān)使用技巧,非常具有使用價值,需要的朋友可以參考下2015-10-10分別用ToolBar和自定義導(dǎo)航欄實現(xiàn)沉浸式狀態(tài)欄
本文主要介紹了分別用ToolBar和自定義導(dǎo)航欄實現(xiàn)沉浸式狀態(tài)欄的方法步驟,具有一定的參考價值,下面跟著小編一起來看下吧2017-01-01Android 8.0升級不跳轉(zhuǎn)應(yīng)用安裝頁面的解決方法
這篇文章主要為大家詳細介紹了Android 8.0升級不跳轉(zhuǎn)應(yīng)用安裝頁面的解決方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-06-06Android編程設(shè)定activity進入和退出效果的方法
這篇文章主要介紹了Android編程設(shè)定activity進入和退出效果的方法,簡單分析了Android Activity進入與退出效果的實現(xiàn)原理及相關(guān)屬性設(shè)置技巧,需要的朋友可以參考下2017-07-07