Compose狀態(tài)保存rememberSaveable原理解析
前言
我曾經(jīng)在一篇介紹 Compose Navigation 的文章 中提到了 Navigation 的狀態(tài)保存實(shí)際是由 rememberSaveable 實(shí)現(xiàn)的,有同學(xué)反饋希望單獨(dú)介紹一下 rememberSaveable 的功能及實(shí)現(xiàn)原理。
我們都知道 remember 可以保存數(shù)據(jù)、避免狀態(tài)因重組而丟失,但它依然無法避免在 ConfigurationChanged 時(shí)的數(shù)據(jù)丟失。想要在橫豎屏切換等場(chǎng)景下依然保存狀態(tài),就需要使用 rememberSavable。
從一個(gè)報(bào)錯(cuò)說起
首先,在代碼使用上 rememberSaveable 和 remember 沒有區(qū)別:
//保存列表狀態(tài)
val list = rememberSaveable {
mutableListOf<String>()
}
//保存普通狀態(tài)
var value by rememberSaveable {
mutableStateOf("")
}
如上,只要將 remember 改為 rememberSaveable,我們創(chuàng)建的狀態(tài)就可以跨越橫豎屏切換甚至跨越進(jìn)程持續(xù)保存了。不過 rememberSaveable 中并非任何類型的值都可以存儲(chǔ):
data class User(
val name: String = ""
)
val user = rememberSaveable {
User()
}
上面代碼運(yùn)行時(shí)會(huì)發(fā)生錯(cuò)誤:
java.lang.IllegalArgumentException: User(name=) cannot be saved using the current SaveableStateRegistry. The default implementation only supports types which can be stored inside the Bundle. Please consider implementing a custom Saver for this class and pass it to rememberSaveable().
User 無法存入 Bundle。這非常合理,因?yàn)?rememberSaveable 中數(shù)據(jù)的持久化最終在 ComponentActivity#onSaveInstanceState 中執(zhí)行,這需要借助到 Bundle 。
rememberSaveable 源碼分析
那么,rememberSaveable 是如何關(guān)聯(lián)到 onSaveInstanceState 的呢?接下來簡(jiǎn)單分析一下內(nèi)部實(shí)現(xiàn)
@Composable
fun <T : Any> rememberSaveable(
vararg inputs: Any?,
saver: Saver<T, out Any> = autoSaver(),
key: String? = null,
init: () -> T
): T {
//...
// 通過 CompositionLocal 獲取 SaveableStateRegistry
val registry = LocalSaveableStateRegistry.current
// 通過 init 獲取需要保存的數(shù)據(jù)
val value = remember(*inputs) {
// registry 根據(jù) key 恢復(fù)數(shù)據(jù),恢復(fù)的數(shù)據(jù)是一個(gè) Saveable
val restored = registry?.consumeRestored(finalKey)?.let {
// 使用 Saver 將 Saveable 轉(zhuǎn)換為業(yè)務(wù)類型
saver.restore(it)
}
restored ?: init()
}
// 用一個(gè) MutableState 保存 Saver,主要是借助 State 的事務(wù)功能避免一致性問題發(fā)生
val saverHolder = remember { mutableStateOf(saver) }
saverHolder.value = saver
if (registry != null) {
DisposableEffect(registry, finalKey, value) {
//ValueProvider:通過 Saver#save 存儲(chǔ)數(shù)據(jù)
val valueProvider = {
with(saverHolder.value) { SaverScope { registry.canBeSaved(it) }.save(value) }
}
//試探數(shù)值是否可被保存
registry.requireCanBeSaved(valueProvider())
//將ValueProvider 注冊(cè)到 registry ,等到合適的時(shí)機(jī)被調(diào)用
val entry = registry.registerProvider(finalKey, valueProvider)
onDispose {
entry.unregister()
}
}
}
return value
}
如上,邏輯很清晰,主要是圍繞 registry 展開的:
- 通過 key 恢復(fù)持久化的數(shù)據(jù)
- 基于 key 注冊(cè) ValueProvider,等待合適時(shí)機(jī)執(zhí)行數(shù)據(jù)持久化
- 在 onDispose 中被注銷注冊(cè)
registry 是一個(gè) SaveableStateRegistry 。
恢復(fù) key 的數(shù)據(jù)
rememberSaveable 是加強(qiáng)版的 remember,首先要具備 remember 的能力,可以看到內(nèi)部也確實(shí)是調(diào)用了 remember 來創(chuàng)建數(shù)據(jù)同時(shí)緩存到 Composition 中。init 提供了 remember 數(shù)據(jù)的首次創(chuàng)建。被創(chuàng)建的數(shù)據(jù)在后續(xù)某個(gè)時(shí)間點(diǎn)進(jìn)行持久化,下次執(zhí)行 rememberSaveable 時(shí)會(huì)嘗試恢復(fù)之前持久化的數(shù)據(jù)。具體過程分為以下兩步:
- 通過 registry.consumeRestored 查找 key 獲取 Saveable,
- Saveable 經(jīng)由 saver.restore 轉(zhuǎn)換為業(yè)務(wù)類型。
上述過程涉及到兩個(gè)角色:
- SaveableStateRegistry:通過 CompositionLocal 獲取,它負(fù)責(zé)將 Bundle 中的數(shù)據(jù)反序列化后,返回一個(gè) Saveable
- Saver:Saver 默認(rèn)有 autoSaver 創(chuàng)建,負(fù)責(zé) Saveable 與業(yè)務(wù)數(shù)據(jù)之間的轉(zhuǎn)換。
Saveable 并不是一個(gè)在具體類型,它可以是可被持久化(寫入 Bundle)的任意類型。對(duì)于 autoSaver 來說, 這個(gè) Saveable 就是業(yè)務(wù)數(shù)據(jù)類型本身。
private val AutoSaver = Saver<Any?, Any>(
save = { it },
restore = { it }
)
對(duì)于一些復(fù)雜的業(yè)務(wù)結(jié)構(gòu)體,有時(shí)并非是所有字段都需要持久化。Saver 為我們提供了這樣一個(gè)機(jī)會(huì)機(jī)會(huì),可以按照需要將業(yè)務(wù)類型轉(zhuǎn)化為可序列化類型。Compose 也提供了兩個(gè)預(yù)置的 Saver:ListSaver 和 MapSaver,可以用來轉(zhuǎn)換成 List 或者 Map。
關(guān)于恢復(fù)數(shù)據(jù)的 Key :可以看到數(shù)據(jù)的保存和恢復(fù)都依賴一個(gè) key,按道理 key 需要在保存和恢復(fù)時(shí)嚴(yán)格保持一致 ,但我們平日調(diào)用 rememberSaveable 時(shí)并沒有指定具體的 key,那么在橫豎屏切換甚至進(jìn)程重啟后是如何恢復(fù)數(shù)據(jù)的呢?其實(shí)這個(gè) key 是 Compose 自動(dòng)幫我們?cè)O(shè)置的,它就是編譯期插樁生成的基于代碼位置的 key ,所以可以保證每次進(jìn)程執(zhí)行到此處都保持不變
注冊(cè) ValueProvider
SaveableStateRegistry 在 DisposableEffect 中關(guān)聯(lián) key 注冊(cè) ValueProvider。 ValueProvider 是一個(gè) lambda,內(nèi)部會(huì)調(diào)用 Saver#save 將業(yè)務(wù)數(shù)據(jù)轉(zhuǎn)化為 Saveable。
Saver#save 是 SaverScope 的擴(kuò)展函數(shù),所以這里需要?jiǎng)?chuàng)建一個(gè) SaverScope 來調(diào)用 save 方法。SaverScope 主要用來提供 canBeSaved 方法,我們?cè)谧远x Saver 時(shí)可以用來檢查類型是否可被持久化
ValueProvider 創(chuàng)建好后緊接著會(huì)調(diào)用 registry.registerProvider 進(jìn)行注冊(cè),等待合適的時(shí)機(jī)(比如 Activity 的 onSaveInstanceState)被調(diào)用。在注冊(cè)之前,先調(diào)用 requireCanBeSaved 判斷數(shù)據(jù)類型是否可以保存,這也就是文章前面報(bào)錯(cuò)的地方。先 mark 一下,稍后我們看一下具體檢查的實(shí)現(xiàn)。
注銷 registry
最后在 onDispose 中調(diào)用 unregister 注銷之前的注冊(cè) 。
rememberSaveable 的基本流程理清楚了,可以看見主角就是 registry,因此有必要深入 SaveableStateRegistry 去看一下。我們順著 LocalSaveableStateRegistry 可以很容易找到 registry 的出處。
DisposableSavableStateRegistry 源碼分析
override fun setContent(content: @Composable () -> Unit) {
//...
ProvideAndroidCompositionLocals(owner, content)
//...
}
@Composable
@OptIn(ExperimentalComposeUiApi::class)
internal fun ProvideAndroidCompositionLocals(
owner: AndroidComposeView,
content: @Composable () -> Unit
) {
val view = owner
val context = view.context
//...
val viewTreeOwners = owner.viewTreeOwners ?: throw IllegalStateException(
"Called when the ViewTreeOwnersAvailability is not yet in Available state"
)
val saveableStateRegistry = remember {
DisposableSaveableStateRegistry(view, viewTreeOwners.savedStateRegistryOwner)
}
//...
CompositionLocalProvider(
//...
LocalSaveableStateRegistry provides saveableStateRegistry,
//...
) {
ProvideCommonCompositionLocals(
owner = owner,
//...
content = content
)
}
}
如上,我們?cè)?Activity 的 setContent 中設(shè)置各種 CompositionLocal,其中就有 LocalSaveableStateRegistry,所以 registry 不僅是一個(gè) SaveableStateRegistry,更是一個(gè) DisposableSaveableStateRegistry 。
接下來看一下 DisposableSaveableStateRegistry 的創(chuàng)建過程 。
saveableStateRegistry 與 SavedStateRegistry
注意下面這個(gè) DisposableSaveableStateRegistry 不是真正的構(gòu)造函數(shù),它是同名構(gòu)造函數(shù)的一個(gè) Wrapper,在調(diào)用構(gòu)造函數(shù)創(chuàng)建實(shí)例之前,先調(diào)用 androidxRegistry 進(jìn)行了一系列處理:
internal fun DisposableSaveableStateRegistry(
id: String,
savedStateRegistryOwner: SavedStateRegistryOwner
): DisposableSaveableStateRegistry {
//基于 id 創(chuàng)建 key
val key = "${SaveableStateRegistry::class.java.simpleName}:$id"
// 基于 key 獲取 bundle 數(shù)據(jù)
val androidxRegistry = savedStateRegistryOwner.savedStateRegistry
val bundle = androidxRegistry.consumeRestoredStateForKey(key)
val restored: Map<String, List<Any?>>? = bundle?.toMap()
// 創(chuàng)建 saveableStateRegistry,傳入 restored 以及 canBeSaved
val saveableStateRegistry = SaveableStateRegistry(restored) {
canBeSavedToBundle(it)
}
val registered = try {
androidxRegistry.registerSavedStateProvider(key) {
//調(diào)用 register#performSave 并且轉(zhuǎn)為 Bundle
saveableStateRegistry.performSave().toBundle()
}
true
} catch (ignore: IllegalArgumentException) {
false
}
return DisposableSaveableStateRegistry(saveableStateRegistry) {
if (registered) {
androidxRegistry.unregisterSavedStateProvider(key)
}
}
}
androidxRigistry 跟 rememberSaveable 中的 registry 做的事情類似:
- 基于 key 恢復(fù) bundle 數(shù)據(jù),
- 基于 key 注冊(cè) SavedStateProvider。
但 androidxRegistry 不是一個(gè) SaveableStateRegistry 而是一個(gè) SavedStateRegistry。名字上有點(diǎn)繞,后者來自 androidx.savedstate ,屬于平臺(tái)代碼,而 SaveableStateRegistry 屬于 compose-runtime 的平臺(tái)無關(guān)代碼??梢娺@個(gè)構(gòu)造函數(shù)的同名 Wrapper 很重要,他就像一個(gè)橋梁,解耦和關(guān)聯(lián)了平臺(tái)相關(guān)和平臺(tái)無關(guān)代碼。
DisposableSaveableStateRegistry 與 SaveableStateRegistryImpl
DisposableSaveableStateRegistry 真正的構(gòu)造函數(shù)定義如下:
internal class DisposableSaveableStateRegistry(
saveableStateRegistry: SaveableStateRegistry,
private val onDispose: () -> Unit
) : SaveableStateRegistry by saveableStateRegistry {
fun dispose() {
onDispose()
}
}
這里用了參數(shù) saveableStateRegistry 作為 SaveableStateRegistry 接口的代理。saveableStateRegistry 實(shí)際是一個(gè) SaveableStateRegistryImpl 對(duì)象,它像這樣創(chuàng)建:
val saveableStateRegistry = SaveableStateRegistry(restored) {
canBeSavedToBundle(it)
}
fun SaveableStateRegistry(
restoredValues: Map<String, List<Any?>>?,
canBeSaved: (Any) -> Boolean
): SaveableStateRegistry = SaveableStateRegistryImpl(restoredValues, canBeSaved)
SaveableStateRegistryImpl 被創(chuàng)建時(shí)傳入兩個(gè)參數(shù):
- restoredValues:androidxRegistry 恢復(fù)的 bundle 數(shù)據(jù),是一個(gè) Map 對(duì)象。
- canBeSaved : 用來檢查數(shù)據(jù)是否可持久化,可以的看到這里實(shí)際調(diào)用了 canBeSavedToBundle。
canBeSavedToBundle
文章開頭的報(bào)錯(cuò)就是 requireCanBeSaved -> canBeSavedToBundle 檢查出來的,通過 canBeSavedToBundle 看一下 rememberSaveable 支持的持久化類型:
private fun canBeSavedToBundle(value: Any): Boolean {
// SnapshotMutableStateImpl is Parcelable, but we do extra checks
if (value is SnapshotMutableState<*>) {
if (value.policy === neverEqualPolicy<Any?>() ||
value.policy === structuralEqualityPolicy<Any?>() ||
value.policy === referentialEqualityPolicy<Any?>()
) {
val stateValue = value.value
return if (stateValue == null) true else canBeSavedToBundle(stateValue)
} else {
return false
}
}
for (cl in AcceptableClasses) {
if (cl.isInstance(value)) {
return true
}
}
return false
}
private val AcceptableClasses = arrayOf(
Serializable::class.java,
Parcelable::class.java,
String::class.java,
SparseArray::class.java,
Binder::class.java,
Size::class.java,
SizeF::class.java
)
首先, SnapshotMutableState 允許被持久化,因?yàn)槲覀冃枰?rememberSaveable 中調(diào)用 mutableStateOf;其次,SnapshotMutableState 的泛型必須是 AcceptableClasses 中的類型,我們自定義的 User 顯然不符合要求,因此報(bào)了開頭的錯(cuò)誤。
SaveableStateRegistryImpl 源碼分析
前面理清了幾個(gè) Registry 類型的關(guān)系,整理如下圖

