質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.10%

viewPagerでのタブ切替時にRecyclerViewを表示し直したい

解決済

回答 3

投稿

  • 評価
  • クリップ 0
  • VIEW 80

jun74

score 184

前提・実現したいこと

viewPagerでタブ切替を行い、Fragmentを切り替える画面があります。
それぞれにリサイクルビューがあります。
タブの切替えではFragmentの「onResume」は実行されません。

viewPagerでタブを切替時に「SoundAdapter.mPlayStop()」で音楽を停止し、「notifyDataSetChanged()」で「onBindViewHolder」の「再生ボタン設定」を行いたいです。
しかし、staticでは「notifyDataSetChanged()」は呼べないとエラーになります。

タブ切替時に「再生ボタン設定」を行う方法は何か無いでしょうか?

viewPagerでのタブ切替時にRecyclerViewを表示し直す、または親のFragmentの表示のし直しでも良いので何か方法があれば、よろしくお願いいたします。

該当のソースコード

MainActivity.java

        //ビューページャータブ切替判定
        viewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
            //ビューページャータブ選択判定
            @Override
            public void onPageSelected(int position) {
                //ミュージックプレイヤー解放
                SoundAdapter.mPlayStop();
            }
        });

SoundAdapter.java(リサイクルビューアダプター)

    @Override
    public void onBindViewHolder(final ItemViewHolder holder, final int position) {
        onBind = true;

        //行の値設定
        holder.rb_sound.setChecked(soundList.get(position).m_rb_sound);
        holder.t_sound_name.setText(soundList.get(position).m_t_sound_name);

        //再生ボタン設定
        if(playFlg){
            if(playPosition == position){
                holder.t_play.setText(R.string.playStop_name);
            }else{
                holder.t_play.setText(R.string.play_name);
            }
        }else{
            holder.t_play.setText(R.string.play_name);
        }

        onBind = false;
    }

    //ミュージックプレイヤー解放
    public static void mPlayStop() {
        if(playFlg){
            // アラーム音が再生されていれば停止する
            if(mPlayer.isPlaying()) {
                mPlayer.stop();  // 停止
            }

            // フォーカスを明け渡す(他のアプリの音楽を停止を解除)
            adm.abandonAudioFocus(afChangeListener);

            // メディアプレイヤー を解放する
            if(mPlayer != null){
                mPlayer.reset();
                mPlayer.release();
                mPlayer = null;
            }

            playFlg = false;
        }
        //リサイクルビュー更新
        notifyDataSetChanged();  //staticだと駄目となる!!
    }

補足情報(FW/ツールのバージョンなど)

Android Studio3.4
APIレベル14から28まで対象

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • jimbe

    2019/10/10 03:41

    SoundAdapter の全文をご提示願えますでしょうか.

    キャンセル

回答 3

checkベストアンサー

+1

androidx.fragment:fragment:1.1.0-alpha07 以降をお使いであれば、FragmentPagerAdapterもしくはFragmentStatePagerAdapterの第2引数にBEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENTを渡すことでページの切替時にアクティブなページのonResumeメソッドが呼ばれるようになります。

それ以前のものをお使いであれば、setUserVisibleHintが代わりに呼ばれるので使えるはずです。

参考記事: setUserVisibleHintのdeprecatedとsetMaxLifecycle


余談ですが、オブジェクト間の通知のためだけにメソッドをstaticで宣言するのはオブジェクト指向の考え方に反するアンチパターンなので避けた方がいいと思います。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/10/10 22:37

    いつもありがとうございます!
    androidxにはまだしてないので、setUserVisibleHintで対応出来ました。
    staticは避けた方が良いですよね。。
    SoundAdapter.mPlayStop()がstaticで残ってしまい悩み中です。。

    キャンセル

+1

ご提示されたコードが一部のため, 想像でコードを修正してみました.
単一の PlayerManager インスタンスを必要な個所で使用している他, play/stop 時には PlayerManager.PlayerListener によって通知し, SoundAdapter はそれを受けて行の特定・notify(再表示) しています.

SoundAdapter は過去のご質問のコードから拝借しています.
SoundData の m_sound の型が分からなかったので, Uri としました.
Globals による context の取得も, 普通に上位からの指定にしました.

コードを切り貼りしただけで, 動作は確認していません

MainActivity(一部)

    final PlayerManager playerManager = new PlayerManager(this);

    List<SoundData> alarmList = new ArrayList<>(); //データの設定は省略
    SoundAdapter alarmAdapter = new SoundAdapter(this, playerManager, alarmList);
    //alarmAdapter は alarmFragment の RecyclerView に設定

    List<SoundData> musicList = new ArrayList<>(); //データの設定は省略
    SoundAdapter musicAdapter = new SoundAdapter(this, playerManager, musicList);
    //musicAdapter は musicFragment の RecyclerView に設定

    //ビューページャータブ切替判定
    viewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
      //ビューページャータブ選択判定
      @Override
      public void onPageSelected(int position) {
        //ミュージックプレイヤー解放
        try {
          playerManager.stop();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    });


SoundAdapter(一部想像)

//サウンドリサイクルビューアダプター
public class SoundAdapter extends RecyclerView.Adapter<SoundAdapter.ItemViewHolder> implements PlayerManager.PlayerListener {
  //使途不明
  private boolean onBind;
  int alarmType;
  String alarmSound;
  String musicSound;
  int alarmPosition;
  int musicPosition;
  int chekPosition;
  int playPosition;

  private PlayerManager playerManager;
  private List<SoundData> soundList;

  @Override
  public void onPlay(Uri sound) {
    int index = search(sound);
    if(index >= 0) notifyItemChanged(index);
  }
  @Override
  public void onStop(Uri sound) {
    int index = search(sound);
    if(index >= 0) notifyItemChanged(index);
  }
  private int search(Uri sound) {
    for(int i=0; i<soundList.size(); i++) if(soundList.get(i).m_sound.equals(sound)) return i;
    return -1;
  }

  static class ItemViewHolder extends RecyclerView.ViewHolder {
    LinearLayout row_layout;
    RadioButton rb_sound;
    TextView t_sound_name;
    TextView t_play;
    ItemViewHolder(View v){
      super(v);
      row_layout = (LinearLayout)v.findViewById(R.id.row_layout);
      rb_sound = (RadioButton) v.findViewById(R.id.rb_sound);
      t_sound_name = (TextView)v.findViewById(R.id.t_sound_name);
      t_play = (TextView)v.findViewById(R.id.t_play);
    }
  }

  SoundAdapter(Context context, PlayerManager playerManager, List<SoundData> soundList){
    this.playerManager = playerManager;
    this.soundList = soundList;

    //設定画面初期値設定
    SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);

    //アラームタイプ取得
    alarmType = sharedPreferences.getInt("alarmType", 0);
    //アラーム音取得
    alarmSound = sharedPreferences.getString("alarmSound", "");
    //ミュージック音取得
    musicSound = sharedPreferences.getString("musicSound", "");
    //アラームポジション取得
    alarmPosition = sharedPreferences.getInt("alarmPosition", 0);
    //ミュージックポジション取得
    musicPosition = sharedPreferences.getInt("musicPosition", 0);

    playerManager.addPlayerListener(this);
  }

  @Override
  public ItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType){
    View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.sound_row, parent, false);
    return new ItemViewHolder(v);
  }

  @Override
  public void onBindViewHolder(final ItemViewHolder holder, final int position) {
    onBind = true;

    //行の値設定
    holder.rb_sound.setChecked(soundList.get(position).m_rb_sound);
    holder.t_sound_name.setText(soundList.get(position).m_t_sound_name);

    //再生ボタン設定
    if(soundList.get(position).m_sound.equals(playerManager.getLastPlayedSound())){
      holder.t_play.setText(R.string.playStop_name);
    }else{
      holder.t_play.setText(R.string.play_name);
    }

    //再生ボタンクリック
    holder.t_play.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        //アラームタイプ判定
        if(alarmType == 0){
          chekPosition = alarmPosition;
        }else{
          chekPosition = musicPosition;
        }
        //再生、停止処理
        try {
          if(soundList.get(position).m_sound.equals(playerManager.getLastPlayedSound())) {
            playerManager.stop();
          } else {
            playerManager.play(soundList.get(position).m_sound);
          }
        } catch (IOException e) {
          e.printStackTrace();
        }
        //再生ポジション設定
        playPosition = position;
      }
    });

    onBind = false;
  }

  @Override
  public int getItemCount() {
    return soundList.size();
  }
}


PlayerManager

public class PlayerManager {
  private Context context;
  private MediaPlayer mediaPlayer;
  private AudioManager audioManager;
  private AudioManager.OnAudioFocusChangeListener afChangeListener;

  private Uri lastPlayedSound;

  interface PlayerListener {
    void onPlay(Uri path);
    void onStop(Uri path);
  }
  private List<PlayerListener> playerListenerList = new ArrayList<>();

  public void addPlayerListener(PlayerListener l) {
    playerListenerList.add(l);
  }
  public void removePlayerListener(PlayerListener l) {
    playerListenerList.remove(l);
  }
  protected void firePlay(Uri path) {
    for(PlayerListener l : playerListenerList) l.onPlay(path);
  }
  protected void fireStop(Uri path) {
    for(PlayerListener l : playerListenerList) l.onStop(path);
  }

  public PlayerManager(Context context) {
    this.context = context;

    // AudioManagerを取得する
    audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);

