Skip to content

Commit fa93c53

Browse files
Add TabLayout#doOnCustomTabSelected()
1 parent 8e5cf2f commit fa93c53

File tree

4 files changed

+99
-68
lines changed

4 files changed

+99
-68
lines changed

viewbinding-ktx/build.gradle

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,15 @@ android {
3434
}
3535

3636
buildFeatures {
37-
viewBinding = true
38-
dataBinding = true
37+
viewBinding true
38+
dataBinding true
3939
}
4040
}
4141

4242
dependencies {
43-
implementation 'com.google.android.material:material:1.3.0'
43+
implementation 'com.google.android.material:material:1.4.0'
44+
implementation "androidx.lifecycle:lifecycle-common-java8:2.4.0-alpha03"
4445
testImplementation 'junit:junit:4.13.2'
45-
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
46-
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
46+
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
47+
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
4748
}

viewbinding-ktx/src/main/java/com/dylanc/viewbinding/ViewBinding.kt

Lines changed: 50 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -25,27 +25,29 @@ import android.view.ViewGroup
2525
import androidx.activity.ComponentActivity
2626
import androidx.databinding.ViewDataBinding
2727
import androidx.fragment.app.Fragment
28-
import androidx.lifecycle.Lifecycle
29-
import androidx.lifecycle.LifecycleObserver
30-
import androidx.lifecycle.OnLifecycleEvent
28+
import androidx.lifecycle.*
3129
import androidx.viewbinding.ViewBinding
3230
import com.google.android.material.navigation.NavigationView
3331
import com.google.android.material.tabs.TabLayout
3432
import kotlin.properties.ReadOnlyProperty
3533
import kotlin.reflect.KProperty
3634

35+
private var View.binding: Any?
36+
get() = getTag(-1)
37+
set(value) = setTag(-1, value)
3738

