zukkeyの技術奮闘記

”当たり前”が誰かのためになる、はず

Groupieことはじめ #2 ~サンプルを作ってみる~

はじめに

こんばんは

FlutterもAnkoもやりたいzukkeyです

 

今日は前回の続きでGroupieをつかったサンプルの紹介とその作成方法についてご紹介します

 

前回の記事はこちら

rozkey.hatenablog.com

 

そして、今回作成するサンプルが以下になります。

f:id:rozkey59:20180311220124g:plain

 

今回はHeaderとGridのViewが表示されているサンプルをGroupieを用いて簡単に作っていきます

 

まずはじめに

 

前回の記事にもありますが、下記リンクのライブラリを使います

github.com

 

前回の記事では、DataBindingを使用していないパターンで紹介していたのですが、今回はDataBindingを使用していきます。

 

Databindingを使用するために

DataBindingを利用するには、まず初めにapp.gradleファイルの中で

android {
...
dataBinding {
enabled = true
}
...
}

上記のようにコードを追加してください。

 

また、kaptでエラーが出るので、ファイルの上の方に以下のように追加する必要があります。

apply plugin: 'kotlin-kapt'

 

最後に、dependenciesの中で下記のように追加してください。

dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation"org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
implementation 'com.android.support:appcompat-v7:27.0.2'
implementation 'com.android.support.constraint:constraint-layout:1.0.2'
implementation 'com.android.support:design:27.0.2'
implementation 'com.android.support:cardview-v7:27.0.2'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.1'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
compile 'com.xwray:groupie:2.0.3'
compile 'com.xwray:groupie-kotlin-android-extensions:2.0.3'
compile 'com.xwray:groupie-databinding:2.0.3'
kapt 'com.android.databinding:compiler:3.0.1' // 追加
}

 

これでDataBindingが使用できます

 

サンプルを作成していく

Headerを作成する

まず初めにRecyclerViewのHeaderを作成していきます

下記のようにitem_header.xmlを新規作成しコードを追加してください

<?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"
xmlns:tools="http://schemas.android.com/tools">

<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">

<TextView
android:id="@+id/title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingTop="12dp"
android:paddingBottom="12dp"
android:paddingLeft="12dp"
android:textColor="@android:color/black"
android:textSize="24sp"
android:background="@android:color/darker_gray"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
tools:text="Header Title"/>

<TextView
android:id="@+id/sub_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingTop="12dp"
android:paddingBottom="12dp"
android:paddingLeft="12dp"
android:textColor="@android:color/black"
android:textSize="12sp"
android:background="@android:color/darker_gray"
app:layout_constraintTop_toBottomOf="@+id/title"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
tools:text="Sub Title"/>

</android.support.constraint.ConstraintLayout>
</layout>

 

Previewでみると以下のようになっているのが確認できます

f:id:rozkey59:20180311221138p:plain

 

次にこのレイアウト用のItemクラスを作成していきます

HeaderItemクラスを下記のように作成します


open class HeaderItem(
@StringRes private val titleResId: Int,
@StringRes private val subTitleResId: Int? = null): BindableItem<ItemHeaderBinding>() {

override fun bind(viewBinding: ItemHeaderBinding, position: Int) {
viewBinding.title.setText(titleResId)
subTitleResId?.let { viewBinding.subTitle.setText(it) }
}

override fun getLayout(): Int = R.layout.item_header
}

タイトルとサブタイトルのstringIdを定義することでRecyclerViewのHeaderを用意できます

 

中身のカードを作成していく

次にグリッド表示されているカードを作成していきます

まず、item_card.xmlを用意してください

下記のようにコードを追加します

<?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"
xmlns:tools="http://schemas.android.com/tools">

<android.support.v7.widget.CardView
android:id="@+id/item_fancy_cardView"
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_margin="12dp"
android:background="@android:color/white"
android:clickable="true"
android:focusable="true"
app:cardCornerRadius="12dp"
app:cardElevation="4dp">

<TextView
android:id="@+id/item_fancy_number"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textColor="@color/colorAccent"
android:textSize="24sp"
tools:text="1" />

