もやもやエンジニア

IT系のネタで思ったことや技術系のネタを備忘録的に綴っていきます。フロント率高め。

AndroidでViewPager配下のFragment間でViewの状態を共有する

概要

タイトルだけだと何のこっちゃという話ですが、例えばTwitterとかでフォローするボタンがあるじゃないですか。で、ViewPager管理下の各Fragmentにフォローするボタンを置いてどこかでフォローするを押したら、押したページ以外にも押された結果を反映させたいという話です。ViewPagerを使っていなければデータを引き回すなりしてonCreateViewあたりでデータに合わせてViewを編集すればいいのですが、ViewPagerを使ってるとスワイプする時に今見ているページと次に表示するページの両方を作るので、1ページ目でフォローする→スワイプで2ページ目に行く、というアクションをすると2ページ目はすでに1ページ目の表示時に作成済なのでonCreateViewを通らずに未フォローの状態のままになってしまいます。

作ってみた

んで、Observerパターンを使うとすっきり作ることができました。フォロアーリストのModelを作ってそいつのObserverとして各フラグメントを登録することでデータが変更されたらObserverとして観測中のFragmentは変更の通知をキャッチしてViewを変更するようにします。

ActivityとPagerAdaptor

デフォルトで作成されるPager付きのActivityからいらないものを削ってFragmentを差し替えただけです。

PagerSampleActivity.java

package me.rei_m.androidsample.activitiy;

import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.content.Context;
import android.content.Intent;
import android.support.v13.app.FragmentPagerAdapter;
import android.os.Bundle;

import android.support.v4.view.ViewPager;

import me.rei_m.androidsample.R;
import me.rei_m.androidsample.fragment.PagerItemFragment;

public class PagerSampleActivity extends Activity {

    public static Intent createIntent(Context context) {
        return new Intent(context, PagerSampleActivity.class);
    }

    SectionsPagerAdapter mSectionsPagerAdapter;

    ViewPager mViewPager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_pager_sample);

        mSectionsPagerAdapter = new SectionsPagerAdapter(getFragmentManager());

        mViewPager = (ViewPager) findViewById(R.id.pager);
        mViewPager.setAdapter(mSectionsPagerAdapter);
    }

    public class SectionsPagerAdapter extends FragmentPagerAdapter {
        public SectionsPagerAdapter(FragmentManager fm) {
            super(fm);
        }
        @Override
        public Fragment getItem(int position) {
            return PagerItemFragment.newInstance(position);
        }
        @Override
        public int getCount() {
            return 4;
        }
        @Override
        public CharSequence getPageTitle(int position) {
            return String.valueOf(position) + "枚目";
        }
    }
}

Fragment

フラグメントはこんな見た目でページ番号が変わる以外は全部同じです。

f:id:Rei19:20150212093251p:plain

PagerItemFragment.java

package me.rei_m.androidsample.fragment;

import android.app.Fragment;
import android.os.Bundle;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;

import java.util.Observable;
import java.util.Observer;

import me.rei_m.androidsample.R;
import me.rei_m.androidsample.entity.FollowerEntity;
import me.rei_m.androidsample.model.FollowerListModel;
import me.rei_m.androidsample.model.ModelLocator;

public class PagerItemFragment extends Fragment implements Observer, View.OnClickListener {
    // Modelからの通知を受け取る必要があるのでObserverを実装する

    private static final String ARG_PAGE_NO = "pageNo";

    public static PagerItemFragment newInstance(int pageNo) {
        Bundle args = new Bundle();
        args.putInt(ARG_PAGE_NO, pageNo);

        PagerItemFragment fragment = new PagerItemFragment();
        fragment.setArguments(args);

        return fragment;
    }

    private FollowerEntity mUser;
    private int mPageNo;
    private TextView mTextView;
    private Button mButton;

    private FollowerListModel mFollowerListModel;

    public PagerItemFragment() {
        // Required empty public constructor
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // ページ番号を取得
        mPageNo = getArguments().getInt(ARG_PAGE_NO);

        // ModelのObserverにインスタンスを登録
        mFollowerListModel = ModelLocator.getInstance().getFollowerListModel();
        mFollowerListModel.addObserver(this);

        // 仮のユーザを設定
        mUser = new FollowerEntity();
        mUser.setId("0000001");
        mUser.setName("ほげー");
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {

        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.fragment_pager_item, container, false);

        // TextViewとButtonを取得
        mTextView = (TextView) view.findViewById(R.id.text_pager_item_title);
        mTextView.setText(String.valueOf(mPageNo));
        mButton = (Button) view.findViewById(R.id.button_pager_item);
        mButton.setOnClickListener(this);

        // 初期表示の設定。対象のユーザーをフォロー済かどうかで表示を変える
        if(mFollowerListModel.hasFollower(mUser)){
            setFollowedStyle();
        }else{
            setUnFollowedStyle();
        }

        return view;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        // ModelのObserverからインスタンスを解除
        mFollowerListModel.deleteObserver(this);
    }

