Skip to content

Instantly share code, notes, and snippets.

@saeed-younus
Created September 26, 2019 16:06
Show Gist options
  • Save saeed-younus/91f681cd3bec83158695ac3196fb2cc6 to your computer and use it in GitHub Desktop.
Save saeed-younus/91f681cd3bec83158695ac3196fb2cc6 to your computer and use it in GitHub Desktop.
Selection Recyclerview List using live data, viewmodel and list adapter
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/app_bar_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#FFFFFF"
app:liftOnScroll="true">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/cl_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/cardForeground"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:id="@+id/image_back"
style="@style/ToolbarIcon"
android:layout_marginTop="8dp"
android:layout_marginStart="8dp"
android:src="@drawable/ic_left_arrow"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
style="@style/ToolbarTitle"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:gravity="center_vertical"
android:text="Mileage"
app:layout_constraintBottom_toBottomOf="@id/image_back"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/image_back"
app:layout_constraintTop_toTopOf="@id/image_back" />
<ImageView
android:id="@+id/image_delete"
style="@style/ToolbarIcon"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:src="@drawable/ic_delete"
android:layout_marginEnd="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/text_select"
style="@style/FlatButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginBottom="8dp"
android:layout_marginTop="8dp"
android:text="Select"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/image_back" />
<com.google.android.material.button.MaterialButton
android:id="@+id/text_select_all"
style="@style/FlatButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginBottom="8dp"
android:layout_marginTop="8dp"
android:text="Select All"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/image_back" />
<View
android:id="@+id/divider"
android:layout_width="1dp"
android:layout_height="0dp"
android:layout_marginTop="4dp"
android:layout_marginBottom="4dp"
android:layout_marginStart="8dp"
android:background="@color/colorPrimary"
app:layout_constraintBottom_toBottomOf="@id/text_create_list"
app:layout_constraintStart_toEndOf="@id/text_select_all"
app:layout_constraintTop_toTopOf="@id/text_create_list" />
<com.google.android.material.button.MaterialButton
android:id="@+id/text_create_list"
style="@style/FlatButton.Icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginBottom="8dp"
android:layout_marginTop="8dp"
android:text="Create New List"
app:icon="@drawable/ic_plus_circle"
android:background="?attr/selectableItemBackground"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/divider" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:id="@+id/nested_scrollview"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" >
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" />
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<View
android:layout_width="0dp"
android:layout_height="0dp"
android:elevation="10dp"
android:background="?attr/selectableItemBackground"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/image_selector"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginStart="8dp"
android:src="@drawable/ic_circle_empty"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/card_item"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/card_item" />
<com.google.android.material.card.MaterialCardView
android:id="@+id/card_item"
style="@style/itemCard"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/image_selector"
app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/text_date"
style="@style/accentText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:padding="0dp"
android:text="02-01-2019"
android:singleLine="true"
android:ellipsize="end"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/text_mile_number"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/text_mile_number"
style="@style/primaryText.Medium"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:padding="0dp"
android:text="45.2"
android:textAlignment="center"
android:textStyle="bold"
android:layout_marginTop="16dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@+id/text_mile"
app:layout_constraintEnd_toEndOf="@+id/text_mile"
app:layout_constraintStart_toStartOf="@+id/text_mile" />
<TextView
android:id="@+id/text_mile"
style="@style/primaryText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="MILES"
android:singleLine="true"
android:ellipsize="end"
android:textAllCaps="true"
android:layout_marginBottom="16dp"
android:layout_marginEnd="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/text_mile_number"
app:layout_constraintEnd_toStartOf="@id/divider" />
<ImageView
android:id="@+id/image_arrow"
style="@style/imageIcon"
android:layout_width="40dp"
android:layout_height="40dp"
android:background="#fff"
android:padding="8dp"
android:src="@drawable/ic_right_arrow"
android:tint="@color/iconSecondary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/text_property_number"
style="@style/primaryText.Medium"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:padding="0dp"
android:text="56"
android:textAlignment="center"
android:textStyle="bold"
android:singleLine="true"
android:ellipsize="end"
android:layout_marginTop="16dp"
app:layout_constraintBottom_toTopOf="@+id/text_property"
app:layout_constraintEnd_toEndOf="@+id/text_property"
app:layout_constraintStart_toStartOf="@+id/text_property"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/text_property"
style="@style/primaryText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Properties"
android:textAllCaps="true"
android:singleLine="true"
android:ellipsize="end"
android:layout_marginBottom="16dp"
app:layout_constraintTop_toBottomOf="@id/text_property_number"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/image_arrow" />
<View
android:id="@+id/divider"
android:layout_width="1dp"
android:layout_height="0dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:background="@color/iconSecondary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/text_property"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
</androidx.constraintlayout.widget.ConstraintLayout>
enum class ListState(val state: Int) {
OPEN_SELECTION(0),
CLOSED_SELECTION(0),
SELECT_ALL(0)
}
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
class SelectionListAdapter(private val onItemClickListener: () -> Unit) :
ListAdapter<String, SelectionListAdapter.SelectionViewHolder>(
SelectionDiffUtil()
) {
private var listState: ListState = ListState.CLOSED_SELECTION
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): SelectionViewHolder {
val view = LayoutInflater.from(parent.context).inflate(viewType, parent, false)
return SelectionViewHolder(view, onItemClickListener)
}
override fun getItemViewType(position: Int): Int {
return R.layout.item_selection
}
fun setListState(state: ListState) {
listState = state
this.notifyDataSetChanged()
}
override fun onBindViewHolder(
holder: SelectionViewHolder,
position: Int
) {
holder.bind(getItem(position), listState)
}
class SelectionViewHolder(val view: View, private val onItemClickListener: () -> Unit) :
RecyclerView.ViewHolder(view) {
// TODO: This is for test purpose Remove isSelected from here when we apply databinding
var isSelected = false
fun bind(item: String, listState: ListState) {
view.setOnClickListener {
if (listState == ListState.OPEN_SELECTION || listState == ListState.SELECT_ALL) {
isSelected = !isSelected
isSelectedItem(isSelected)
} else {
onItemClickListener.invoke()
}
}
when (listState) {
ListState.OPEN_SELECTION -> {
enableSelection(enable = true)
}
ListState.SELECT_ALL -> {
isSelected = true
isSelectedItem(isSelected)
enableSelection(enable = true)
}
ListState.CLOSED_SELECTION -> {
isSelected = false
isSelectedItem(isSelected)
enableSelection(enable = false)
}
}
}
private fun isSelectedItem(selected: Boolean) {
val imageViewItemSelector = view.findViewById<ImageView>(R.id.image_selector)
if (selected) {
imageViewItemSelector.setImageResource(R.drawable.ic_circle_tick)
} else {
imageViewItemSelector.setImageResource(R.drawable.ic_circle_empty)
}
}
private fun enableSelection(enable: Boolean) {
val imageViewItemSelector = view.findViewById<ImageView>(R.id.image_selector)
if (enable) {
imageViewItemSelector.visibility = View.VISIBLE
} else {
imageViewItemSelector.visibility = View.GONE
}
}
}
class SelectionDiffUtil : DiffUtil.ItemCallback<String>() {
override fun areItemsTheSame(oldItem: String, newItem: String): Boolean {
return oldItem == newItem
}
override fun areContentsTheSame(oldItem: String, newItem: String): Boolean {
return oldItem == newItem
}
}
}
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.widget.NestedScrollView
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import androidx.recyclerview.widget.LinearLayoutManager
class MileageFragment : Fragment() {
private lateinit var listAdapter: SelectionListAdapter
private lateinit var viewModel: SelectionListViewModel
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_selection_list, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProviders.of(this).get(SelectionListViewModel::class.java)
setupAdapter()
viewModel.listState.observe(this, Observer {
listAdapter.setListState(it)
})
nested_scrollview.setOnScrollChangeListener { v: NestedScrollView?,
scrollX: Int,
scrollY: Int,
oldScrollX: Int,
oldScrollY: Int ->
if (scrollY <= 0) {
app_bar_layout.setLifted(false)
}
}
image_back.setOnClickListener {
if (viewModel.listState.value == ListState.OPEN_SELECTION ||
viewModel.listState.value == ListState.SELECT_ALL
) {
text_select.visibility = View.VISIBLE
text_select_all.visibility = View.INVISIBLE
viewModel.setListState(ListState.CLOSED_SELECTION)
}
}
text_select_all.setOnClickListener {
viewModel.setListState(ListState.SELECT_ALL)
}
text_select.setOnClickListener {
text_select.visibility = View.INVISIBLE
text_select_all.visibility = View.VISIBLE
viewModel.setListState(ListState.OPEN_SELECTION)
}
}
private fun setupAdapter() {
listAdapter = SelectionListAdapter {
//Navigation
}
rv_list.apply {
adapter = listAdapter
layoutManager = LinearLayoutManager(requireContext())
}
listAdapter.submitList(listOf("", "", "", "", "", "", "", ""))
}
companion object {
fun newInstance() = MileageFragment()
}
}
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
class SelectionListViewModel(app: Application) : AndroidViewModel(app) {
private val _listState: MutableLiveData<ListState> = MutableLiveData(ListState.CLOSED_SELECTION)
val listState: LiveData<ListState> = _listState
fun setListState(state: ListState) {
_listState.value = state
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment