Android?自定義View?加?lifecycle?簡單使用詳解
前言
本文是自定義view中最簡單的使用方法,分別進(jìn)行 ‘onMeasure’、‘onDraw’、‘自定義樣式’、‘lifecycle’的簡單使用,了解自定義view的使用。
通過lifecycle來控制 動畫的狀態(tài)

一、onMeasure做了什么?
在onMeasure中獲取view 的寬和高 是 ‘0’
測量View的寬 / 高
- 在某些情況下,需要多次測量
(measure)才能確定View最終的寬/高; - 該情況下,
measure過程后得到的寬 / 高可能不準(zhǔn)確; - 此處建議:在
layout過程中onLayout()去獲取最終的寬 / 高
必須要了解 MeasureSpec 作用
測量規(guī)格(MeasureSpec)是由測量模式(mode)和測量大小(size)組成,共32位(int類型),其中:
- 測量模式(mode):占測量規(guī)格(MeasureSpec)的高2位;
- 測量大小(size):占測量規(guī)格(MeasureSpec)的低30位。

MeasureSpec類用一個(gè)變量封裝了測量模式(mode)和測量大小(size):通過使用二進(jìn)制,將測量模式(mode)和測量大小(size)打包成一個(gè)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 做了什么
計(jì)算位置,里面包含子view 的情況下才會用到這個(gè)函數(shù)
一般繼承自viewGroup或者重新寫layout布局

2、onDraw 做了什么
繪制View自身,設(shè)置padding 時(shí)要在onDraw中計(jì)算
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 類 然后實(shí)現(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中 進(jìn)行生命周期監(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 動畫實(shí)現(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上時(shí)
// override fun onAttachedToWindow() {
// super.onAttachedToWindow()
// Log.e(TAG, "onAttachedToWindow: ")
// anim.start()
// }
//在Act 銷毀時(shí)
// 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時(shí),不應(yīng)該復(fù)寫該方法,而是復(fù)寫 onDraw(Canvas) 方法進(jìn)行繪制
* d. 若自定義的視圖確實(shí)要復(fù)寫該方法,那么需先調(diào)用 super.draw(canvas)完成系統(tǒng)的繪制,然后再進(jìn)行自定義的繪制
*/
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中進(jìn)行view 的測量,在onLayout中進(jìn)行對view位置的控制,在onDraw中進(jìn)行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通過HTTP協(xié)議實(shí)現(xiàn)上傳文件數(shù)據(jù)
這篇文章主要為大家詳細(xì)介紹了Android通過HTTP協(xié)議實(shí)現(xiàn)上傳文件數(shù)據(jù),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-09-09
Android超詳細(xì)介紹自定義多選框與點(diǎn)擊按鈕跳轉(zhuǎn)界面的實(shí)現(xiàn)
這篇文章主要介紹了在Android開發(fā)中如何來實(shí)現(xiàn)自定義多選框以及如何實(shí)現(xiàn)點(diǎn)擊按鈕跳轉(zhuǎn)界面的功能,感興趣的朋友快來看看吧2022-03-03
Android定時(shí)器和Handler用法實(shí)例分析
這篇文章主要介紹了Android定時(shí)器和Handler用法,實(shí)例分析了Android中的定時(shí)器與Handler相關(guān)使用技巧,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2015-04-04
TabLayout+ViewPager實(shí)現(xiàn)切頁的示例代碼
這篇文章主要介紹了TabLayout+ViewPager實(shí)現(xiàn)切頁的示例代碼,可實(shí)現(xiàn)左右滑動切換視圖界面和點(diǎn)擊切換,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2019-01-01

