Skip to content

Commit 3859ae9

Browse files
add transition from RecyclerView to image for activity transition
1 parent 9cfc465 commit 3859ae9

File tree

24 files changed

+618
-19
lines changed

24 files changed

+618
-19
lines changed

Tutorial1-1Basics/build.gradle

+3-1
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,10 @@ dependencies {
4646

4747
implementation 'androidx.appcompat:appcompat:1.2.0'
4848
implementation 'com.google.android.material:material:1.2.1'
49-
implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
49+
implementation 'androidx.constraintlayout:constraintlayout:2.0.2'
5050
implementation 'androidx.viewpager2:viewpager2:1.0.0'
51+
52+
5153
implementation 'io.reactivex.rxjava3:rxjava:3.0.6'
5254

5355
// LifeCycle

Tutorial2-1AnimatedVectorDrawables/build.gradle

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ dependencies {
4646

4747
implementation 'androidx.appcompat:appcompat:1.2.0'
4848
implementation 'com.google.android.material:material:1.2.1'
49-
implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
49+
implementation 'androidx.constraintlayout:constraintlayout:2.0.2'
5050
implementation 'androidx.viewpager2:viewpager2:1.0.0'
5151
implementation 'androidx.recyclerview:recyclerview:1.2.0-alpha06'
5252

Tutorial2-1AnimatedVectorDrawables/src/main/java/com/smarttoolfactory/tutorial2_1animatedvectordrawables/adapter/viewholder/HeaderViewBinder.kt

+1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ class HeaderViewHolder(private val binding: ItemHeaderBinding) :
3535
RecyclerView.ViewHolder(binding.root) {
3636

3737
fun bind(model: HeaderModel) {
38+
3839
binding.tvHeader.text = model.header
3940
}
4041

Tutorial3-1Transitions/build.gradle

+7-2
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,10 @@ dependencies {
4646

4747
implementation 'androidx.appcompat:appcompat:1.2.0'
4848
implementation 'com.google.android.material:material:1.2.1'
49-
implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
49+
implementation 'androidx.constraintlayout:constraintlayout:2.0.2'
5050
implementation 'androidx.viewpager2:viewpager2:1.0.0'
51-
implementation 'io.reactivex.rxjava3:rxjava:3.0.6'
51+
implementation 'androidx.recyclerview:recyclerview:1.2.0-alpha06'
52+
implementation 'io.reactivex.rxjava3:rxjava:3.0.6'
5253

5354
// LifeCycle
5455
implementation "androidx.lifecycle:lifecycle-common-java8:2.2.0"
@@ -63,6 +64,10 @@ dependencies {
6364
// Leak Canary
6465
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.5'
6566

67+
// glide
68+
implementation("com.github.bumptech.glide:glide:4.11.0")
69+
kapt("com.github.bumptech.glide:compiler:4.11.0")
70+
6671
testImplementation 'junit:junit:4.+'
6772
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
6873
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'

Tutorial3-1Transitions/src/main/AndroidManifest.xml

+4-2
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@
1616
<category android:name="android.intent.category.LAUNCHER" />
1717
</intent-filter>
1818
</activity>
19-
<activity android:name=".chapter1_basics.Activity1_1Basics" />
20-
<activity android:name=".chapter1_basics.Activity1_1DetailPage" />
19+
<activity android:name=".chapter1_activity_transitions.Activity1_1Basics" />
20+
<activity android:name=".chapter1_activity_transitions.Activity1_1DetailPage" />
21+
<activity android:name=".chapter1_activity_transitions.Activity1_2RecyclerViewTransition" />
22+
<activity android:name=".chapter1_activity_transitions.Activity1_2DetailPage" />
2123
</application>
2224

2325
</manifest>

Tutorial3-1Transitions/src/main/java/com/smarttoolfactory/tutorial3_1transitions/MainActivity.kt

+9-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ import androidx.recyclerview.widget.LinearLayoutManager
1010
import com.smarttoolfactory.tutorial3_1transitions.adapter.BaseAdapter
1111
import com.smarttoolfactory.tutorial3_1transitions.adapter.ChapterSelectionAdapter
1212
import com.smarttoolfactory.tutorial3_1transitions.adapter.model.ActivityClassModel
13-
import com.smarttoolfactory.tutorial3_1transitions.chapter1_basics.Activity1_1Basics
13+
import com.smarttoolfactory.tutorial3_1transitions.chapter1_activity_transitions.Activity1_1Basics
14+
import com.smarttoolfactory.tutorial3_1transitions.chapter1_activity_transitions.Activity1_2RecyclerViewTransition
1415
import com.smarttoolfactory.tutorial3_1transitions.databinding.ActivityMainBinding
1516
import java.util.ArrayList
1617

@@ -57,13 +58,19 @@ class MainActivity : AppCompatActivity(), BaseAdapter.OnRecyclerViewItemClickLis
5758

5859
// Add Activities to list to be displayed on RecyclerView
5960
activityClassModels.add(
61+
6062
ActivityClassModel(
6163
Activity1_1Basics::class.java,
6264
getString(R.string.activity1_1)
6365
)
6466
)
6567

66-
68+
activityClassModels.add(
69+
ActivityClassModel(
70+
Activity1_2RecyclerViewTransition::class.java,
71+
getString(R.string.activity1_2)
72+
)
73+
)
6774
}
6875

6976
@Override

Tutorial3-1Transitions/src/main/java/com/smarttoolfactory/tutorial3_1transitions/adapter/BaseAdapter.kt

+2
Original file line numberDiff line numberDiff line change
@@ -105,4 +105,6 @@ abstract class BaseAdapter : RecyclerView.Adapter<BaseAdapter.MyViewHolder>() {
105105
fun setOnItemClickListener(listener: OnRecyclerViewItemClickListener) {
106106
this.listener = listener
107107
}
108+
109+
108110
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package com.smarttoolfactory.tutorial3_1transitions.adapter
2+
3+
import androidx.recyclerview.widget.DiffUtil
4+
import com.smarttoolfactory.tutorial3_1transitions.adapter.viewholder.ItemBinder
5+
import com.smarttoolfactory.tutorial3_1transitions.adapter.viewholder.ItemClazz
6+
import com.smarttoolfactory.tutorial3_1transitions.adapter.viewholder.MappableItemBinder
7+
8+
class ItemDiffCallback(
9+
private val viewBinders: Map<ItemClazz, MappableItemBinder>
10+
) : DiffUtil.ItemCallback<Any>() {
11+
12+
override fun areItemsTheSame(oldItem: Any, newItem: Any): Boolean {
13+
if (oldItem::class != newItem::class) {
14+
return false
15+
}
16+
return viewBinders[oldItem::class.java]?.areItemsTheSame(oldItem, newItem) ?: false
17+
}
18+
19+
override fun areContentsTheSame(oldItem: Any, newItem: Any): Boolean {
20+
// We know the items are the same class because [areItemsTheSame] returned true
21+
return viewBinders[oldItem::class.java]?.areContentsTheSame(oldItem, newItem) ?: false
22+
}
23+
}
24+
25+
internal class SingleTypeDiffCallback(
26+
private val viewBinder: ItemBinder
27+
) : DiffUtil.ItemCallback<Any>() {
28+
29+
override fun areItemsTheSame(oldItem: Any, newItem: Any): Boolean {
30+
if (oldItem::class != newItem::class) {
31+
return false
32+
}
33+
return viewBinder?.areItemsTheSame(oldItem, newItem) ?: false
34+
}
35+
36+
override fun areContentsTheSame(oldItem: Any, newItem: Any): Boolean {
37+
// We know the items are the same class because [areItemsTheSame] returned true
38+
return viewBinder?.areContentsTheSame(oldItem, newItem) ?: false
39+
}
40+
}
41+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package com.smarttoolfactory.tutorial3_1transitions.adapter
2+
3+
import android.view.ViewGroup
4+
import androidx.recyclerview.widget.ListAdapter
5+
import androidx.recyclerview.widget.RecyclerView.ViewHolder
6+
import com.smarttoolfactory.tutorial3_1transitions.adapter.viewholder.ItemClazz
7+
import com.smarttoolfactory.tutorial3_1transitions.adapter.viewholder.MappableItemBinder
8+
9+
/**
10+
* RecyclerView adapter for setting list with different layouts using [MappableItemViewBinder].
11+
*
12+
* Takes [Map] of model class [ItemClazz] and [ViewHolder] and transforms that
13+
* map to [viewTypeToBinders] with keys that point to layouts
14+
* returned from [MappableItemViewBinder.getItemLayoutResource].
15+
*
16+
* Mapping happens between Model::class.java <-> ViewBinder
17+
* and between R.layout.RES <-> ViewBinder to glue from model class to layout getItemType
18+
* checks class of data which is send to this adapter with [ListAdapter.submitList].
19+
*
20+
* * For instance, if array of items has three types such as
21+
* Model1, Model2, and Model3, then there should be
22+
* ViewBinder1, ViewBinder2, and ViewBinder each with it's own unique layout resource.
23+
*
24+
* * Whenever a type of model in array is returned based on position,
25+
* ViewBinder that is mapped with that model class is returned from map.
26+
*
27+
*/
28+
class MultipleViewBinderListAdapter(
29+
private val viewBinders: Map<ItemClazz, MappableItemBinder>,
30+
stateRestorationPolicy: StateRestorationPolicy = StateRestorationPolicy.PREVENT_WHEN_EMPTY
31+
) : ListAdapter<Any, ViewHolder>(ItemDiffCallback(viewBinders)) {
32+
33+
init {
34+
this.stateRestorationPolicy = stateRestorationPolicy
35+
}
36+
37+
/**
38+
* Map of layout resource keys to ViewBinder values
39+
*/
40+
private val viewTypeToBinders = viewBinders.mapKeys { it.value.getItemLayoutResource() }
41+
42+
private fun getViewBinder(viewType: Int): MappableItemBinder =
43+
viewTypeToBinders.getValue(viewType)
44+
45+
/**
46+
* Map from data clazz to layout type by returning RecyclerView's
47+
* item view type as layout resource id. [getItem] returns the [Class] that ViewBinder
48+
* constructor parameter and data type shares.
49+
*/
50+
override fun getItemViewType(position: Int): Int =
51+
viewBinders.getValue(super.getItem(position).javaClass).getItemLayoutResource()
52+
53+
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
54+
return getViewBinder(viewType).createViewHolder(parent)
55+
}
56+
57+
/**
58+
* Get ViewBinder in specific position by calling [getItemViewType] with position,
59+
* and bind data that is already is coupled with data type
60+
*/
61+
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
62+
return getViewBinder(getItemViewType(position)).bindViewHolder(getItem(position), holder)
63+
}
64+
65+
override fun onViewRecycled(holder: ViewHolder) {
66+
getViewBinder(holder.itemViewType).onViewRecycled(holder)
67+
super.onViewRecycled(holder)
68+
}
69+
70+
override fun onViewDetachedFromWindow(holder: ViewHolder) {
71+
getViewBinder(holder.itemViewType).onViewDetachedFromWindow(holder)
72+
super.onViewDetachedFromWindow(holder)
73+
}
74+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.smarttoolfactory.tutorial3_1transitions.adapter.animator
2+
3+
import androidx.recyclerview.widget.DefaultItemAnimator
4+
import androidx.recyclerview.widget.RecyclerView
5+
6+
class PostItemAnimator : DefaultItemAnimator() {
7+
8+
override fun animateAdd(holder: RecyclerView.ViewHolder?): Boolean {
9+
return super.animateAdd(holder)
10+
}
11+
12+
override fun animateRemove(holder: RecyclerView.ViewHolder?): Boolean {
13+
return super.animateRemove(holder)
14+
}
15+
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.smarttoolfactory.tutorial3_1transitions.adapter.model
2+
3+
import android.os.Parcelable
4+
import androidx.annotation.DrawableRes
5+
import kotlinx.android.parcel.Parcelize
6+
7+
@Parcelize
8+
data class PostCardModel(val post: Post, @DrawableRes val drawableRes: Int) : Parcelable
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package com.smarttoolfactory.tutorial3_1transitions.adapter.viewholder
2+
3+
import android.view.View
4+
import android.view.ViewGroup
5+
import android.widget.ImageView
6+
import androidx.recyclerview.widget.RecyclerView
7+
import com.bumptech.glide.Glide
8+
import com.bumptech.glide.request.RequestOptions
9+
import com.smarttoolfactory.tutorial3_1transitions.R
10+
import com.smarttoolfactory.tutorial3_1transitions.adapter.model.Post
11+
import com.smarttoolfactory.tutorial3_1transitions.adapter.model.PostCardModel
12+
import com.smarttoolfactory.tutorial3_1transitions.databinding.ItemPostBinding
13+
14+
class PostCardViewBinder(
15+
private val onItemClick: ((ItemPostBinding, PostCardModel) -> Unit)? = null
16+
) :
17+
MappableItemViewBinder<PostCardModel, PostCardViewHolder>(PostCardModel::class.java) {
18+
19+
override fun createViewHolder(parent: ViewGroup): RecyclerView.ViewHolder {
20+
return PostCardViewHolder(
21+
parent.inflate(getItemLayoutResource()),
22+
onItemClick
23+
)
24+
}
25+
26+
override fun bindViewHolder(model: PostCardModel, viewHolder: PostCardViewHolder) {
27+
viewHolder.bind(model)
28+
}
29+
30+
override fun getItemLayoutResource(): Int {
31+
return R.layout.item_post
32+
}
33+
34+
override fun areItemsTheSame(oldItem: PostCardModel, newItem: PostCardModel): Boolean {
35+
return oldItem.post == newItem.post
36+
}
37+
38+
override fun areContentsTheSame(oldItem: PostCardModel, newItem: PostCardModel): Boolean {
39+
return oldItem == newItem
40+
}
41+
42+
}
43+
44+
class PostCardViewHolder(
45+
private val binding: ItemPostBinding,
46+
private val onItemClick: ((ItemPostBinding, PostCardModel) -> Unit)? = null
47+
) :
48+
RecyclerView.ViewHolder(binding.root) {
49+
50+
fun bind(model: PostCardModel) {
51+
52+
val post = model.post
53+
binding.tvTitle.text = post.title
54+
binding.tvBody.text = post.body
55+
56+
setImageUrl(binding.ivAvatar, model.drawableRes)
57+
58+
binding.constraintLayout.setOnClickListener {
59+
onItemClick?.invoke(binding, model)
60+
}
61+
}
62+
63+
private fun setImageUrl(view: ImageView, drawableRes: Int) {
64+
65+
try {
66+
67+
val requestOptions = RequestOptions()
68+
requestOptions.placeholder(R.drawable.ic_launcher_background)
69+
70+
Glide
71+
.with(view.context)
72+
.setDefaultRequestOptions(requestOptions)
73+
.load(drawableRes)
74+
.into(view)
75+
76+
} catch (e: Exception) {
77+
e.printStackTrace()
78+
}
79+
}
80+
81+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package com.smarttoolfactory.tutorial3_1transitions.adapter.viewholder
2+
3+
import android.view.LayoutInflater
4+
import android.view.ViewGroup
5+
import androidx.annotation.LayoutRes
6+
import androidx.databinding.DataBindingUtil
7+
import androidx.databinding.ViewDataBinding
8+
import androidx.recyclerview.widget.DiffUtil
9+
import androidx.recyclerview.widget.RecyclerView
10+
11+
typealias ItemClazz = Class<out Any>
12+
typealias MappableItemBinder = MappableItemViewBinder<Any, RecyclerView.ViewHolder>
13+
typealias ItemBinder = BaseItemViewBinder<Any, RecyclerView.ViewHolder>
14+
15+
/**
16+
* [MappableItemViewBinder] has 3 way relationship which can map model [Class]
17+
* either to ViewHolder type and layout type to ViewHolder type which makes
18+
* this ViewBinder unique for a layout and model type.
19+
*
20+
* * Whenever data is submitted with different types
21+
*/
22+
abstract class MappableItemViewBinder<M, in VH : RecyclerView.ViewHolder>(
23+
val modelClazz: Class<out M>
24+
) : BaseItemViewBinder<M, VH>()
25+
26+
/**
27+
* ViewBinder that maps Layout to [RecyclerView.ViewHolder] which let's this ViewHolder has
28+
* model and ViewHolder couple which can only be used adapters with single layout type
29+
*/
30+
abstract class BaseItemViewBinder<M, in VH : RecyclerView.ViewHolder> : DiffUtil.ItemCallback<M>() {
31+
32+
abstract fun createViewHolder(parent: ViewGroup): RecyclerView.ViewHolder
33+
abstract fun bindViewHolder(model: M, viewHolder: VH)
34+
abstract fun getItemLayoutResource(): Int
35+
36+
// Having these as non abstract because not all the viewBinders are required to implement them.
37+
open fun onViewRecycled(viewHolder: VH) = Unit
38+
open fun onViewDetachedFromWindow(viewHolder: VH) = Unit
39+
}
40+
41+
inline fun <reified T : ViewDataBinding> ViewGroup.inflate(
42+
@LayoutRes layout: Int,
43+
attachToRoot: Boolean = false
44+
): T = DataBindingUtil.inflate<T>(
45+
LayoutInflater.from(context),
46+
layout,
47+
this,
48+
attachToRoot
49+
)

0 commit comments

Comments
 (0)