SwipeRefresh+AsyncTaskLoader の使い方

SwipeRefreshLayout を使うと、引っ張って更新することができるようになります。様々なアプリで見られる基本的な機能で、これ自体は簡単に実装できます。

今回は、ローダと一緒に使うところで少し詰まってしまったので (というかローダの再利用のところをいまいち理解していなかったので)、SwipeRefreshLayout で更新したときに AsyncTaskLoader を再利用し、CustomAdapter (ArrayAdapter) を使って ListView に反映するところまでをやりたいと思います。

スポンサーリンク

SwipeRefreshLayout の実装

Gradle

dependencies {
    implementation 'com.android.support:support-v4:21.0.0'
}

予め build.gradle にサポートライブラリを読み込んでおきます。バージョンのところは適宜合わせてください。

以前は compile だったようですが、廃止されて現在は implementation となっています。また、バージョンのところで 21.0.+ と書くと (記憶の限りでは) 怒られるので、厳密に指定する必要があるっぽいです。

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

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()

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 を更新する処理を書きます。

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) になるような実装をすれば、引っ張らずとも更新できそうです。

(参考)

タイトルとURLをコピーしました