Created
March 13, 2018 15:40
-
-
Save thibseisel/5382c0f460dc2ae89082654c816c2225 to your computer and use it in GitHub Desktop.
A RecyclerView adapter that wraps an existing adapter to split data into sections.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="utf-8"?> | |
<resources> | |
<item name="viewtype_header" type="id"/> | |
</resources> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="utf-8"?> | |
<TextView xmlns:android="http://schemas.android.com/apk/res/android" | |
xmlns:tools="http://schemas.android.com/tools" | |
android:id="@+id/section_label" | |
android:layout_width="match_parent" | |
android:layout_height="wrap_content" | |
android:gravity="center_vertical" | |
android:minHeight="48dp" | |
android:paddingEnd="16dp" | |
android:paddingLeft="16dp" | |
android:paddingRight="16dp" | |
android:paddingStart="16dp" | |
android:textAppearance="@style/TextAppearance.AppCompat.Medium" | |
android:textColor="?attr/colorPrimary" | |
android:textSize="14sp" | |
tools:text="3 days ago" /> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.support.annotation.IdRes | |
import android.support.annotation.LayoutRes | |
import android.support.v7.widget.RecyclerView | |
import android.util.SparseArray | |
import android.view.ViewGroup | |
import android.widget.TextView | |
open class SectionedAdapter( | |
baseAdapter: RecyclerView.Adapter<RecyclerView.ViewHolder>, | |
@LayoutRes private val headerLayoutResId: Int, | |
@IdRes private val headerLabelId: Int | |
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() { | |
private val sections = SparseArray<SectionHeader>() | |
private var isValid: Boolean = true | |
private val baseAdapter: RecyclerView.Adapter<RecyclerView.ViewHolder> = baseAdapter.also { | |
it.registerAdapterDataObserver(BaseAdapterObserver()) | |
} | |
constructor(baseAdapter: RecyclerView.Adapter<RecyclerView.ViewHolder>) : this( | |
baseAdapter, | |
R.layout.item_section_header, | |
R.id.section_label | |
) | |
override fun getItemCount() = if (isValid) baseAdapter.itemCount + sections.size() else 0 | |
override fun getItemViewType(position: Int): Int = if (isSectionPosition(position)) | |
R.id.viewtype_header else baseAdapter.getItemViewType(sectionedPositionToPosition(position)) | |
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder = | |
if (viewType == R.id.viewtype_header) { | |
SectionViewHolder(parent, headerLayoutResId, headerLabelId) | |
} else { | |
baseAdapter.onCreateViewHolder(parent, viewType) | |
} | |
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, sectionedPosition: Int) { | |
if (holder.itemViewType == R.id.viewtype_header) { | |
(holder as SectionViewHolder).label.text = sections[sectionedPosition].title | |
} else { | |
baseAdapter.onBindViewHolder(holder, sectionedPositionToPosition(sectionedPosition)) | |
} | |
} | |
override fun getItemId(position: Int): Long { | |
return if (isSectionPosition(position)) Long.MAX_VALUE - sections.indexOfKey(position) | |
else baseAdapter.getItemId(sectionedPositionToPosition(position)) | |
} | |
fun setSections(sectionHeaders: List<SectionHeader>) { | |
sections.clear() | |
var offset = 0 | |
for (section in sectionHeaders.sorted()) { | |
section.sectionedPosition = section.position + offset | |
sections.append(section.sectionedPosition, section) | |
offset++ | |
} | |
notifyDataSetChanged() | |
} | |
protected fun isSectionPosition(position: Int): Boolean = sections[position] != null | |
private fun positionToSectionedPosition(position: Int): Int { | |
var offset = 0 | |
for (i in 0 until sections.size()) { | |
if (sections.valueAt(i).position > position) break | |
offset++ | |
} | |
return position + offset | |
} | |
private fun sectionedPositionToPosition(sectionedPosition: Int): Int { | |
if (isSectionPosition(sectionedPosition)) { | |
return RecyclerView.NO_POSITION | |
} | |
var offset = 0 | |
for (i in 0 until sections.size()) { | |
if (sections.valueAt(i).sectionedPosition > sectionedPosition) break | |
offset-- | |
} | |
return sectionedPosition + offset | |
} | |
internal class SectionViewHolder( | |
parent: ViewGroup, | |
@LayoutRes headerLayoutResId: Int, | |
@IdRes headerLabelId: Int | |
) : RecyclerView.ViewHolder(parent.inflate(headerLayoutResId)) { | |
val label: TextView = itemView.findViewById(headerLabelId) | |
} | |
private inner class BaseAdapterObserver : RecyclerView.AdapterDataObserver() { | |
override fun onChanged() { | |
isValid = baseAdapter.itemCount > 0 | |
notifyDataSetChanged() | |
} | |
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { | |
isValid = baseAdapter.itemCount > 0 | |
notifyItemRangeInserted(positionStart, itemCount) | |
} | |
override fun onItemRangeChanged(positionStart: Int, itemCount: Int) { | |
isValid = baseAdapter.itemCount > 0 | |
notifyItemRangeChanged(positionStart, itemCount) | |
} | |
override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) { | |
isValid = baseAdapter.itemCount > 0 | |
notifyItemRangeRemoved(positionStart, itemCount) | |
} | |
} | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class SectionHeader( | |
val position: Int, | |
val title: String | |
) : Comparable<SectionHeader> { | |
internal var sectionedPosition: Int = -1 | |
override fun compareTo(other: SectionHeader): Int { | |
return position - other.position | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment