ActionBar-PullToRefreshをメニューボタン等で連動させる

2014-02-13T00:00:00+00:00 Android Java

っていうActionBar-PullToRefreshを使っている場合において、メニューでリフレッシュボタンとかでActionBar-PullToRefreshを発生させるような要件を実現したい場合にはどうすれば良いのか。簡単に言えば上記画像中の右上にあるリフレッシュボタンをクリックする事でonRefreshStartedメソッドを発生させたいっていう所

っていうのをやってみた

SampleListFragment.java

package sample.test;

import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;

import com.actionbarsherlock.app.SherlockListFragment;

import uk.co.senab.actionbarpulltorefresh.extras.actionbarsherlock.PullToRefreshLayout;
import uk.co.senab.actionbarpulltorefresh.library.ActionBarPullToRefresh;
import uk.co.senab.actionbarpulltorefresh.library.listeners.OnRefreshListener;

public class SampleListFragment extends SherlockListFragment
    implements OnRefreshListener {

    private static final String TAG = SampleListFragment.class.getName();

    private final Handler mHandler = new Handler();
    private ArrayAdapter<String> mAdapter;
    private PullToRefreshLayout mPullToRefreshLayout;

    @Override
    public void onActivityCreated(Bundle bundle) {
        super.onActivityCreated(bundle);
        Log.v(TAG, "onActivityCreated");
        counter++;

        mAdapter = new ArrayAdapter<String>(
            getActivity(),
            android.R.layout.simple_list_item_1
        );
        setListAdapter(mAdapter);
    }

    @Override
    public void onViewCreated(View view, Bundle bundle) {
        super.onViewCreated(view, bundle);
        Log.v(TAG, "onViewCreated");
        mPullToRefreshLayout = new PullToRefreshLayout(view.getContext());

        ActionBarPullToRefresh.from(getActivity())
            .insertLayoutInto((ViewGroup) view)
            .theseChildrenArePullable(getListView(), getListView().getEmptyView())
            .listener(this)
            .setup(mPullToRefreshLayout);
    }

    @Override
    public void onRefreshStarted(View view) {
        Log.v(TAG, "onRefreshStarted: " + view);

        mHandler.postDelayed(
            new Thread() {
                @Override
                public void run() {
                    if (mPullToRefreshLayout.isRefreshing())
                        mPullToRefreshLayout.setRefreshComplete();
                }
            },
            5000
        );
    }

    // PullToRefreshLayoutを取得できるようにメソッド追加
    PullToRefreshLayout getPullToRefresh() {
        return mPullToRefreshLayout;
    }
}

MainActivity.java

恐らくは現状だとActionBar-PullToRefreshのリロード処理を促せるようなAPIが無い(てか見つけられなかった)。正確にはあるんだけれどもパブリックでは無い、なのでリフレクションという力技を使ってPullToRefreshAttacher?のstartRefreshメソッドを実行すれば良い模様

package sample.test;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.view.View;

import com.actionbarsherlock.app.SherlockFragmentActivity;
import com.actionbarsherlock.view.Menu;
import com.actionbarsherlock.view.MenuItem;

import uk.co.senab.actionbarpulltorefresh.library.PullToRefreshAttacher;
import uk.co.senab.actionbarpulltorefresh.library.PullToRefreshLayout;

public class MainActivity extends SherlockFragmentActivity {

    @Override
    protected void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        setContentView(R.layout.activity_main);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getSupportMenuInflater().inflate(R.menu.menu, menu);
        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (item.getItemId() == R.id.menu_refresh) {
            FragmentManager fm = getSupportFragmentManager();
            Fragment fragment = fm.findFragmentByTag(
                // R.string.sample_list_fragment_tagは"sample_list_fragment"
                // <fragment android:tag="@string/sample_list_fragment_tag">が必要
                getString(R.string.sample_list_fragment_tag)
            );

            if (fragment instanceof SampleListFragment)
                startRefresh((SampleListFragment)fragment);
        }

        return super.onOptionsItemSelected(item);
    }

    private void startRefresh(SampleListFragment fragment) {
        PullToRefreshLayout pullToRefreshLayout = fragment.getPullToRefresh();

        try {
            Field f = PullToRefreshLayout.class.getDeclaredField("mPullToRefreshAttacher");
            f.setAccessible(true);

            PullToRefreshAttacher attacher = (PullToRefreshAttacher)f.get(pullToRefreshLayout);
            Method m = PullToRefreshAttacher.class.getDeclaredMethod("startRefresh", View.class, boolean.class);
            m.setAccessible(true);
            m.invoke(attacher, new Object[] { fragment.getListView(), true });
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}

てな感じでPullToRefreshLayoutなインスタンスからPullToRefreshAttacherなインスタンスを取って、そのプライベートメソッド?なstartRefreshメソッドをリフレクションを使ってコールするっていうただそれだけ

但し、onRefreshStartedメソッドの引数に指定されるandroid.view.ViewのインスタンスはListFragmentを使っているのであればListViewになる模様。ListFragmentを使うケースであればそうだと思うけど、そうじゃないケースには別のView実装が指定されるので(ry

検証動画

{% youtube nn89-8Lpoqo %}

gulpでmocha-phantomjs+jscoverage gradle-android-toolkitのallprojects