5
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Groupie 基本系からドラッグ&ドロップ、スワイプなど

Last updated at Posted at 2021-06-14

こんにちはめっしーです(Twitter:https://twitter.com/messi_0601)

最近JetpackComposeが世間的には話題になってますが、あえてGroupieを触れたので一連の使い方まとめました。

Groupieとは

複雑なRecyclerViewレイアウト用のシンプルで柔軟なライブラリ

導入

gradle

implementation "com.github.lisawray.groupie:groupie:$groupie_version"
implementation "com.github.lisawray.groupie:groupie-databinding:$groupie_version"
//databindingとviewbindingを両方利用している場合は以下を使う
implementation "com.github.lisawray.groupie:groupie-viewbinding:$groupie_version"
allprojects {
  repositories {
    maven { url "https://jitpack.io" }
  }
}

xml

activityかfragmentのxmlにRecyclerView追加

<androidx.recyclerview.widget.RecyclerView
      android:layout_width="0dp"
      android:layout_height="0dp"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toTopOf="parent" />

次にitemに表示させたいviewの作成
今回はtextのみを表示する

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto">

  <data>
    <variable
      name="itemLabel"
      type="String" />
  </data>

  <androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="100dp">

    <TextView
      android:id="@+id/textView"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="@{itemLabel}"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toTopOf="parent" />
  </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

Itemクラス

viewbindingを利用の際にはinitializeViewBinding必要

class Item(private val itemLabel: String) : BindableItem<HogeBinding>() {
  override fun bind(viewBinding: ModelHogeItemBinding, position: Int) {
    viewBinding.itemLabel = itemLabel
  }

  override fun getLayout(): Int = R.layout.model_hoge_item

  //viewbindingの際には必要
  override fun initializeViewBinding(view: View): ModelHogeItemBinding {
    return ModelHogeItemBinding.bind(view)
  }
}

activity/fragment

databinding想定

val adapter = GroupieAdapter()
with(binding) {
  recyclerView.adapter = adapter
  recyclerView.layoutManager = LinearLayoutManager(context)
}

adapterに対してdateの渡し方

  • addを使う

Itemの1つ1つ入れていく形

livedateを使用してる。

viewModel.itemList.observe(viewLifecycleOwner) {
  for (item in it) {
    adapter.add(Item(item))
	}
}
  • updateを使う

updateにはItemクラスのcollectionが入る

val hogeList = listOf<Group>(
  Item("test1"),
  Item("test2"),
  Item("test3")
)
adapter.update(hogeList)

click処理について

Item自体のクリック

OnItemClickListenerを継承でつける

class GroupieCarouselItem(
  private val colorInt: Int,
  private val adapter: GroupieAdapter
) : BindableItem<ModelGroupieCarouselItemBinding>(), OnItemClickListener {

  //bindとgetLayout省略してますが必要です。
  
  //以下2つを追加
  init {
    adapter.setOnItemClickListener(this)
  }
  override fun onItemClick(item: Item<*>, view: View) {
    //ここにクリックした際の処理をかく
  }
}

アイテムの中にあるviewでクリック処理

Itemクラスのbindで定義する。

interfaceでListenerを作り、bindで指定する。
ここではItem内にinterfaceを作成した。

class GroupieItem(
  private val playgroundModel: GroupiePlaygroundModel,
  private val listener: Listener
) : BindableItem<ModelGroupieItemBinding>() {

  interface Listener {
    fun onItemClick()
  }

  override fun bind(viewBinding: ModelGroupieItemBinding, position: Int) {
    viewBinding.toGroupieView.setOnClickListener {
      listener.onItemClick()
    }
  }
}

fragmentにGroupieItem.Listenerを継承で渡す。


class GroupieFragment : Fragment(), GroupieItem.Listener {
  override fun onItemClick() {
    //クリック処理をかく
  }
}

データの削除

adapter.remove(item)

様々な種類のGroupie

Groupieの種類を紹介していきます。

HeaderとBodyアイテムがあるタイプ

Videotogif.gif

val basicHeaderItem = listOf("フルーツ","動物")
val basicFruitsItems = listOf("りんご","みかん","ばなな","メロン","スイカ","ぶどう","マスカット")
val basicAnimalItems = listOf("ぞう","キリン","ペンギン","ライオン")

private val _itemList = MutableLiveData<List<Group>>()
val itemList: LiveData<List<Group>> = _itemList

fun fetchData() {
  val list = mutableListOf<Group>()
  val bodyList = listOf(
    groupieSample.basicFruitsItems,
    groupieSample.basicAnimalItems
  )
  //ここでlistに表示したい順に入れていく
  for ((index, title) in groupieSample.basicHeaderItem.withIndex()) {
    list += HeaderItem(title)
    for (item in bodyList[index]) {
      list += BodyItem(item)
    }
  }
  _itemList.value = list
}

Carousel

Videotogif (2).gif
layoutManagerLinearLayoutManager.HORIZONTALに変える

binding.recyclerView.layoutManager =
        LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)