SaveableStateRegistry 接口的各主要方法都由 SaveableStateRegistryImpl 代理的:
- consumeRestored:根據(jù) key 恢復(fù)數(shù)據(jù)
- registerProvider:注冊(cè) ValueProvider
- canBeSaved:用來檢查數(shù)據(jù)是否是可保存類型
- performSave:執(zhí)行數(shù)據(jù)保存
canBeSaved 前面介紹過,其實(shí)會(huì)回調(diào) canBeSavedToBundle。接下來看一下 SaveableStateRegistryImpl 中其他幾個(gè)方法是如何實(shí)現(xiàn)的:
consumeRestored
override fun consumeRestored(key: String): Any? {
val list = restored.remove(key)
return if (list != null && list.isNotEmpty()) {
if (list.size > 1) {
restored[key] = list.subList(1, list.size)
}
list[0]
} else {
null
}
}
我們知道 restored 是從 Bundle 中恢復(fù)的數(shù)據(jù),實(shí)際是一個(gè) Map了類型。而 consumeRestored 就是在 restored 中通過 key 查找數(shù)據(jù)。restore 的 Value 是 List 類型。當(dāng)恢復(fù)數(shù)據(jù)時(shí),只保留最后一個(gè)只。順便吐槽一下 consumeRestored 這個(gè)名字,將 restore 這個(gè) private 成員信息暴露給了外面,有些莫名其妙。
registerProvider
override fun registerProvider(key: String, valueProvider: () -> Any?): Entry {
require(key.isNotBlank()) { "Registered key is empty or blank" }
@Suppress("UNCHECKED_CAST")
valueProviders.getOrPut(key) { mutableListOf() }.add(valueProvider)
return object : Entry {
override fun unregister() {
val list = valueProviders.remove(key)
list?.remove(valueProvider)
if (list != null && list.isNotEmpty()) {
// if there are other providers for this key return list back to the map
valueProviders[key] = list
}
}
}
}
將 ValueProvider 注冊(cè)到 valueProviders ,valueProviders 也是一個(gè)值為 List 的 Map,同一個(gè) Key 可以對(duì)應(yīng)多個(gè) Value。返回的 Entry 用于 onDispose 中調(diào)用 unregister。
DisposableSaveableStateRegistry 是一個(gè) CompositionLocal 單例,所以需要 unregister 避免不必要的泄露。注意這里要確保同一個(gè) key 中的 List 中的其它值不被移除
不解:什么情況下同一個(gè) key 會(huì) registerProvider 多個(gè)值呢?
performSave
override fun performSave(): Map<String, List<Any?>> {
val map = restored.toMutableMap()
valueProviders.forEach { (key, list) ->
if (list.size == 1) {
val value = list[0].invoke()
if (value != null) {
check(canBeSaved(value))
map[key] = arrayListOf<Any?>(value)
}
} else {
map[key] = List(list.size) { index ->
val value = list[index].invoke()
if (value != null) {
check(canBeSaved(value))
}
value
}
}
}
return map
}
在這里調(diào)用了 ValueProvider 獲取數(shù)據(jù)后存入 restored ,這里也是有針對(duì) Value 是 List 類型的特別處理。performSave 的調(diào)用時(shí)機(jī)前面已經(jīng)出現(xiàn)了,是 androidxRegistry 注冊(cè)的 Provider 中調(diào)用:
androidxRegistry.registerSavedStateProvider(key) {
//調(diào)用 register#performSave 并且轉(zhuǎn)為 Bundle
saveableStateRegistry.performSave().toBundle()
}
SavedStateProvider 會(huì)在 onSaveInstance 時(shí)被執(zhí)行。
至此, rememberSaveable 持久化發(fā)生的時(shí)機(jī)與平臺(tái)進(jìn)行了關(guān)聯(lián)。
最后回看 androidxRegistry
最后我們?cè)倩乜匆幌?DisposableSavableStateRegistry,主要是使用 androidxRegistry 獲取 key 對(duì)應(yīng)的數(shù)據(jù),并注冊(cè) key 對(duì)應(yīng)的 Provider。那么 androidxRegistry 和 key 是怎么來的?
internal fun DisposableSaveableStateRegistry(
id: String,
savedStateRegistryOwner: SavedStateRegistryOwner
): DisposableSaveableStateRegistry {
val key = "${SaveableStateRegistry::class.java.simpleName}:$id"
val androidxRegistry = savedStateRegistryOwner.savedStateRegistry
//...
}
先說 key 。key 由 id 唯一決定,而這個(gè) id 其實(shí)是 ComposeView 的 layoutId。我們知道 ComposeView 是 Activity/Fragment 承載 Composable 的容器,rememberSaveable 會(huì)按照 ComposeView 為單位來持久化數(shù)據(jù)。
因?yàn)槟?ComposeView 的 id 決定了 rememberSaveable 存儲(chǔ)數(shù)據(jù)的位置,如果 Activity/Fragment 范圍內(nèi)如果有多個(gè) ComposeView 使用了同一個(gè) id,則只有第一個(gè) ComposeView 能正?;謴?fù)數(shù)據(jù),這一點(diǎn)要特別注意
再看一下 androidxRegistry,他由 SavedStateRegistryOwner 提供,而這個(gè) owner 是ComposeView 被 attach 到 Activity 時(shí)賦的值,就是 Activity 本身:
public class ComponentActivity extends androidx.core.app.ComponentActivity implements
ContextAware,
LifecycleOwner,
ViewModelStoreOwner,
HasDefaultViewModelProviderFactory,
SavedStateRegistryOwner, // ComponentActivity 是一個(gè) SavedStateRegistryOwner
OnBackPressedDispatcherOwner,
ActivityResultRegistryOwner,
ActivityResultCaller {
//...
public final SavedStateRegistry getSavedStateRegistry() {
return mSavedStateRegistryController.getSavedStateRegistry();
}
//...
}
mSavedStateRegistryController 會(huì)在 Activity 重建時(shí) onCreate 中調(diào)用 performRestore;在 onSaveInstanceState 時(shí)執(zhí)行 performSave。
protected void onCreate(@Nullable Bundle savedInstanceState) {
mSavedStateRegistryController.performRestore(savedInstanceState);
//...
}
protected void onSaveInstanceState(@NonNull Bundle outState) {
//...
mSavedStateRegistryController.performSave(outState);
}
mSavedStateRegistryController 最終調(diào)用到 SavedStateRegistry 的同名方法,看一下 SavedStateRegistry#performSave:
fun performSave(outBundle: Bundle) {
//...
val it: Iterator<Map.Entry<String, SavedStateProvider>> =
this.components.iteratorWithAdditions()
while (it.hasNext()) {
val (key, value) = it.next()
components.putBundle(key, value.saveState())
}
if (!components.isEmpty) {
outBundle.putBundle(SAVED_COMPONENTS_KEY, components)
}
}
components 是注冊(cè) SavedStateProvider 的 Map。 performSave 中調(diào)用 Provider 的 saveState 方法獲取到 rememberSaveable 中保存的 bundle,然后存入 outBundle 進(jìn)行持久化。
至此,rememberSaveable 在 Android 平臺(tái) 完成了橫豎屏切換時(shí)的狀態(tài)保存。
最后我們用一個(gè)圖收尾,紅色是保存數(shù)據(jù)時(shí)的數(shù)據(jù)流流向,綠色是恢復(fù)數(shù)據(jù)時(shí)的數(shù)據(jù)流流向:

以上就是Compose狀態(tài)保存rememberSaveable原理解析的詳細(xì)內(nèi)容,更多關(guān)于Compose rememberSaveable的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android實(shí)現(xiàn)判斷某個(gè)服務(wù)是否正在運(yùn)行的方法
這篇文章主要介紹了Android實(shí)現(xiàn)判斷某個(gè)服務(wù)是否正在運(yùn)行的方法,涉及Android針對(duì)系統(tǒng)服務(wù)運(yùn)行狀態(tài)的判斷技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-10-10
Android中的指紋識(shí)別demo開發(fā)實(shí)例
這篇文章主要介紹了Android中的指紋識(shí)別demo開發(fā)實(shí)例的相關(guān)知識(shí),非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-09-09
Android應(yīng)用圖標(biāo)上的小紅點(diǎn)Badge實(shí)踐代碼
本篇文章主要介紹了Android應(yīng)用圖標(biāo)上的小紅點(diǎn)Badge實(shí)踐代碼,具有一定的參考價(jià)值,有興趣的可以了解一下2017-07-07
Android 使用Path實(shí)現(xiàn)涂鴉功能
到月底了最近比較空閑,今天抽空給大家實(shí)現(xiàn)一個(gè)涂鴉效果,會(huì)分幾步實(shí)現(xiàn),這里有一個(gè)重要的知識(shí)點(diǎn)就是圖層,要理解這個(gè)。下面先從簡(jiǎn)單的說起,一起看看代碼吧2016-12-12
Android studio 自動(dòng)換行和取消自動(dòng)換行操作
這篇文章主要介紹了Android studio 自動(dòng)換行和取消自動(dòng)換行操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-04-04
AndroidGUI27中findViewById返回null的快速解決辦法
這篇文章主要介紹了AndroidGUI27中findViewById返回null的快速解決辦法的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-06-06
Android定制RadioButton樣式三種實(shí)現(xiàn)方法
三種方法實(shí)現(xiàn)Android定制RadioButton樣式:使用XML文件進(jìn)行定義/在JAVA代碼中定義等等,感興趣的朋友可以參考下,希望可以幫助到你2013-02-02
Android實(shí)現(xiàn)信號(hào)強(qiáng)度監(jiān)聽的方法
這篇文章主要介紹了Android實(shí)現(xiàn)信號(hào)強(qiáng)度監(jiān)聽的方法,是Android手機(jī)中很常見的一個(gè)實(shí)用功能,需要的朋友可以參考下2014-08-08
android Animation監(jiān)聽器AnimationListener的使用方法)
AnimaitonListener的使用方法主要是在Animation上設(shè)置一個(gè)監(jiān)聽器,下面通過一個(gè)實(shí)例說明它的使用方法2013-11-11