    @Override
    public void onDetach() {
        super.onDetach();
        mTextView = null;
        mButton = null;
        mFollowerListModel = null;
        mUser = null;
    }

    @Override
    public void update(Observable observable, Object data) {
        // Modelからフォロアーリストの更新通知が来たら走る処理
        if(data instanceof FollowerListModel.ChangeFollowerListEvent){
            FollowerListModel.ChangeFollowerListEvent event = (FollowerListModel.ChangeFollowerListEvent) data;
            // フォローアクションの状態によって表示を変える
            if(event.getIsAdded()){
                setFollowedStyle();
            }else{
                setUnFollowedStyle();
            }
        }
    }

    @Override
    public void onClick(View v) {

        switch (v.getId()){
            case R.id.button_pager_item:
                // フォロアーリストを確認して、追加済であれば削除、未追加であれば追加
                // ボタンのイベントではViewの表示は変えない
                if(mFollowerListModel.hasFollower(mUser)){
                    mFollowerListModel.removeFollower(mUser);
                }else{
                    mFollowerListModel.addFollower(mUser);
                }
                break;
        }
    }

    // フォロー済の状態に表示を変更する
    private void setFollowedStyle(){
        mButton.setText(mUser.getName() + "をふぉろーしてるよ");
        mTextView.setBackgroundColor(getResources().getColor(R.color.active));
    }

    // 未フォローの状態の表示を変更する
    private void setUnFollowedStyle(){
        mButton.setText(mUser.getName() + "はまだふぉろーしてないよ");
        mTextView.setBackground(null);
    }
}

ModelとEntity

Obserbleを実装したModelを作ってデータに変更があったら更新の通知を送るように実装します。Entitiyは適当。

FollowerListModel.java

package me.rei_m.androidsample.model;

import java.util.EventObject;
import java.util.HashMap;
import java.util.Observable;

import me.rei_m.androidsample.entity.FollowerEntity;

public class FollowerListModel extends Observable{

    public static FollowerListModel createInstance(){

        FollowerListModel instance = new FollowerListModel();
        instance.mFollowerList = new HashMap<>();

        return instance;
    }

    private FollowerListModel(){}

    private HashMap<String, FollowerEntity> mFollowerList;

    public HashMap<String, FollowerEntity> getFollowerList() {
        return mFollowerList;
    }

    // フォロー済か確認する
    public boolean hasFollower(FollowerEntity target){
        return mFollowerList.containsKey(target.getId());
    }

    // フォロアーリストに追加する
    public void addFollower(FollowerEntity target){
        mFollowerList.put(target.getId(), target);
        notifyEvent(true);
    }

    // フォロアーリストから削除する
    public void removeFollower(FollowerEntity target){
        mFollowerList.remove(target.getId());
        notifyEvent(false);
    }

    // フォロアーリストの更新通知をObserverに対して送る
    private void notifyEvent(boolean isAdded){
        ChangeFollowerListEvent event = new ChangeFollowerListEvent(this);
        event.setIsAdded(isAdded);
        setChanged();
        notifyObservers(event);
    }

    // 通知用のEventObject
    public static class ChangeFollowerListEvent extends EventObject {
        public ChangeFollowerListEvent(Object source){
            super(source);
        }
        private boolean isAdded;

        public boolean getIsAdded() {
            return isAdded;
        }

        public void setIsAdded(boolean isAdded) {
            this.isAdded = isAdded;
        }
    }

}

FollowerEntity.java (適当。IDと名前だけ持ってる。)

package me.rei_m.androidsample.entity;

import java.io.Serializable;

public class FollowerEntity implements Serializable {

    private String id;
    private String name;

    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

こんな感じでしょうか。これで1ページ目でフォローする→スワイプで2ページ目に行くとやっても2ページ目はちゃんとフォロー済の表示になりました。変なとこがあればご指摘ください〜

コードはこちら

rei-m/androidSample · GitHub