Большая часть Material Design — это способ взаимодействия пользователей с визуальными элементами приложения. Поэтому в дополнение к касаниям и долгим нажатиям сегодня ожидается, что хорошо сделанное приложение для Android будет обрабатывать более сложные жесты, такие как перетаскивание и перетаскивание. Это особенно важно, если приложение использует списки для отображения своих данных.
С помощью виджета RecyclerView
и нескольких других компонентов Android Jetpack вы можете обрабатывать различные жесты, связанные со списком, в своих приложениях. Кроме того, всего за несколько строк кода вы можете связать анимацию Material Motion с этими жестами.
В этом уроке я покажу вам, как добавить несколько общих жестов смахивания, в комплекте с интуитивно понятными анимациями, в ваши списки.
Предпосылки
Чтобы максимально использовать этот урок, вам понадобятся:
- Android Studio 3.2.1 или выше
- телефон или планшет под управлением Android API уровня 23 или выше
1. Создание списка
Чтобы этот урок был коротким, давайте сгенерируем один из шаблонов, доступных в Android Studio.
Начните с запуска Android Studio и создания нового проекта. В мастере создания проекта убедитесь, что вы выбрали опцию « Пустое действие» .
Вместо библиотеки поддержки мы будем использовать Android Jetpack в этом проекте. Итак, после создания проекта перейдите в Refactor> Migrate to AndroidX . При появлении запроса нажмите кнопку « Перенос» .
Затем, чтобы добавить список в проект, выберите «Файл»> «Создать»> «Фрагмент»> «Фрагмент (список)» . В появившемся диалоговом окне продолжите и нажмите кнопку Готово , не внося никаких изменений в значения по умолчанию.
На этом этапе Android Studio создаст новый фрагмент, содержащий полностью настроенный виджет RecyclerView
. Он также будет генерировать фиктивные данные для отображения внутри виджета. Тем не менее, вам все равно придется добавить фрагмент к основному виду деятельности вручную.
Для этого сначала добавьте интерфейс OnListFragmentInteractionListener
к основному OnListFragmentInteractionListener
деятельности и реализуйте единственный содержащийся в нем метод.
1
2
3
4
|
override fun onListFragmentInteraction(
item: DummyContent.DummyItem?) {
// leave empty
}
|
Затем вставьте фрагмент в действие, добавив следующий <fragment>
в файл activity_main.xml :
1
2
3
4
|
<fragment android:layout_width=»match_parent»
android:layout_height=»match_parent»
android:id=»@+id/list_fragment»
android:name=»com.tutsplus.rvswipes.ItemFragment» />
|
На этом этапе, если вы запустите свое приложение, вы сможете увидеть список, который выглядит следующим образом:
2. Добавление жеста для удаления
Используя класс ItemTouchHelper
, вы можете быстро добавлять жесты перетаскивания и перетаскивания в любой виджет RecyclerView
. Класс также предоставляет анимации по умолчанию, которые запускаются автоматически при обнаружении действительного жеста.
Класс ItemTouchHelper
нуждается в экземпляре абстрактного класса ItemTouchHelper.Callback
чтобы иметь возможность обнаруживать и обрабатывать жесты. Хотя вы можете использовать его напрямую, гораздо проще использовать вместо этого класс-оболочку SimpleCallback
. Это тоже абстрактно, но у вас будет меньше методов для переопределения.
Создайте новый экземпляр класса SimpleCallback
внутри onCreateView()
класса ItemFragment
. В качестве аргумента для его конструктора вы должны передать направление свайпа, которое хотите обработать. А пока передайте ему RIGHT
чтобы он обрабатывал жест смахивания вправо.
1
2
3
4
5
6
|
val myCallback = object: ItemTouchHelper.SimpleCallback(0,
ItemTouchHelper.RIGHT) {
// More code here
}
|
У класса есть два абстрактных метода, которые вы должны переопределить: метод onMove()
, который обнаруживает перетаскивания, и метод onSwiped()
, который обнаруживает свайпы. Поскольку сегодня мы не будем обрабатывать какие-либо жесты перетаскивания, убедитесь, что вы возвращаете false
в onMove()
.
01
02
03
04
05
06
07
08
09
10
11
12
|
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean = false
override fun onSwiped(viewHolder: RecyclerView.ViewHolder,
direction: Int) {
// More code here
}
|
Внутри onSwiped()
вы можете использовать свойство adapterPosition
чтобы определить индекс элемента списка, который был проведен. Поскольку сейчас мы реализуем жест смахивания для удаления, передайте индекс методу removeAt()
из фиктивного списка, чтобы удалить элемент.
1
|
DummyContent.ITEMS.removeAt(viewHolder.adapterPosition)
|
Кроме того, вы должны передать этот же индекс notifyItemRemoved()
адаптера виджета RecyclerView
, чтобы убедиться, что элемент больше не отображается. При этом также запускается анимация удаления элемента по умолчанию.
1
|
adapter?.notifyItemRemoved(viewHolder.adapterPosition)
|
На этом этапе объект SimpleCallback
готов. Все, что вам нужно сделать сейчас, это создать объект ItemTouchHelper
и прикрепить к нему виджет RecyclerView
.
1
2
|
val myHelper = ItemTouchHelper(myCallback)
myHelper.attachToRecyclerView(this)
|
Если вы запустите приложение сейчас, вы сможете смахивать элементы из списка.
3. Выявление фона
Хотя жест смахивания с удалением очень интуитивен, некоторые пользователи могут быть не уверены, что произойдет, когда они будут выполнять жест. Поэтому в руководствах по дизайну материалов говорится, что жест должен также постепенно раскрывать вид, скрытый за элементом, который четко указывает на то, что произойдет дальше. Обычно фоновый вид — это просто значок, отображающий мусорное ведро.
Чтобы добавить значок корзины в проект, выберите « Файл»> «Создать»> «Векторный набор» и выберите значок « Удалить» .
Теперь вы можете получить ссылку на значок в своем коде Kotlin, вызвав метод getDrawable()
. Поэтому добавьте следующую строку в метод onCreateView()
класса ItemFragment
:
1
2
3
4
|
val trashBinIcon = resources.getDrawable(
R.drawable.ic_delete_black_24dp,
null
)
|
Отображение вида позади элемента списка немного сложнее, потому что вам нужно нарисовать его вручную, а также убедиться, что его границы соответствуют границам области, которая постепенно раскрывается.
Переопределите метод onChildDraw()
вашей реализации SimpleCallback
чтобы начать рисование.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
override fun onChildDraw(
c: Canvas,
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
dX: Float,
dY: Float,
actionState: Int,
isCurrentlyActive: Boolean
) {
// More code here
super.onChildDraw(c, recyclerView, viewHolder,
dX, dY, actionState, isCurrentlyActive)
}
|
В приведенном выше коде onChildDraw()
метода onChildDraw()
суперкласса. Без этого ваши элементы списка не будут перемещаться при их перелистывании.
Поскольку мы обрабатываем только жест смахивания вправо, координаты X верхнего левого и нижнего левого углов фонового представления всегда будут равны нулю. Координаты X верхнего правого и нижнего правого углов, с другой стороны, должны быть равны параметру dX
, который указывает, насколько элемент списка был перемещен пользователем.
Чтобы определить координаты Y всех углов, вы должны будете использовать свойства top
и bottom
одного из представлений, представленных внутри объекта viewHolder
.
Используя все эти координаты, теперь вы можете определить прямоугольную область клипа. В следующем коде показано, как использовать для clipRect()
метод clipRect()
объекта Canvas
:
1
2
|
c.clipRect(0f, viewHolder.itemView.top.toFloat(),
dX, viewHolder.itemView.bottom.toFloat())
|
Несмотря на то, что это не обязательно, рекомендуется сделать область клипа видимой, придав ей цвет фона. Вот как вы можете использовать метод drawColor()
чтобы сделать область клипа серой, когда расстояние drawColor()
мала, и красной, когда она больше.
1
2
3
4
|
if(dX < width / 3)
c.drawColor(Color.GRAY)
else
c.drawColor(Color.RED)
|
Теперь вам нужно будет указать границы значка корзины. Эти границы должны включать поле, соответствующее тексту, отображаемому в элементах списка. Чтобы определить значение поля в пикселях, используйте getDimension()
и передайте ему text_margin
.
1
2
|
val textMargin = resources.getDimension(R.dimen.text_margin)
.roundToInt()
|
Вы можете повторно использовать координаты верхнего левого угла области клипа в качестве координат верхнего левого угла значка. Однако они должны быть смещены с помощью textMargin
. Чтобы определить координаты его нижнего правого угла, используйте его ширину и высоту. Вот как:
1
2
3
4
5
6
7
|
trashBinIcon.bounds = Rect(
textMargin,
viewHolder.itemView.top + textMargin,
textMargin + trashBinIcon.intrinsicWidth,
viewHolder.itemView.top + trashBinIcon.intrinsicHeight
+ textMargin
)
|
Наконец, нарисуйте значок, вызвав его метод draw()
.
1
|
trashBinIcon.draw(c)
|
Если вы снова запустите приложение, вы сможете увидеть значок, когда проводите пальцем, чтобы удалить элемент списка.
4. Добавление жеста смахивания к обновлению
Жест смахивания до обновления, также известный как жест с вытягиванием до обновления, стал настолько популярным в наши дни, что в Android Jetpack есть специальный компонент для него. Он называется SwipeRefreshLayout
и позволяет быстро связать жест с любым виджетом RecyclerView
, ListView
или GridView
.
Для поддержки жеста смахивания до обновления в вашем виджете RecyclerView
вы должны сделать его дочерним по SwipeRefreshLayout
виджету SwipeRefreshLayout
. Итак, откройте файл <SwipeRefreshLayout>
добавьте к нему тег <SwipeRefreshLayout>
и переместите <RecyclerView>
внутри него. После этого содержимое файла должно выглядеть так:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
<?xml version=»1.0″ encoding=»utf-8″?>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
xmlns:android=»https://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»>
<androidx.recyclerview.widget.RecyclerView
android:id=»@+id/list»
android:name=»com.tutsplus.rvswipes.ItemFragment»
android:layout_width=»match_parent»
android:layout_height=»match_parent»
android:layout_marginLeft=»16dp»
android:layout_marginRight=»16dp»/>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
Фрагмент списка предполагает, что виджет RecyclerView
является корневым элементом его макета. Поскольку это больше не так, вам нужно внести несколько изменений в метод onCreateView()
класса ItemFragment
. Сначала замените первую строку метода, которая раздувает макет, следующим кодом:
1
2
3
4
5
6
|
val srLayout: SwipeRefreshLayout =
inflater.inflate(
R.layout.fragment_item_list, container, false
) as SwipeRefreshLayout
val view = srLayout.findViewById<RecyclerView>(R.id.list)
|
Затем измените последнюю строку метода, чтобы он возвращал виджет SwipeRefreshLayout
вместо виджета RecyclerView
.
1
|
return srLayout
|
Если вы попробуете запустить приложение сейчас, вы сможете выполнить жест вертикальной прокрутки и получить визуальную обратную связь. Содержимое списка не изменится. Чтобы действительно обновить список, вы должны связать объект SwipeRefreshLayout
виджетом SwipeRefreshLayout
.
1
2
3
|
srLayout.setOnRefreshListener {
// More code here
}
|
Внутри слушателя вы можете изменять данные, отображаемые в списке, в соответствии с вашими требованиями. На данный момент, поскольку мы работаем с фиктивными данными, давайте просто очистим список фиктивных элементов и загрузим его с 25 новыми фиктивными элементами. Следующий код показывает вам, как это сделать:
1
2
3
4
5
6
|
DummyContent.ITEMS.clear()
for(i in 1..25) {
DummyContent.ITEMS.add(
DummyContent.DummyItem(«$i», «Item $i», «»)
)
}
|
После обновления данных не забудьте вызвать метод notifyDataSetChanged()
чтобы adapter
виджета RecyclerView
знал, что он должен перерисовать список.
1
|
view.adapter?.notifyDataSetChanged()
|
По умолчанию, как только пользователь выполняет жест смахивания для обновления, виджет SwipeRefreshLayout
отображает анимированный индикатор хода выполнения. Поэтому после обновления списка вы должны не забыть удалить индикатор, установив для свойства isRefreshing
виджета значение false.
1
|
srLayout.isRefreshing = false
|
Если вы запустите приложение сейчас, удалите несколько элементов списка и выполните жест «пролистывание до обновления», вы увидите сам сброс списка.
Вывод
Material Design существует уже несколько лет, и большинство пользователей в наши дни ожидают, что вы справитесь со многими жестами, которые он упоминает. К счастью, это не требует особых усилий. В этом уроке вы узнали, как реализовать два очень распространенных жеста смахивания. Вы также узнали, как постепенно открывать вид, скрытый за предметом списка.
Вы можете узнать больше о жестах и движении материала, обратившись к руководству по дизайну жестов.