3839
inline fun <reified VB : ViewBinding> ComponentActivity.binding() = lazy {
39-
inflateBinding<VB>(layoutInflater).also {
40-
setContentView(it.root)
41-
if (this is ViewDataBinding) lifecycleOwner = this@binding
40+
inflateBinding<VB>(layoutInflater).also { binding ->
41+
setContentView(binding.root)
42+
if (binding is ViewDataBinding) binding.lifecycleOwner = this
4243
}
4344
}
4445

45-
inline fun <reified VB : ViewBinding> Fragment.binding() = FragmentBindingDelegate<VB> { requireView().bind() }
46+
inline fun <reified VB : ViewBinding> Fragment.binding() =
47+
FragmentBindingDelegate<VB> { requireView().getBinding() }
4648

4749
inline fun <reified VB : ViewBinding> Fragment.binding(method: Method) =
48-
FragmentBindingDelegate<VB> { if (method == Method.BIND) requireView().bind() else inflateBinding(layoutInflater) }
50+
FragmentBindingDelegate<VB> { if (method == Method.BIND) requireView().getBinding() else inflateBinding(layoutInflater) }
4951

5052
inline fun <reified VB : ViewBinding> Dialog.binding() = lazy {
5153
inflateBinding<VB>(layoutInflater).also { setContentView(it.root) }
@@ -58,18 +60,34 @@ inline fun <reified VB : ViewBinding> ViewGroup.binding(attachToParent: Boolean
5860
inline fun <reified VB : ViewBinding> ViewGroup.inflate() =
5961
inflateBinding<VB>(LayoutInflater.from(context), this, true)
6062

61-
inline fun <reified VB : ViewBinding> TabLayout.Tab.setCustomView(onBindView: VB.() -> Unit) {
62-
customView = inflateBinding<VB>(LayoutInflater.from(parent!!.context)).apply(onBindView).root
63+
inline fun <reified VB : ViewBinding> TabLayout.Tab.setCustomView(block: VB.() -> Unit) {
64+
inflateBinding<VB>(LayoutInflater.from(parent!!.context)).apply(block).let { binding ->
65+
customView = binding.root
66+
customView?.tag = binding
67+
}
6368
}
6469

65-
inline fun <reified VB : ViewBinding> TabLayout.Tab.bindCustomView(onBindView: VB.() -> Unit) =
66-
customView?.bind<VB>()?.run(onBindView)
70+
inline fun <reified VB : ViewBinding> TabLayout.doOnCustomTabSelected(
71+
crossinline onTabUnselected: VB.(TabLayout.Tab) -> Unit = {},
72+
crossinline onTabReselected: VB.(TabLayout.Tab) -> Unit = {},
73+
crossinline onTabSelected: VB.(TabLayout.Tab) -> Unit = {},
74+
) =
75+
addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
76+
override fun onTabSelected(tab: TabLayout.Tab) {
77+
tab.customView?.getBinding<VB>()?.onTabSelected(tab)
78+
}
79+
80+
override fun onTabUnselected(tab: TabLayout.Tab) {
81+
tab.customView?.getBinding<VB>()?.onTabUnselected(tab)
82+
}
6783

68-
inline fun <reified VB : ViewBinding> TabLayout.Tab.bindCustomView(bind: (View) -> VB, onBindView: VB.() -> Unit) =
69-
customView?.let { bind(it) }?.run(onBindView)
84+
override fun onTabReselected(tab: TabLayout.Tab) {
85+
tab.customView?.getBinding<VB>()?.onTabReselected(tab)
86+
}
87+
})
7088

71-
inline fun <reified VB : ViewBinding> NavigationView.setHeaderView(index: Int = 0, onBindView: VB.() -> Unit) =
72-
getHeaderView(index)?.bind<VB>()?.run(onBindView)
89+
inline fun <reified VB : ViewBinding> NavigationView.setHeaderView(index: Int = 0, block: VB.() -> Unit) =
90+
getHeaderView(index)?.getBinding<VB>()?.run(block)
7391

7492
inline fun <reified VB : ViewBinding> inflateBinding(layoutInflater: LayoutInflater) =
7593
VB::class.java.getMethod("inflate", LayoutInflater::class.java).invoke(null, layoutInflater) as VB
@@ -83,16 +101,15 @@ inline fun <reified VB : ViewBinding> inflateBinding(
83101
VB::class.java.getMethod("inflate", LayoutInflater::class.java, ViewGroup::class.java, Boolean::class.java)
84102
.invoke(null, layoutInflater, parent, attachToParent) as VB
85103

86-
inline fun <reified VB : ViewBinding> View.bind() =
87-
VB::class.java.getMethod("bind", View::class.java).invoke(null, this) as VB
104+
inline fun <reified VB : ViewBinding> View.getBinding() = getBinding(VB::class.java)
88105

89-
inline fun Fragment.doOnDestroyView(crossinline block: () -> Unit) =
90-
viewLifecycleOwner.lifecycle.addObserver(object : LifecycleObserver {
91-
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
92-
fun onDestroyView() {
93-
block.invoke()
94-
}
95-
})
106+
@Suppress("UNCHECKED_CAST")
107+
fun <VB : ViewBinding> View.getBinding(clazz: Class<VB>) =
108+
if (binding != null) {
109+
binding as VB
110+
} else {
111+
(clazz.getMethod("bind", View::class.java).invoke(null, this) as VB).also { binding = it }
112+
}
96113

97114
enum class Method { BIND, INFLATE }
98115

@@ -106,16 +123,18 @@ class FragmentBindingDelegate<VB : ViewBinding>(private val block: () -> VB) : R
106123
override fun getValue(thisRef: Fragment, property: KProperty<*>): VB {
107124
if (binding == null) {
108125
binding = try {
109-
block().also {
110-
if (it is ViewDataBinding) it.lifecycleOwner = thisRef.viewLifecycleOwner
126+
block().also { binding ->
127+
if (binding is ViewDataBinding) binding.lifecycleOwner = thisRef.viewLifecycleOwner
111128
}
112129
} catch (e: IllegalStateException) {
113130
throw IllegalStateException("The binding property has been destroyed.")
114131
}
115-
thisRef.doOnDestroyView {
116-
if (thisRef is BindingLifecycleOwner) thisRef.onDestroyViewBinding(binding!!)
117-
binding = null
118-
}
132+
thisRef.viewLifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver {
133+
override fun onDestroy(owner: LifecycleOwner) {
134+
if (thisRef is BindingLifecycleOwner) thisRef.onDestroyViewBinding(binding!!)
135+
binding = null
136+
}
137+
})
119138
}
120139
return binding!!
121140
}

viewbinding-nonreflection-ktx/build.gradle

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,15 @@ android {
3434
}
3535

3636
buildFeatures {
37-
viewBinding = true
38-
dataBinding = true
37+
viewBinding true
38+
dataBinding true
3939
}
4040
}
4141

4242
dependencies {
43-
implementation 'com.google.android.material:material:1.3.0'
43+
implementation 'com.google.android.material:material:1.4.0'
44+
implementation "androidx.lifecycle:lifecycle-common-java8:2.4.0-alpha03"
4445
testImplementation 'junit:junit:4.13.2'
45-
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
46-
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
46+
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
47+
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
4748
}

viewbinding-nonreflection-ktx/src/main/java/com/dylanc/viewbinding/nonreflection/ViewBinding.kt

Lines changed: 37 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,7 @@ import android.view.ViewGroup
2525
import androidx.activity.ComponentActivity
2626
import androidx.databinding.ViewDataBinding
2727
import androidx.fragment.app.Fragment
28-
import androidx.lifecycle.Lifecycle
29-
import androidx.lifecycle.LifecycleObserver
30-
import androidx.lifecycle.OnLifecycleEvent
28+
import androidx.lifecycle.*
3129
import androidx.viewbinding.ViewBinding
3230
import com.google.android.material.navigation.NavigationView
3331
import com.google.android.material.tabs.TabLayout
@@ -36,9 +34,9 @@ import kotlin.reflect.KProperty
3634

3735

3836
fun <VB : ViewBinding> ComponentActivity.binding(inflate: (LayoutInflater) -> VB) = lazy {
39-
inflate(layoutInflater).also {
40-
setContentView(it.root)
41-
if (this is ViewDataBinding) lifecycleOwner = this@binding
37+
inflate(layoutInflater).also { binding ->
38+
setContentView(binding.root)
39+
if (binding is ViewDataBinding) binding.lifecycleOwner = this
4240
}
4341
}
4442

@@ -55,30 +53,40 @@ fun <VB : ViewBinding> ViewGroup.binding(
5553
inflate(LayoutInflater.from(context), if (attachToParent) this else null, attachToParent)
5654
}
5755

58-
inline fun <reified VB : ViewBinding> ViewGroup.inflate(inflate: (LayoutInflater, ViewGroup?, Boolean) -> VB) =
56+
fun <VB : ViewBinding> ViewGroup.inflate(inflate: (LayoutInflater, ViewGroup?, Boolean) -> VB) =
5957
inflate(LayoutInflater.from(context), this, true)
6058

61-
fun <VB : ViewBinding> TabLayout.Tab.setCustomView(
62-
inflate: (LayoutInflater) -> VB,
63-
onBindView: VB.() -> Unit
64-
) {
65-
customView = inflate(LayoutInflater.from(parent!!.context)).apply(onBindView).root
59+
fun <VB : ViewBinding> TabLayout.Tab.setCustomView(inflate: (LayoutInflater) -> VB, block: VB.() -> Unit) {
60+
customView = inflate(LayoutInflater.from(parent!!.context)).apply(block).root
6661
}
6762

68-
inline fun <reified VB : ViewBinding> TabLayout.Tab.bindCustomView(bind: (View) -> VB, onBindView: VB.() -> Unit) =
69-
customView?.let { bind(it) }?.run(onBindView)
63+
fun <VB : ViewBinding> TabLayout.doOnCustomTabSelected(
64+
bind: (View) -> VB,
65+
onTabUnselected: (VB.(TabLayout.Tab) -> Unit)? = null,
66+
onTabReselected: (VB.(TabLayout.Tab) -> Unit)? = null,
67+
onTabSelected: (VB.(TabLayout.Tab) -> Unit)? = null,
68+
) =
69+
addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
70+
override fun onTabSelected(tab: TabLayout.Tab) {
71+
tab.getBinding(bind)?.let { onTabSelected?.invoke(it, tab) }
72+
}
7073

71-
inline fun <reified VB : ViewBinding> NavigationView.setHeaderView(index: Int = 0, bind: (View) -> VB, onBindView: VB.() -> Unit) =
72-
getHeaderView(index)?.let { bind(it) }?.run(onBindView)
74+
override fun onTabUnselected(tab: TabLayout.Tab) {
75+
tab.getBinding(bind)?.let { onTabUnselected?.invoke(it, tab) }
76+
}
7377

74-
inline fun Fragment.doOnDestroyView(crossinline block: () -> Unit) =
75-
viewLifecycleOwner.lifecycle.addObserver(object : LifecycleObserver {
76-
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
77-
fun onDestroyView() {
78-
block.invoke()
78+
override fun onTabReselected(tab: TabLayout.Tab) {
79+
tab.getBinding(bind)?.let { onTabReselected?.invoke(it, tab) }
7980
}
8081
})
8182

83+
@Suppress("UNCHECKED_CAST")
84+
fun <VB : ViewBinding> TabLayout.Tab.getBinding(bind: (View) -> VB): VB? =
85+
(tag as? VB) ?: customView?.let { bind(it) }?.also { tag = it }
86+
87+
fun <VB : ViewBinding> NavigationView.setHeaderView(index: Int = 0, bind: (View) -> VB, block: VB.() -> Unit) =
88+
getHeaderView(index)?.let { bind(it) }?.run(block)
89+
8290
interface BindingLifecycleOwner {
8391
fun onDestroyViewBinding(destroyingBinding: ViewBinding)
8492
}
@@ -89,16 +97,18 @@ class FragmentBindingDelegate<VB : ViewBinding>(private val bind: (View) -> VB)
8997
override fun getValue(thisRef: Fragment, property: KProperty<*>): VB {
9098
if (binding == null) {
9199
binding = try {
92-
bind(thisRef.requireView()).also {
93-
if (it is ViewDataBinding) it.lifecycleOwner = thisRef.viewLifecycleOwner
100+
bind(thisRef.requireView()).also { binding ->
101+
if (binding is ViewDataBinding) binding.lifecycleOwner = thisRef.viewLifecycleOwner
94102
}
95103
} catch (e: IllegalStateException) {
96104
throw IllegalStateException("The binding property has been destroyed.")
97105
}
98-
thisRef.doOnDestroyView {
99-
if (thisRef is BindingLifecycleOwner) thisRef.onDestroyViewBinding(binding!!)
100-
binding = null
101-
}
106+
thisRef.viewLifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver {
107+
override fun onDestroy(owner: LifecycleOwner) {
108+
if (thisRef is BindingLifecycleOwner) thisRef.onDestroyViewBinding(binding!!)
109+
binding = null
110+
}
111+
})
102112
}
103113
return binding!!
104114
}

0 commit comments

Comments
 (0)