</android.support.v7.widget.CardView>
</layout>

 

そうすると、Previewの中で以下のような表示でカードのレイアウトが組めていることが確認できます。

 

f:id:rozkey59:20180311221629p:plain

 

次に、カードのItemクラスを作成します

class CardItem(@ColorInt private val color: Int,
private val number: Int) : Item() {
override fun bind(viewHolder: ViewHolder, position: Int) {
viewHolder.item_fancy_cardView.setCardBackgroundColor(color)
viewHolder.item_fancy_number.text = number.toString()
}

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

override fun getSpanSize(spanCount: Int, position: Int): Int = spanCount / 3

}

Cardの背景の色と、テキストを設定することができるようにしています

 

カラムグループを定義する

最後にグリッド表示するカラムグループを定義するためのクラスを作成します

これは、Groupieのexampleの中ですでにあるのでそこから持ってきましょう

下記のようなクラスになります

class ColumnGroup(items: List<Item<*>>) : NestedGroup() {

private val items = ArrayList<Item<*>>()

init {
for (i in items.indices) {
// Rearrange items so that the adapter appears to arrange them in vertical columns
var index: Int
if (i % 2 == 0) {
index = i / 2
} else {
index = (i - 1) / 2 + (items.size / 2f).toInt()
// If columns are uneven, we'll put an extra one at the end of the first column,
// meaning the second column's indices will all be increased by 1
if (items.size % 2 == 1) index++
}
val trackItem = items[index]
this.items.add(trackItem)
}
}

override fun getPosition(group: Group): Int {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}

override fun getGroup(position: Int): Group {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}

override fun getGroupCount(): Int {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}

override fun unregisterGroupDataObserver(groupDataObserver: GroupDataObserver) {
// no need to do anything here
}

override fun getItem(position: Int): Item<*> {
return items[position]
}

override fun getItemCount(): Int {
return items.size
}

}

 

最後にMainActivityにてaddしていく

実際のコードは以下の通り

class MainActivity : AppCompatActivity() {

val binding: ActivityMainBinding by lazy {
DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val groupAdapter = GroupAdapter<ViewHolder>().apply {
spanCount = 3
}

val cardItem = generateCards(24)

Section(HeaderItem(R.string.test_title, R.string.test_subtitle)).apply {
add(ColumnGroup(cardItem))
groupAdapter.add(this)
}
binding.recyclerView.apply {
layoutManager = GridLayoutManager(context, groupAdapter.spanCount).apply {
spanSizeLookup = groupAdapter.spanSizeLookup
}
adapter = groupAdapter
}

}


private fun generateCards(count: Int): MutableList<CardItem> {
val rnd = Random()
return MutableList(count) {
val color = Color.argb(255, rnd.nextInt(256),
rnd.nextInt(256), rnd.nextInt(256))
CardItem(color, rnd.nextInt(100))
}
}
}

 

generateCardsでは、ランダムなカラーと数字を入れたリストを作って返しています。

 

Itemクラスでは、どのレイアウトを使うか定義するViewHolderという感じです。

Groupクラスではある程度の機能を持った単位でまとめるもので、カスタムしたい場合は好きなように作ることができます。

SectionはHeaderと中身をまとめた単位で持つものになります。

 

groupAdapterの中にaddしていって最後にrecyclerViewに追加してるといった感じになります。

 

 

さいごに

ライブラリのサンプルを見たりしながら、簡単なサンプルの作成を紹介しました。

 

実際にheaderをもったrecyclerViewを書く場合はViewTypeでif / switch(when)の分岐を書いたりしなくてはならないので便利っちゃ便利。

 

でも、まだすげぇwwwみたいに感じていないのでもう少しわかってから続編を書こうかなと思ってます。

今、Groupieを使ってサンプルアプリを作っている最中なのでまた後程紹介していくことができたらと考えています。

 

おまけ

今作成しているサンプルアプリ。

何とか完成させて、三月中にリリースしたい( ^ω^)・・・

f:id:rozkey59:20180311223519g:plain