AndroidStudioでGoogleカレンダーを模したアプリを作成していたところ、問題発生。
スワイプで前後の日週月に移動させたいが、日のみ縦スクロールが優先されてスワイプできない
検索する限り様々対策されている方はいるようだが、自分のケースにあった解決法が見当たらなかったため、ChatGPTと相談しながら原因特定→問題解決を図った。が、なかなかいい解決方法が出てこないので、最終的には自分がこれ、と思った解決方法が当たった。
問題発生時の状況
メイン画面(MainActivity)で上部タブにより、月・週・日の表示が切り替え可能。日のみ縦にスクロールして24時間分を表示させる。(GoogleCalendarの上にタブがあると思ってください)
横スワイプでは前後の月・週・日付に移動させる(GestureDetectorを使用)が、日のみスワイプできない。
原因
ScrollViewが優先されてGestureDetectorが効かなくなっている。
改善策
スワイプ方向に応じてScrollViewを無効にする必要があるが、最初はこれをMainActivity.javaで実施しようとしていたが、うまくいかない。そこで、日の画面だけはMainActivity.javaでGestureDetectorのタッチリスナーを無効にし、日画面作成時にactivity_mainのコンテキストを引き継いだうえでGestureDetectorを実装。また、ScrollViewのタッチリスナーで横スワイプを無効に設定。
コード抜粋
文章では分かりづらいため、コードの抜粋を以下に示す。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
// GestureDetectorの設定
gestureDetector = new GestureDetector(this, new GestureListener());
// タブのクリックリスナー設定
dateTypeTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(@NonNull TabLayout.Tab tab) {
// タブが選択されたときに、タブの位置に応じて画面を表示
int tabPosition = tab.getPosition();
// タブのテキストを更新
updateTextViewBasedOnDate(showingDate);
// タブの位置に応じて適切なフラグメントを作成
displayFragmentForTab(tabPosition);
// タブがDayタブ以外の場合、スワイプジェスチャーを有効にする(DayのスワイプはDayFragmentで設定)
if (tabPosition != 2) { // Dayタブ以外が選択された場合
findViewById(R.id.toDoDisplay).setOnTouchListener((v, event) -> {
gestureDetector.onTouchEvent(event);
return true;
});
} else {
findViewById(R.id.toDoDisplay).setOnTouchListener(null);
}
}
@Override
public void onTabUnselected(@NonNull TabLayout.Tab tab) {
// タブが非選択になったときの処理(必要なら追加)
}
@Override
public void onTabReselected(@NonNull TabLayout.Tab tab) {
// タブが再選択されたときの処理(必要なら追加)
}
});
}
public void displayFragmentForTab(int tabPosition) {
Fragment fragment;
switch (tabPosition) {
case 0:
fragment = MonthFragment.newInstance(showingDate);
break;
case 1:
fragment = WeekFragment.newInstance(showingDate);
break;
case 2:
fragment = DayFragment.newInstance(showingDate);
break;
default:
fragment = DayFragment.newInstance(showingDate); // デフォルトのフラグメント
break;
}
// フラグメントを表示
getSupportFragmentManager().beginTransaction()
.replace(R.id.toDoDisplay, fragment)
.commit();
}
}
public class DayFragment extends Fragment {
private MainActivity mainActivity;
private static final String ARG_DATE = "showingDate";
private Date showingDate;
private TabLayout dateTypeTabLayout;
private GestureDetector gestureDetector;
public DayFragment() {
}
public static DayFragment newInstance(Date showingDate) {
DayFragment fragment = new DayFragment();
Bundle args = new Bundle();
args.putSerializable(ARG_DATE, showingDate);
fragment.setArguments(args);
return fragment;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof MainActivity) {
mainActivity = (MainActivity) context; // contextをMainActivityにキャスト
} else {
throw new RuntimeException(context.toString()
+ " must be an instance of MainActivity");
}
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
showingDate = (Date) getArguments().getSerializable(ARG_DATE);
}
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_day, container, false);
gestureDetector = new GestureDetector(getContext(), mainActivity.new GestureListener());
ScrollView scrollView = rootView.findViewById(R.id.dayScrollView);
// ScrollView に OnTouchListener を設定
scrollView.setOnTouchListener((v, event) -> {
if (gestureDetector.onTouchEvent(event)) {
// 横スワイプを検出した場合、ScrollView のスクロールを無効にする
return true;
} else {
// 縦スワイプの場合、ScrollView のデフォルトのスクロールを許可する
return v.onTouchEvent(event);
}
});
return rootView;
}
}