PoolingContainer.kt
@file:JvmName("PoolingContainer")
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.customview.poolingcontainer
import android.annotation.SuppressLint
import android.view.View
import android.view.ViewGroup
import androidx.annotation.UiThread
import androidx.core.view.allViews
import androidx.core.view.ancestors
import androidx.core.view.children
/**
* A callback to inform a child View within a container that manages its children's lifecycle
* outside of the View hierarchy (such as a RecyclerView) when that child should dispose any
* resources it holds.
*
* This callback is not necessarily triggered if the pooling container is disassociating the View
* from a particular piece of data (that is, it is *not* an "unbind listener"). It is intended for
* expensive resources that need to be cached across data items, but need a signal to be
* disposed of.
*/
fun interface PoolingContainerListener {
/**
* Signals that this view should dispose any resources it may be holding onto, because its
* container is either discarding the View or has been removed from the hierarchy itself.
*
* Note: This may be called multiple times. A call to this method does *not* mean the View
* will not later be reattached.
*/
@UiThread
fun onRelease()
}
/**
* Add a callback for when this View should dispose its resources.
*
* @receiver the child view to receive callbacks regarding
*/
@SuppressLint("ExecutorRegistration") // This is a UI thread callback
fun View.addPoolingContainerListener(listener: PoolingContainerListener) {
this.poolingContainerListenerHolder.addListener(listener)
}
/**
* Remove a callback that was previously added by [addPoolingContainerListener]
*/
@SuppressLint("ExecutorRegistration") // This is a UI thread callback
fun View.removePoolingContainerListener(listener: PoolingContainerListener) {
this.poolingContainerListenerHolder.removeListener(listener)
}
/**
* Whether this View is a container that manages the lifecycle of its child Views.
*
* Any View that sets this to `true` must call [callPoolingContainerOnRelease] on child Views
* before they are discarded and may possibly not be used in the future. This includes when the
* view itself is detached from the window, unless it is being held for possible later
* reattachment and its children should not release their resources.
*
* **Warning: Failure to call [callPoolingContainerOnRelease] when a View is removed from the
* hierarchy and discarded is likely to result in memory leaks!**
*/
var View.isPoolingContainer: Boolean
get() = getTag(IsPoolingContainerTag) as? Boolean ?: false
set(value) {
setTag(IsPoolingContainerTag, value)
}
/**
* Whether one of this View's ancestors has `isPoolingContainer` set to `true`
*/
val View.isWithinPoolingContainer: Boolean
get() {
ancestors.forEach {
if (it is View && it.isPoolingContainer) {
return true
}
}
return false
}
/**
* Calls [PoolingContainerListener.onRelease] on any [PoolingContainerListener]s attached to
* this View or any of its children.
*
* At the point when this is called, the View should be detached from the window.
*/
fun View.callPoolingContainerOnRelease() {
this.allViews.forEach { child ->
child.poolingContainerListenerHolder.onRelease()
}
}
/**
* Calls [PoolingContainerListener.onRelease] on any [PoolingContainerListener]s attached to
* any of its children (not including the `ViewGroup` itself)
*
* At the point when this is called, the View should be detached from the window.
*/
fun ViewGroup.callPoolingContainerOnReleaseForChildren() {
this.children.forEach { child ->
child.poolingContainerListenerHolder.onRelease()
}
}
private val PoolingContainerListenerHolderTag = R.id.pooling_container_listener_holder_tag
private val IsPoolingContainerTag = R.id.is_pooling_container_tag
private class PoolingContainerListenerHolder {
private val listeners = ArrayList<PoolingContainerListener>()
fun addListener(listener: PoolingContainerListener) {
listeners.add(listener)
}
fun removeListener(listener: PoolingContainerListener) {
listeners.remove(listener)
}
fun onRelease() {
for (i in listeners.lastIndex downTo 0) {
listeners[i].onRelease()
}
}
}
private val View.poolingContainerListenerHolder: PoolingContainerListenerHolder
get() {
var lifecycle =
getTag(PoolingContainerListenerHolderTag) as PoolingContainerListenerHolder?
if (lifecycle == null) {
lifecycle = PoolingContainerListenerHolder()
setTag(PoolingContainerListenerHolderTag, lifecycle)
}
return lifecycle
}