SwipeRefresh+AsyncTaskLoaderの使い方
SwipeRefreshLayout を使うと,引っ張って更新することができるようになります.様々なアプリで見られる基本的な機能で,これ自体は簡単に実装できます.
今回は,ローダと一緒に使うところで少し詰まってしまったので (というかローダの再利用のところをいまいち理解していなかったので),SwipeRefreshLayout で更新したときに AsyncTaskLoader を再利用し,CustomAdapter (ArrayAdapter) を使って ListView に反映するところまでをやりたいと思います.
SwipeRefreshLayout の実装
Gradle
build.gradle
dependencies {
implementation 'com.android.support:support-v4:21.0.0'
}
予め build.gradle にサポートライブラリを読み込んでおきます.バージョンのところは適宜合わせてください.
以前は compile
だったようですが,廃止されて現在は implementation
となっています.また,バージョンのところで 21.0.+
と書くと (記憶の限りでは) 怒られるので,厳密に指定する必要があるっぽいです.
XML
list.xml
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/swipe_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" />
</android.support.v4.widget.SwipeRefreshLayout>
SwipeRefreshLayout
は内部に ListView
か GridView
を一つだけ持つことができます.
Java
Fragment.java
private SwipeRefreshLayout mSwipeRefresh;
...
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.list, container, false);
mSwipeLayout = rootView.findViewById(R.id.swipe_layout);
// アニメーションの矢印の色を変更できる (デフォルトは黒)
mSwipeLayout.setColorSchemeResources(R.color.red, R.color.yellow, R.color.green, R.color.blue)
mSwipeLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
loadData(true);
}
})
}
TabLayout
を使ったので Fragment
で作っていますが,Activity
でも大して変わらないと思います.
いくつかのサイトで紹介されていた setColorScheme()
は既に廃止されていて,ID で指定する場合は setColorSchemeResources()
を使います.使いたい色は res > values ディレクトリの color.xml に 16 進数の RGB で記述しておく必要があります.
更新時の動作は onRefresh()
内に記述します.
AsyncTaskLoader の処理
ローダ側で既に loadInBackground()
は実装しているものとします.ネットワークに繋いでニュース記事を取得するなど,遅延が発生する処理はここに書いておきます.
メインスレッドでやろうとするとコンパイルエラーかなんかで弾かれます.
restartLoader()
Fragment.java
private void loadData(boolean b) {
LoaderManager loaderManger = getLoaderManager();
if (!b) {
loaderManager.initLoader(LOADER_ID, null, this);
} else {
loaderManager.restartLoader(LOADER_ID, null, this);
}
}
ローダを再利用するときに同一の ID を指定して restartLoader()
を使うと,onCreateLoader()
が呼ばれます.initLoader()
は ID に対して一回しか使えません.二回目以降は onCreateLoader()
が呼ばれないからです.
ArrayAdapter と ListView の処理
onCreateLoader
で return new CustomLoader()
し,処理が終わると onLoadFinish()
が呼ばれるので,そこに ListView
を更新する処理を書きます.
Fragment.java
private CustomAdapter mAdapter;
...
@Override public View onCreateView(...) {
...
listView = rootView.findViewById(R.id.list_view)
mAdapter = new CustomAdapter(getActivity(), new ArrayList<D>());
listView.setAdapter(mAdapter);
}
...
@Override
public void onLoadFinished(Loader<List<D>> loader, List<D> items) {
mAdapter.clear();
if (items != null && !items.isEmpty()) {
mAdapter.addAll(items);
}
mAdapter.notifyDataSetChanged();
mSwipeLayout.setRefreshing(false);
}
CustomAdapter
は ArrayAdapter
クラスを継承しています.
notifyDataSetChanged()
がないと ListView
が更新されないという話が合ったので書いてますが,実際どうなのかはわかりません.これはアダプタに対して clear()
や add()
してないと呼ぶことができないようです.
setRefreshing(false)
を呼ぶことで,アニメーションが終了します.これがないと,更新が終わっても回り続けます.更新ボタンを押したときに setRefreshing(true)
になるような実装をすれば,引っ張らずとも更新できそうです.