Skip to content

Instantly share code, notes, and snippets.

@osipxd
Last active May 20, 2020 05:04
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save osipxd/1d5d7096f0e62793c7093297e0b98b09 to your computer and use it in GitHub Desktop.
Save osipxd/1d5d7096f0e62793c7093297e0b98b09 to your computer and use it in GitHub Desktop.
View binding extensions + delegate
/**
* Инфлейт ViewBinding заданного типа [T].
*
* В качестве родителя используется [ViewGroup], по умолчанию view прикрепляется к корню родителя.
* **ВАЖНО!** Для инфлейта вьюх с `merge` в корне нужно использовать только этот метод.
*/
inline fun <reified T : ViewBinding> ViewGroup.inflateViewBinding(
context: Context = this.context,
attachToRoot: Boolean = true
): T {
return T::class.inflate(LayoutInflater.from(context), this, attachToRoot)
}
/**
* Инфлейт ViewBinding заданного типа [T].
*
* Метод для случая если нет [ViewGroup] или готового [LayoutInflater], в этом случае можно передать
* контекст из которого будет получен [LayoutInflater].
*/
inline fun <reified T : ViewBinding> Context.inflateViewBinding(
parent: ViewGroup? = null,
attachToRoot: Boolean = parent != null
): T {
return T::class.inflate(LayoutInflater.from(this), parent, attachToRoot)
}
/**
* Инфлейт ViewBinding заданного типа [T], с использованием заданного [LayoutInflater].
* @sample com.openbank.library.dialog.CommonDialog.onCreateView
*/
inline fun <reified T : ViewBinding> LayoutInflater.inflateViewBinding(
parent: ViewGroup? = null,
attachToRoot: Boolean = parent != null
): T {
return T::class.inflate(this, parent, attachToRoot)
}
/**
* Динамический вызов метода inflate у ViewBinding.
*
* При помощи этого метода можно вызвать inflate у любого ViewBinding, что делает возможным
* упрощение этого вызова.
* @see inflateViewBinding
*/
fun <T : ViewBinding> KClass<T>.inflate(
inflater: LayoutInflater,
parent: ViewGroup?,
attachToRoot: Boolean
): T {
val inflateMethod = java.getInflateMethod()
@Suppress("UNCHECKED_CAST")
return if (inflateMethod.parameterTypes.size > 2) {
inflateMethod.invoke(null, inflater, parent, attachToRoot)
} else {
if (!attachToRoot) Log.d("ViewBinding", "attachToRoot is always true for ${java.simpleName}.inflate")
inflateMethod.invoke(null, inflater, parent)
} as T
}
private val inflateMethodsCache = mutableMapOf<Class<out ViewBinding>, Method>()
private fun Class<out ViewBinding>.getInflateMethod(): Method {
return inflateMethodsCache.getOrPut(this) {
declaredMethods.find { method ->
val parameterTypes = method.parameterTypes
method.name == "inflate" &&
parameterTypes[0] == LayoutInflater::class.java &&
parameterTypes.getOrNull(1) == ViewGroup::class.java &&
(parameterTypes.size == 2 || parameterTypes[2] == Boolean::class.javaPrimitiveType)
} ?: error("Method ${this.simpleName}.inflate(LayoutInflater, ViewGroup[, boolean]) not found.")
}
}
/**
* Получение биндинга заданного типа [T] из [View].
*/
inline fun <reified T : ViewBinding> View.getBinding(): T = T::class.bind(this)
-keepclassmembers class ** implements androidx.viewbinding.ViewBinding {
public static *** inflate(...);
public static *** bind(***);
}
/**
* Делегат, который возвращает ViewBinding. ViewBinding пересоздаётся при каждом пересоздании View фрагмента.
*
* Пример использования:
* ```
* class MyFragment : Fragment(R.layout.my_fragment) {
* private val binding: MyFragmentBinding by viewBinding()
* }
* ```
* Если попытаться получить значение до onViewCreated или после onViewDestroy, будет выброшено исключение.
*/
inline fun <reified T : ViewBinding> Fragment.viewBinding(): ViewBindingDelegate<T> {
return ViewBindingDelegate(this, T::class)
}
/** @see viewBinding */
class ViewBindingDelegate<T : ViewBinding> @PublishedApi internal constructor(
private val fragment: Fragment,
private val viewBindingClass: KClass<T>
) : ReadOnlyProperty<Any?, T> {
private var binding: T? = null
init {
fragment.viewLifecycleOwnerLiveData.observe(fragment) { lifecycleOwner ->
lifecycleOwner.lifecycle.addObserver(
LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_DESTROY) binding = null
}
)
}
}
override fun getValue(thisRef: Any?, property: KProperty<*>): T = binding ?: obtainBinding()
private fun obtainBinding(): T {
val view = checkNotNull(fragment.view) {
"ViewBinding is only valid between onCreateView and onDestroyView."
}
return viewBindingClass.bind(view)
.also { binding = it }
}
}
/**
* Динамический вызов метода bind у ViewBinding.
*
* При помощи этого метода можно вызвать bind у любого ViewBinding, что делает возможным
* упрощение этого вызова.
* @see getBinding
*/
fun <T : ViewBinding> KClass<T>.bind(rootView: View): T {
val inflateMethod = java.getBindMethod()
@Suppress("UNCHECKED_CAST")
return inflateMethod.invoke(null, rootView) as T
}
private val bindMethodsCache = mutableMapOf<Class<out ViewBinding>, Method>()
private fun Class<out ViewBinding>.getBindMethod(): Method {
return bindMethodsCache.getOrPut(this) { getDeclaredMethod("bind", View::class.java) }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment