検証環境
この記事の内容は、以下の環境で検証しました。
- Java:open jdk 1.8.0_152
- Android Studio 3.0
- CompileSdkVersion:26
- MinSdkVersion:21
- TargetSdkVersion:26
- BuildToolsVersion:26.0.2
- gradle:3.0.0
SwipeToRefreshとは
リストを使った多くのアプリでは、リストの先頭が表示された状態で下方向にスワイプするとインジケータが表示され、更新処理が始まります。
このような機能(スワイプ時にインジケータを表示し、更新処理などを行える機能)をAndroidでは、『SwipeToRefresh』と呼びます。(参照:https://developer.android.com/training/swipe/index.html)
AndroidでSwipeToRefreshを実現するには、SwipeRefreshLayoutとそのレイアウトのコールバックメソッドを利用します。
操作イメージは下図のとおりです。
SwipeRefreshLayout
SwipeRefreshLayoutは、標準ライブラリーに同梱されていません。利用するには、「Support Library v4」を導入します。
SwipeRefreshLayoutは、SwipeRefreshLayoutタグ内のListViewなどをスワイプした際、以下の機能を提供します。
- インジケータの表示
- SwipeRefreshLayoutのコールバックメソッドを呼び出す
また、インジケータを非表示にするには、非表示にする処理をプログラムで実装する必要があります。
完成イメージ
この記事で作成するサンプルアプリの完成イメージは、下図のとおりです。
サンプルアプリの詳細
更新処理前のイメージ
「5行のサンプルデータが表示された初期画面」が表示される。
更新処理のイメージ
リストを下方向にスワイプするとインジケータが表示され、5行分のデータが約1秒間隔で1行ずつ追加される。
更新完了後のイメージ
5行分追加すると、表示されていたインジケータが非表示になる。
ソースコードと解説
サンプルアプリは、以下のファイルで構成されています。
(プロジェクト生成後から変更がないマニフェストファイルなどは、省略しています。)
ファイル一覧
レイアウト
- row.xml
- activity_main.xml
Activity
- MainActivity.kt
Gradle
- build.gradle(app)
Gradle
「Support Library v4」を導入するため、build.gradle(app)のdependenciesに下記の1行を追記します。
implementation 'com.android.support:support-v4:26.1.0'
ライブラリーを追加する際、次の事に注意してください。
他のSupport Libraryを導入している場合、必ずライブラリー同士のバージョンを一致させる必要があります。
この記事では、「Appcompat v7」も使用している為、dependenciesは以下のようになります。
dependencies {
・・・省略・・・
implementation 'com.android.support:appcompat-v7: 26.1.0 '
implementation 'com.android.support:support-v4:26.1.0'
}
レイアウト
最終的なレイアウトのイメージは、下図のとおりです。
row.xml
リストに表示する1行分のデザインのレイアウトファイルの内容は、以下のとおりです。
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" />
サンプルアプリは、リストの1行に対して1行の文字列のみを表示するため、TextViewだけを実装しています。
activity_main.xml
画面全体のレイアウトファイルの内容は、以下のとおりです。
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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"
tools:context="jp.co.casareal.swipetorefreshsample.MainActivity">
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/mySwipeRefreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="@android:id/list"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginBottom="8dp"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.v4.widget.SwipeRefreshLayout>
</android.support.constraint.ConstraintLayout>
SwipeRefreshLayoutを利用する際、以下の3つに注意点してください。
- ListViewもしくはGridViewのみが包括できる
- 包括するビューは必ず1つのみ
- 包括したビューのidは「@android:id/list」にする
この記事のサンプルアプリは、ListViewを利用してます。
また、SwipeRefreshLayoutのオブジェクトにリストが下方向にスワイプされたときのコールバックメソッドを設定するため、SwipeRefreshLayoutに任意のidを付与します。
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/mySwipeRefreshLayout"
・・・省略・・・>
<ListView
android:id="@android:id/list"
・・・省略・・・ />
</android.support.v4.widget.SwipeRefreshLayout>
Activity
サンプルアプリのアクティビティのソースコードは、以下のとおりです。
package jp.co.casareal.swipetorefreshsample
import android.os.AsyncTask
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.widget.ArrayAdapter
import kotlinx.android.synthetic.main.activity_main.*
import java.text.SimpleDateFormat
import java.util.*
class MainActivity : AppCompatActivity() {
// 内部クラス内でも参照できるように
// Adapterをプロパティとして宣言
var adapter: ArrayAdapter<String>? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// ListViewに設定するAdapterの生成
adapter = ArrayAdapter(this, R.layout.row, createData())
// ListViewにAdapterを設定
list.adapter = adapter
// リストを下にスワイプされたら更新処理を実行
mySwipeRefreshLayout.setOnRefreshListener {
MyTask().execute()
}
}
// 内部クラスとしてMyTaskを定義
// innerキーワードを付与している理由は、
// 内部クラス内でMainActivityクラスの参照を使用するため
inner class MyTask : AsyncTask<Void, String, Void>() {
// 5つのデータを生成後、1秒スリープしてから
// ListViewに反映させる
override fun doInBackground(vararg p0: Void?): Void? {
createData().forEach {
Thread.sleep(1000)
publishProgress(it)
}
return null
}
// データ追加の更新処理
override fun onProgressUpdate(vararg values: String?) {
values.forEach {
adapter?.add(it)
}
adapter?.notifyDataSetChanged()
}
// データの追加処理が修了後、インジケータを非表示にする
override fun onPostExecute(result: Void?) {
mySwipeRefreshLayout.isRefreshing = false
}
}
}
inline fun createData(): List<String> {
var mList = mutableListOf<String>()
val dateFormat = SimpleDateFormat("MM月dd日のhh時mm分ss秒SSSミリ秒に作成したデータ")
for (i in 0 until 5) {
Date(System.currentTimeMillis()).let {
mList.add(dateFormat.format(it))
}
Thread.sleep(20)
}
return mList
}
OnRefreshListenerインターフェイス
リストを下方向にスワイプすると、SwipeRefreshLayoutにリスナーとして登録されている
OnRefreshListenerインターフェイスのonRefreshメソッドが呼び出されます。
メソッドのシグニチャは、以下のとおりです。
void onRefresh();
OnRefreshListenerインターフェイスを実装したオブジェクトを、SwipeRefreshLayoutのオブジェクトのリスナーに設定します。
サンプルアプリは、ラムダ式で実装しています。
※ここでのmySwipeRefreshLayoutは、SwipeRefreshLayoutのオブジェクトです。
mySwipeRefreshLayout.setOnRefreshListener {・・・更新処理などを記述・・・}
インジケータを非表示にする
インジケータを非表示にするには、SwipeRefreshLayoutクラスのsetRefreshingメソッドの引数にfalseを渡します。1
Kotlinでは、isRefreshingプロパティにfalseを渡すことで実現します。isRefreshingプロパティにfalseを渡すことで、setRefreshingメソッドが内部的に呼び出されます。
実装例は、以下のとおりです。
mySwipeRefreshLayout.isRefreshing = false
まとめ
SwipeRefreshLayoutを利用することにより、比較的少ないコード量でリッチな機能が実装できます。
-
setRefreshingメソッドの引数にtrueを渡すと、インジケータが表示されます。 ↩