スワイプ削除とドラッグ移動

Videotogif (3).gif

Itemクラス

スワイプ動作
スワイプ方向を返すgetSwipeDirs()を追加

ドラッグ移動
ドラッグ方向を返すgetDragDirs()を追加

class GroupieSwipeItem() : BindableItem<ModelGroupieSwipeItemBinding>() {

  override fun bind(viewBinding: ModelGroupieSwipeItemBinding, position: Int) {}
  override fun getLayout(): Int = R.layout.model_groupie_swipe_item
  //追加
  override fun getSwipeDirs(): Int = ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
  override fun getDragDirs(): Int = ItemTouchHelper.UP or ItemTouchHelper.DOWN
}

fragment/activity

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
  super.onViewCreated(view, savedInstanceState)
  //...

  //追加:recyclerViewでドラッグ&ドロップ移動やスワイプ動作をする際に加える
  ItemTouchHelper(itemTouchHelper).attachToRecyclerView(binding.recyclerView)
}

private val itemTouchHelper: TouchCallback by lazy {
  object : TouchCallback() {
    override fun onMove(
      recyclerView: RecyclerView,
      viewHolder: RecyclerView.ViewHolder,
      target: RecyclerView.ViewHolder
    ): Boolean  {
      val fromPosition = viewHolder.adapterPosition
      val toPosition = target.adapterPosition
      binding.recyclerView.adapter?.notifyItemMoved(fromPosition, toPosition)
      return false
    }

    override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
      val item = adapter.getItem(viewHolder.adapterPosition)
      adapter.remove(item)
    }
  }
}

TouchCallbackについて
ドラッグとスワイプの方向で作成できるデフォルトのコールバックの単純なラッパー。
このクラスはフラグのコールバックを処理します。ユースケースに応じて、onMoveまたはonSwipedをオーバーライドする必要があります。
https://developer.android.com/reference/androidx/recyclerview/widget/ItemTouchHelper.SimpleCallback?hl=ja

onMove

override fun onMove(
  recyclerView: RecyclerView,
  viewHolder: RecyclerView.ViewHolder,
  target: RecyclerView.ViewHolder
): Boolean {
  //adapterPositionでアイテムのポジションをIntで取ってる
  val fromPosition = viewHolder.adapterPosition
  val toPosition = target.adapterPosition
  binding.recyclerView.adapter?.notifyItemMoved(fromPosition, toPosition)
  return false
}

ドラッグアンドドロップが完了した際に呼び出されるメソッド

引数 説明
recyclerView ドラッグアンドドロップが発生したRecyclerViewのオブジェクト。
viewHolder 開始時のオブジェクト。開始時のポジション情報を保持している。
target 完了時のオブジェクト。完了時のポジション情報を保持している。

最後に返り値として正常にドラッグアンドドロップが終了した際にtrueかfalseを返す
以下ドキュメント

/**
* @return True if the {@code viewHolder} has been moved to the adapter position of
* {@code target}.
* @see #onMoved(RecyclerView, ViewHolder, int, ViewHolder, int, int, int)
*/

上記のに書いてあるようにtureの場合onMovedメソッドが呼び出される
呼び出す必要がない場合はfalseで問題ない。

onSwiped

override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
  val item = adapter.getItem(viewHolder.adapterPosition)
  //スワイプでアイテムを消してる
  adapter.remove(item)
}
引数 説明
viewHolder スワイプされたViewHolderのオブジェクト
direction viewHolderがスワイプされる方向をとる

サンプル

こちらは自分が行ってきた技術サンプルが詰まってます。
その中のGroupieフォルダーに今回のコードが書かれてあります。ぜひご覧ください。

参考

おわり

ではまた次回の記事で
Twitterフォローも是非よろしくお願いします!
https://twitter.com/messi_0601

5
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?