RecyclerView i PagerSnapHelper. Centrowanie elementów z zachowaniem UX.
Dowiesz się, w jaki sposób podczas pracy z RecyclerView (bez scrollbar) wycentrować wybrany element przy użyciu SnapHelper.
W tym artykule dowiesz się, w jaki sposób podczas pracy z RecyclerView (bez scrollbar) wycentrować wybrany element a dodatkowo zadbać o to, aby użytkownicy wiedzieli, że istnieją inne elementy na liście.
Co chcemy osiągnąć?
Nasz plan podzielimy na dwa podpunkty.
- Chcemy, aby podczas przewijania listy, element, który aktualnie jest najbardziej widoczny po puszczeniu palca, został wycentrowany. Zależy nam na efekcie 'przyklejenia’ go do środka ekranu.
- Nie chcemy używać pasków przewijania. Zamiast tego pokażemy krańce sąsiednich elementów.
Do it!
Krok pierwszy
Chcemy, aby podczas przewijania horyzontalnej listy wybrany jej element znalazł się na środku ekranu.
Podczas projektowania interfejsu użytkownika pamiętaj o tym, że bardzo ważne jest zaprojektowanie go w taki sposób, aby użytkownik nie miał problemów ze zrozumieniem działania aplikacji.
Konfiguracja
W ramach projektu demo wykorzystamy bibliotekę Material oraz co nie jest zaskoczeniem – RecyclerView.
implementation 'com.google.android.material:material:1.1.0'
implementation 'androidx.recyclerview:recyclerview:1.1.0'
Stwórz widok, w którym osadzimy nasz RecyclerView oraz widok jego pojedynczego elementu.
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView 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:layout_width="match_parent"
android:layout_height="150dp"
android:layout_margin="8dp"
app:cardCornerRadius="16dp"
app:cardElevation="4dp">
<View
android:layout_width="match_parent"
android:layout_height="150dp"
android:background="#ff2674" />
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="16dp"
android:gravity="center"
android:textAllCaps="true"
android:textColor="#ffffff"
android:textSize="24sp"
android:textStyle="bold"
tools:text="text" />
</androidx.cardview.widget.CardView>
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:text="List of items"
android:textColor="#333333"
android:textSize="20sp"
android:textStyle="bold"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="8dp"
android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/title"
tools:listitem="@layout/item_rv" />
</androidx.constraintlayout.widget.ConstraintLayout>
Widoki skończone, więc płynnie przejdźmy do kodu. Adapter jest standardowy i nie jest celem artykułu, dlatego nie będę go opisywał. Zwróć uwagę na użycie klasy PagerSnapHelper. Dzięki niej podczas przewijania RecyclerView element, który jest aktualnie najbardziej widoczny, zostanie wyśrodkowany po oderwaniu palca od ekranu. Magia!
Przekonaj się.
class RVPagerSnapActivity : AppCompatActivity()
{
override fun onCreate(savedInstanceState: Bundle?)
{
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_rv_snaper)
val items = listOf("a", "b", "c", "d", "e", "f")
rv.adapter = MyAdapter(items)
val pagerSnapHelper = PagerSnapHelper()
pagerSnapHelper.attachToRecyclerView(rv)
}
}
class MyAdapter(private val items: List<String>) : RecyclerView.Adapter<MyAdapter.MyViewHolder>()
{
override fun getItemCount() = items.size
override fun onBindViewHolder(holder: MyViewHolder, position: Int)
{
val text = items[position]
holder.textView.text = text
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder
{
val view: View =
LayoutInflater.from(parent.context).inflate(R.layout.item_rv, parent, false)
return MyViewHolder(view)
}
class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
{
val textView = itemView.findViewById<TextView>(R.id.title)
}
}
Czas na krok drugi
Poprzednie rozwiązanie posiada spory mankament. Kompletnie nie jest widoczne to, że mamy do czynienia z przewijalną listą. Naprawiając to, wcale nie musimy zaglądać do kodu. Wystarczą sztuczki z paddingami i obcinaniem zawartości.
Wystarczy, że uzupełnisz deklarację RecyclerView o następujące atrybuty:
android:clipToPadding="false"
android:paddingStart="16dp"
android:paddingEnd="16dp"
Dzięki temu, że dodaliśmy lewy i prawy padding
w naszym RecyclerView
, a do tego nakazaliśmy mu nieucinanie zawartości, która znajduje się poza jego obszarem (wyznaczonym przez padding
), sąsiednie elementy są widoczne.
Jeśli uruchomisz teraz aplikację, zwróć uwagę na to, że zachowaliśmy poprzednio wypracowane rozwiązanie (przyklejanie się najbardziej widocznego elementu), a dodatkowo pomogliśmy zrozumieć użytkownikowi, z czym ma do czynienia. Prawda, że łatwe?
Pamiętaj, że to rozwiązanie zadziała, jeśli pojedynczy padding
będzie większy od marginesu bocznego pojedynczego elementu listy.
Aktualnie margines elementy wynosi 8dp a padding
listy 16dp. To pozwala na wyświetlenie krawędzi elementu sąsiedniego o długości 8dp.