Reputation: 1153
I have a parent layout it has some TextViews and a RecyclerView in it. And the child layout, it has a RecyclerView in it and the adapter has 5 different View Types
For the child recyclerview, i use an Adapter which has 5 different layouts in it that consists of
The problem occurs when the child RecyclerView has more than x items in it. When I scroll down to the bottom of the screen, and then scroll up to the top of the screen and scroll down again to the bottom of the screen, somehow the child RecyclerView shows wrong layout / View Types.
parent_layout.xml
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="android.view.View" />
<variable
name="viewModel"
type="home.HomeTabViewModel" />
</data>
<android.support.constraint.ConstraintLayout
android:id="@+id/cl_container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include
android:id="@+id/v_toolbar"
layout="@layout/toolbar_home_v3" />
<android.support.v7.widget.RecyclerView
android:id="@+id/rv_home_content"
android:layout_width="match_parent"
android:layout_height="0dp"
android:clipToPadding="false"
android:overScrollMode="never"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@+id/v_toolbar" />
</android.support.constraint.ConstraintLayout>
</layout>
child_layout.xml
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/dimens_32dp">
<TextView
android:id="@+id/tv_title"
style="@style/TextSoftBlackBold.20sp"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/dimens_16dp"
android:layout_marginRight="@dimen/dimens_16dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
tools:text="Popular Ideas" />
<TextView
android:id="@+id/tv_subtitle"
style="@style/TextDarkGrayNormal.14sp"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/dimens_16dp"
android:layout_marginTop="@dimen/dimens_7dp"
android:layout_marginRight="@dimen/dimens_16dp"
android:ellipsize="end"
android:maxLines="2"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_title"
tools:text="Popular events happening during your stay in Bali" />
<android.support.v7.widget.RecyclerView
android:id="@+id/rv_menu_grid"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/dimens_2dp"
app:layout_constraintTop_toBottomOf="@+id/tv_subtitle" />
</android.support.constraint.ConstraintLayout>
</layout>
BaseMenuAdapter.kt
class BaseMenuAdapter(val context: Context?,
private var contents: List<HomeViewParam.Content>?,
private val menuGrid: HomeItem.MenuGrid?) :
RecyclerView.Adapter<BaseMenuAdapter.MyViewHolder>() {
companion object {
const val VIEW_TYPE_LANDSCAPE = 0
const val VIEW_TYPE_LANDSCAPE_CARD = 1
const val VIEW_TYPE_PORTRAIT = 2
const val VIEW_TYPE_TWO_COLUMN = 3
const val VIEW_TYPE_SQUARE = 4
}
inner class MyViewHolder : RecyclerView.ViewHolder {
var landscapeBinding: ViewTemplateLandscapeBinding? = null
var landscapeCardBinding: ViewTemplateLandscapeCardBinding? = null
var portraitBinding: ItemHomeMenuPortraitBinding? = null
var twoColumnBinding: ItemHomeMenuTwoColumnBinding? = null
var squareBinding: ItemHomeMenuSquareBinding? = null
constructor(binding: ViewTemplateLandscapeBinding) : super(binding.root) {
landscapeBinding = binding
}
constructor(binding: ViewTemplateLandscapeCardBinding) : super(binding.root) {
landscapeCardBinding = binding
}
constructor(binding: ItemHomeMenuPortraitBinding) : super(binding.root) {
portraitBinding = binding
}
constructor(binding: ItemHomeMenuTwoColumnBinding) : super(binding.root) {
twoColumnBinding = binding
}
constructor(binding: ItemHomeMenuSquareBinding) : super(binding.root) {
squareBinding = binding
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseMenuAdapter.MyViewHolder {
val inflater = LayoutInflater.from(parent.context)
val binding: ViewDataBinding
return when (viewType) {
0 -> {
binding = DataBindingUtil.inflate(inflater, R.layout.view_template_landscape, parent, false)
MyViewHolder(binding as ViewTemplateLandscapeBinding)
}
1 -> {
binding = DataBindingUtil.inflate(inflater, R.layout.view_template_landscape_card, parent, false)
MyViewHolder(binding as ViewTemplateLandscapeCardBinding)
}
2 -> {
binding = DataBindingUtil.inflate(inflater, R.layout.item_home_menu_portrait, parent, false)
MyViewHolder(binding as ItemHomeMenuPortraitBinding)
}
3 -> {
binding = DataBindingUtil.inflate(inflater, R.layout.item_home_menu_two_column, parent, false)
MyViewHolder(binding as ItemHomeMenuTwoColumnBinding)
}
4 -> {
binding = DataBindingUtil.inflate(inflater, R.layout.item_home_menu_square, parent, false)
MyViewHolder(binding as ItemHomeMenuSquareBinding)
}
else -> {
throw RuntimeException("The type has to be ONE or TWO")
}
}
}
override fun onBindViewHolder(holder: BaseMenuAdapter.MyViewHolder, position: Int) {
context?.resources.run {
when (holder.itemViewType) {
VIEW_TYPE_LANDSCAPE -> {
val binding = holder.landscapeBinding
binding?.run {
val item = contents?.let { it[holder.adapterPosition] }
binding.content = item
root.setOnClickListener { AllWebViewActivityV2.startThisActivity(context, item?.title ?: "",
item?.url ?: "") }
executePendingBindings()
}
}
VIEW_TYPE_LANDSCAPE_CARD -> {
val binding = holder.landscapeCardBinding
binding?.run {
val item = contents?.let { it[holder.adapterPosition] }
binding.content = item
root.setOnClickListener { AllWebViewActivityV2.startThisActivity(context, item?.title ?: "",
item?.url ?: "") }
executePendingBindings()
}
}
VIEW_TYPE_TWO_COLUMN -> {
val binding = holder.twoColumnBinding
binding?.run {
val item = contents?.let { it[holder.adapterPosition] }
binding.content = item
root.setOnClickListener { AllWebViewActivityV2.startThisActivity(context, item?.title ?: "",
item?.url ?: "") }
executePendingBindings()
}
}
VIEW_TYPE_PORTRAIT -> {
val binding = holder.portraitBinding
binding?.run {
val item = contents?.let { it[holder.adapterPosition] }
binding.content = item
root.setOnClickListener { AllWebViewActivityV2.startThisActivity(context, item?.title ?: "",
item?.url ?: "") }
executePendingBindings()
}
}
VIEW_TYPE_SQUARE -> {
val binding = holder.squareBinding
binding?.run {
val item = contents?.let { it[holder.adapterPosition] }
binding.content = item
root.setOnClickListener { AllWebViewActivityV2.startThisActivity(context, item?.title ?: "",
item?.url ?: "") }
executePendingBindings()
}
}
else -> {}
}
}
}
override fun getItemViewType(position: Int): Int {
contents?.let {
return when (menuGrid?.templateType) {
TemplateType.LANDSCAPE -> VIEW_TYPE_LANDSCAPE
TemplateType.LANDSCAPE_CARD -> VIEW_TYPE_LANDSCAPE_CARD
TemplateType.TWO_COLUMN -> VIEW_TYPE_TWO_COLUMN
TemplateType.POTRAIT -> VIEW_TYPE_PORTRAIT
TemplateType.SQUARE -> VIEW_TYPE_SQUARE
else -> -1
}
}
return -1
}
override fun getItemCount(): Int {
return contents?.size ?: 0
}
override fun setHasStableIds(hasStableIds: Boolean) {
super.setHasStableIds(false)
}
override fun getItemId(position: Int): Long {
return position.toLong()
}
fun setItems(contents: List<HomeViewParam.Content>?) {
this.contents = contents
}
}
EDIT Here's my view looks like
So as I've mentioned above, when I scroll up or down, sometimes some items use wrong view type
Upvotes: 2
Views: 242
Reputation: 3173
You need to define one ViewHolder for each of your layout types, and then you need to use each ViewHolder's class instance type to bind the data on the fly, respectively in getItemViewType() and onBindViewHolder(), something along these lines:
@Override
public int getItemViewType(int position) {
if (isValidPosition(position)) {
Data d = mDataset[position];
if (d instanceof LandscapeData) {
return VIEW_TYPE_LANDSCAPE;
} else if (d instanceof PortraitData) {
return VIEW_TYPE_PORTRAIT;
}
// more else-ifs here
}
// default to landscape
return VIEW_TYPE_LANDSCAPE;
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Data d = mDataset[position];
if (holder instanceof LandscapeVH) {
// binding of dataset for landscape layouts
LandscapeData data = (LandscapeData) d;
LandscapeVH vh = (LandscapeVH) holder;
// bind the data to the view on the fly here
vh.myTextView.setText(data.getLandscapeTitle());
} else if (holder instanceof PortraitVH) {
// binding of dataset for portrait layouts
PortraitData data = (PortraitData) d;
PortraitVH vh = (PortraitVH) holder;
// bind the data to the view on the fly here
vh.myTextView.setText(data.getPortraitTitle());
}
// more else-ifs here
}
Note: your mDataset[] is your only source of truth, and it contains datasets of type Data which can be subclassed to more specialized datasets like LandscapeData and PortraitData which go hand in hand with your layout's types.
Upvotes: 2