androidx.viewpager2.widget.ViewPager2はすごいです。
こういう画面の下部のナビゲーションに応答して切りかえを行う、上の部分のコンテナです。
アダプターを用いていて、その実装として、切り替えた先それぞれがフラグメントとなっています。
NavHostFragmentを用いることで、階層構造も実現できますね。
(画像は、現在開発中の次期バージョンです)

レイアウトXMLから見ていきましょう。
activity_main.xml<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/drawerLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".MainActivity">
<com.google.android.material.navigation.NavigationView
android:id="@+id/navSideView"
android:layout_width="100dp"
android:layout_height="0dp"
android:layout_gravity="start"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:menu="@menu/bottom_nav_menu" />
<!-- app:layout_constraintTop_toBottomOf="@+id/mainActivityToolbar" -->
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/myViewPager"
android:layout_width="0dp"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="@id/navBottomView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/navSideView"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/navBottomView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:menu="@menu/bottom_nav_menu" />
</androidx.constraintlayout.widget.ConstraintLayout>
navSideView – 画面が横長の場合のみ表示するナビ
クラス:com.google.android.material.navigation.NavigationView
navBottomView – 画面が縦長の場合のみ表示するナビ
クラス:com.google.android.material.bottomnavigation.BottomNavigationView
myViewPager – ナビによって制御される部分
クラス:androidx.viewpager2.widget.ViewPager2
縦長の場合画面下部に、横長の場合画面左にナビを配置する予定です。
そのため、AppBarLayoutをネストしています。
MyPagerAdapter _pagerAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
_pagerAdapter = new MyPagerAdapter(this);
binding.myViewPager.setUserInputEnabled(false); //アダプターがスワイプに反応しないようにする
binding.myViewPager.setAdapter(_pagerAdapter);
}
@Override
protected void onResume() {
super.onResume();
binding.navBottomView.setLabelVisibilityMode(BottomNavigationView.LABEL_VISIBILITY_LABELED);
binding.navBottomView.setItemIconTintList(null);
binding.navSideView.setItemIconTintList(null);
binding.myViewPager.setUserInputEnabled(false);
Menu menu = binding.navBottomView.getMenu();
binding.navBottomView.setOnItemSelectedListener(item -> {
for (int i = 0; i < menu.size(); ++ i) {
if (menu.getItem(i).getItemId() == item.getItemId()) {
binding.myViewPager.setCurrentItem(i, true);
break;
}
}
return true;
});
binding.navSideView.setNavigationItemSelectedListener(item -> {
for (int i = 0; i < menu.size(); ++ i) {
if (menu.getItem(i).getItemId() == item.getItemId()) {
binding.myViewPager.setCurrentItem(i, true);
break;
}
}
return true;
});
}
メニューXMLの宣言はたとえばこんな感じでいかがでしょうか。
bottom_nav_menu.xml<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/navigation_pad_2"
android:icon="@drawable/icon_pad_2"
android:title="@string/title_control" />
<item
android:id="@+id/navigation_piano_2"
android:icon="@drawable/icon_piano_2"
android:title="@string/title_piano" />
<item
android:id="@+id/navigation_midi_2"
android:icon="@drawable/icon_midi_2"
android:title="@string/title_midi" />
<item
android:id="@+id/navigation_config_2"
android:icon="@drawable/ic_config"
android:title="@string/title_config" />
<item
android:id="@+id/navigation_sponsor_2"
android:icon="@drawable/ic_launcher"
android:title="@string/title_support" />
</menu>
BottomNavigationViewの、app:menu=”@menu/bottom_nav_menu” で指定しています。
MyPagerAdapterにも同じ数だけ指定があります。
PagerAdapterとMenuで、順番をそろえる実装方法をとりました。
private class MyPagerAdapter extends FragmentStateAdapter {
MyPagerAdapter(FragmentActivity fa) {
super(fa);
}
@Override
public int getItemCount() {
return menuList.length;
}
int [] menuList = {
R.id.navigation_pad_2,
R.id.navigation_piano_2,
R.id.navigation_midi_2,
R.id.navigation_config_2,
R.id.navigation_sponsor_2,
};
@Override
public Fragment createFragment(int position) {
int newId = menuList[position];
if (newId == R.id.navigation_pad_2) {
return NavHostFragment.create(R.navigation.navigation_pad16);
}
if (newId == R.id.navigation_piano_2) {
return NavHostFragment.create(R.navigation.navigation_player);
}
if (newId == R.id.navigation_midi_2) {
return new MidiSelectorFragment(); //ナビゲーションしない場合
}
if (newId == R.id.navigation_config_2) {
//return new ConfigFragment();
return NavHostFragment.create(R.navigation.navigation_config);
}
return new SupportFragment();
}
}
new NavHostFragment.create(R.navigation.navigation_player);
この行では、切り替えた部位に、さらにネストしたナビゲーションを配置できます。その実装方法に続きます。
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/navigation_player"
app:startDestination="@+id/nav_song_select">
<fragment
android:id="@+id/nav_song_select"
android:name="org.star_advance.mixandccle.ui.pianoroll.SongSelectorFragment"
android:label="@string/hello_song_player"/>
<fragment
android:id="@+id/nav_song_pianoroll"
android:name="org.star_advance.mixandccle.ui.pianoroll.PianorollFragment"
android:label="@string/hello_song_player"/>
</navigation>
スタートを、SongSelectorFragmentとしていますが、navigationに従ったフラグメントの切り替えは下記で行っています。
SongSelectorFragment.javapublic class SongSelectorFragment extends Fragment {
/* cut some lines */
public void startStream(InputStream stream) {
try {
SMFSequencer seq2 = new SMFSequencer();
seq2._parser.readFile(stream);
stream.close();
PianorollFragment._globalSequencer = seq2;
NavHostFragment frag = (NavHostFragment) getParentFragment();
NavHostFragment.findNavController(frag).navigate(R.id.nav_song_pianoroll);
} catch (IOException e) {
Log.e(TAG, e.getMessage(), e);
}
}
}
PianoRollFragment.javapackage org.star_advance.mixandccle.ui.pianoroll;
public class PianoRollFragment extends Fragment {
static SMFSequencer _globalSequencer = null; //package private
/* cut some lines */
@Override
public void onResume() {
super.onResume();
if (_globalSequencer == null) {
new Handler(Looper.getMainLooper()).post(() -> {
MXMixVolume.getInstance().setNoteCountListener(null);
getParentFragmentManager().popBackStack();
});
return;
}
if (MXPianoRollSetting._instance._paintKeyReverse) {
setPianoRollOrientation(true);
} else {
setPianoRollOrientation(false);
}
if (_globalSequencer.isPlayerRunning() == false) {
MainParameters params = MainParameters.getInstance();
_globalSequencer.setMusicBoxMode(params._isMusicBox.getValue());
_globalSequencer.setTranspose(MXPianoRollSetting._instance._transposePianoroll);
_globalSequencer.startPlayerThread(0, new SMFCallback() {
@Override
public void smfPlayNote(SMFMessage e) {
MidiOne.getInstance().startBySequencer(e);
}
@Override
public void smfStarted() {
MXMixVolume.getInstance().activateVolumer(true);
}
@Override
public void smfStoped(boolean fineFinish) {
MXMixVolume.getInstance().activateVolumer(false);
_globalSequencer = null;
new Handler(Looper.getMainLooper()).post(() -> {
MXMixVolume.getInstance().setNoteCountListener(null);
getParentFragmentManager().popBackStack();
});
}
@Override
public void smfProgress(long pos, long finish) {
}
});
}
binding.pianoRoll.setup(_globalSequencer, binding.pianoKeys);
}
遷移もとでは、この書式ですが、
NavHostFragment frag = (NavHostFragment) getParentFragment();
NavHostFragment.findNavController(frag).navigate(R.id.nav_song_pianoroll);
遷移した先から戻る場合は、この書式じゃないと、うまくいかなかったです。2回目の表示でつながりが切れちゃったり、、
getParentFragmentManager().popBackStack();
まぁこれでうまくいっています。ご参考にしていただけたら、幸いです。
※おまけ 画面を回転させたとき、ナビを下部から左にするコード(上のつづきです)
MainActivity_setupNaviBar.java @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding.getRoot().addOnLayoutChangeListener((v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
setupNaviBar();
});
/* cut some lines */
}
public void setupNaviBar() {
if (_stopFeedback > 0) {
return;
}
_stopFeedback ++;
try
{
try {
int parentWidth = binding.getRoot().getWidth();
int parentHeight = binding.getRoot().getHeight();
if (parentWidth == 0 || parentHeight == 0 || binding == null) {
new Handler(Looper.getMainLooper()).postDelayed(() -> {
setupNaviBar();
}, 10);
return;
}
if (saveWidth == parentWidth && saveHeight == parentHeight) {
return;
}
saveWidth = parentWidth;
saveHeight = parentHeight;
boolean hideTopNavi = false;
boolean hideSideNavi = false;
boolean hideBottomNavi = false;
if (saveWidth > saveHeight) {
hideTopNavi = false;
hideSideNavi = false;
hideBottomNavi = true;
}else {
hideTopNavi = false;
hideSideNavi = true;
hideBottomNavi = false;
}
/*
if (hideTopNavi) {
binding.mainActivityToolbar.getLayoutParams().height = 0;
}else {
binding.mainActivityToolbar.getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT;
}*/
if (hideSideNavi) {
binding.navSideView.getLayoutParams().width = 1;
}else {
binding.navSideView.getLayoutParams().width = 200;
}
if (hideBottomNavi) {
binding.navBottomView.getLayoutParams().height = 1;
}else {
binding.navBottomView.getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT;
}
binding.getRoot().requestLayout();
}catch(Throwable ex){
Log.e(TAG, ex.getMessage(), ex);
}
}finally {
_stopFeedback --;
}
}
広告を表示しています。
コメント