    //オーディオフォーカスのコールバック
    afChangeListener = new AudioManager.OnAudioFocusChangeListener() {
      public void onAudioFocusChange(int focusChange) {
        //フォーカスを完全に失ったら
        if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
        } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {//一時的なフォーカスロスト
        } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {//通知音とかによるフォーカスロスト(ボリュームを下げて再生し続けるべき)
        } else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {//フォーカスを再度得た場合
        }
      }
    };
  }

  //再生処理
  public void play(Uri sound) throws IOException {
    if(sound == null) throw new IllegalArgumentException();
    if (mediaPlayer != null) stop();

    // 現在のメディア音量を表示する
    // 現在の音量を取得する
    int ringVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
    // 音量を設定する
    audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, ringVolume, AudioManager.FLAG_SHOW_UI);

    // フォーカスを排他的に奪う(他のアプリの音楽を停止)
    audioManager.requestAudioFocus(afChangeListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE);

    mediaPlayer = new MediaPlayer();

    //アラーム音設定
    mediaPlayer.setDataSource(context, sound);    // 音声を設定
    mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); // ミュージックボリュームで再生
    mediaPlayer.setLooping(true);                              // ループ再生を設定
    mediaPlayer.prepare();                                     // 音声を読み込み

    //アラーム音再生
    mediaPlayer.start(); // 再生

    lastPlayedSound = sound;
    firePlay(sound);
  }

  //停止処理
  public void stop() throws IOException {
    if(mediaPlayer == null) return;

    // フォーカスを明け渡す(他のアプリの音楽停止を解除)
    audioManager.abandonAudioFocus(afChangeListener);

    // アラーム音が再生されていれば停止する
    if (mediaPlayer.isPlaying()) {
      mediaPlayer.stop();  // 停止
    }

    // メディアプレイヤー を解放する
    mediaPlayer.reset();
    mediaPlayer.release();
    mediaPlayer = null;

    Uri sound = lastPlayedSound;
    lastPlayedSound = null;
    fireStop(sound);
  }

  public Uri getLastPlayedSound() {
    return lastPlayedSound;
  }
}

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/10/12 19:41

    反応が遅れて申し訳ありません。
    只今、無事インスタンスの対応完了しました!
    ありがとうございました。

    キャンセル

0

それぞれのFragmentにRecyclerViewを再度表示し直す処理を作り、解決しました。
もう少しスマートな解決方法がありそうな気もするので解決済にはしないで回答募集を継続します。

MainActivity.java

        //ビューページャータブ切替判定
        viewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
            //ビューページャータブ選択判定
            @Override
            public void onPageSelected(int position) {
                //ミュージックプレイヤー解放
                SoundAdapter.mPlayStop();

                if(position == 0){
                    //アラーム音選択フラグメントのリサイクルビュー再描写
                    AlarmFragment.reRecyclerView();
                }else{
                    //音楽音選択フラグメントのリサイクルビュー再描写
                    MusicFragment.reRecyclerView();
                }
            }
        });

AlarmFragment.java

    //リサイクルビュー再描写
    public static void reRecyclerView() {
        //アラームポジション取得
        alarmPosition = sharedPreferences.getInt("alarmPosition", 0);
        //ラジオボタンチェック位置へ移動
        mRecyclerView.scrollToPosition(alarmPosition);
        //リサイクルビュー表示
        mRecyclerView.setAdapter(mAdapter);
    }

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/10/10 12:45

    SoundAdapter が, RecyclerView へのデータ管理のほかに, プレイヤーの動作まで(static で)管理しているのが原因と思います.

    プレイヤーの管理を別クラス(例えば PlayManager クラス)とし, そのオブジェクトへの参照をアダプタが持って play/stop の依頼( play(String name) 等)をすると同時に, 新たにリスナ(例えば PlayManager.PlayListener )を定義してアダプタに実装・登録し, 再生・停止毎に情報(例えば再生/停止した名前)をアダプタに通知するようにすれば, アダプタ側はその情報からどの行のラジオボタンの表示変更が必要かが分かり, 自身で notify を発行することが出来るのではないでしょうか.

    キャンセル

  • 2019/10/10 22:42

    いつもありがとうございます。
    アダプタで再生、停止が基本なのですが、Activity(現状はFragmentに変わりました)からも停止するタイミングがあり、そこの解消方法(static以外での呼び出し)が浮かびません。。

    キャンセル

  • 2019/10/10 23:43

    SoundAdapter の2つのインスタンスをそれぞれ AlarmFragment 上のRecyclerView と MusicFragment 上の RecyclerView にアダプタとして設定しているために プレイヤー関係を static にしているものと推測しています.
    であれば, プレイヤー関係を統括するクラスを新たに作ってインスタンスを1つ用意し, それを各 SoundAdapter や Fragment? から使用すればよいかと思います.

    キャンセル

  • 2019/10/11 00:05

    ありがとうございます!
    SoundAdapterでプレイヤー制御しているのですが、AlarmFragmentやMusicFragmentが非表示になった瞬間にプレイヤーを停止したくてstaticにしています。
    プレイヤーを統括するクラスを作ってインスタンスを用意して使用するようにしてみようと思います。
    https://www.sejuku.net/blog/50043
    上記より、aというインスタンスにplayというメソッド、stopというメソッドを作り、SoundAdapterで再生、AlarmFragmentで停止と出来るなら良さげです。
    明日にでも試してみようと思います。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.10%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる