シームレスなナビゲーションを提供するために、同じアクティビティ内で単一のアクティビティと複数のフラグメント(スクリーンショットが添付されています)を使用しています。しかし、最新のツールバーとナビゲーションビューを実装した後は、ナビゲーションボタンとホームボタンを処理するのが難しいようです。次のことで問題があります。
OnBackPressed()、setHomeAsUpIndicatorのオーバーライド、フラグメントの手動ポップなど、いくつかのことを試しました。以前、私はこれを処理するためにActionBarDrawerトグルを使用していましたが、今では何とか失敗しています。ほとんどの場所で別々のアクティビティを使用していると思われるGoogleサンプルを確認しました。
誰でも、NavigationView、内部フラグメントおよびページタイトルの戻るボタンを処理するための適切な戻るナビゲーションを実装する方法をガイドできますか?私はAppCompatActivity、Android.app.Fragment、NavigationViewおよびToolbar.
あなたのActivity
とFragment
に対する責任のある種の分割で説明する方がはるかに簡単です。
問題1:左上のハンバーガー/戻るボタンの管理。アイコンと機能をメニューと戻るナビゲーションに切り替える
図から、ソリューションはActivity
でカプセル化する必要があります。これは次のようになります。
_public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener {
private ActionBarDrawerToggle mDrawerToggle;
private DrawerLayout mDrawer;
private ActionBar mActionBar;
private boolean mToolBarNavigationListenerIsRegistered = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
mActionBar = getSupportActionBar();
mDrawer = (DrawerLayout) findViewById(R.id.drawer_layout);
mDrawerToggle = new ActionBarDrawerToggle(this, mDrawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
mDrawer.addDrawerListener(mDrawerToggle);
mDrawerToggle.syncState();
NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
navigationView.setNavigationItemSelectedListener(this);
// On orientation change savedInstanceState will not be null.
// Use this to show hamburger or up icon based on fragment back stack.
if(savedInstanceState != null){
resolveUpButtonWithFragmentStack();
} else {
// You probably want to add your ListFragment here.
}
}
@Override
public void onBackPressed() {
if (mDrawer.isDrawerOpen(GravityCompat.START)) {
mDrawer.closeDrawer(GravityCompat.START);
} else {
int backStackCount = getSupportFragmentManager().getBackStackEntryCount();
if (backStackCount >= 1) {
getSupportFragmentManager().popBackStack();
// Change to hamburger icon if at bottom of stack
if(backStackCount == 1){
showUpButton(false);
}
} else {
super.onBackPressed();
}
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
} else if (id == Android.R.id.home) {
// Home/Up logic handled by onBackPressed implementation
onBackPressed();
}
return super.onOptionsItemSelected(item);
}
@SuppressWarnings("StatementWithEmptyBody")
@Override
public boolean onNavigationItemSelected(MenuItem item) {
// Handle navigation view item clicks here.
int id = item.getItemId();
// Navigation drawer item selection logic goes here
mDrawer.closeDrawer(GravityCompat.START);
return true;
}
private void replaceFragment() {
/**
* Your fragment replacement logic goes here
* e.g.
* FragmentTransaction ft = getFragmentManager().beginTransaction();
* String tag = "MyFragment";
* ft.replace(R.id.content, MyFragment.newInstance(tag), tag).addToBackStack(null).commit();
*/
// The part that changes the hamburger icon to the up icon
showUpButton(true);
}
private void resolveUpButtonWithFragmentStack() {
showUpButton(getSupportFragmentManager().getBackStackEntryCount() > 0);
}
private void showUpButton(boolean show) {
// To keep states of ActionBar and ActionBarDrawerToggle synchronized,
// when you enable on one, you disable on the other.
// And as you may notice, the order for this operation is disable first, then enable - VERY VERY IMPORTANT.
if(show) {
// Remove hamburger
mDrawerToggle.setDrawerIndicatorEnabled(false);
// Show back button
mActionBar.setDisplayHomeAsUpEnabled(true);
// when DrawerToggle is disabled i.e. setDrawerIndicatorEnabled(false), navigation icon
// clicks are disabled i.e. the UP button will not work.
// We need to add a listener, as in below, so DrawerToggle will forward
// click events to this listener.
if(!mToolBarNavigationListenerIsRegistered) {
mDrawerToggle.setToolbarNavigationClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onBackPressed();
}
});
mToolBarNavigationListenerIsRegistered = true;
}
} else {
// Remove back button
mActionBar.setDisplayHomeAsUpEnabled(false);
// Show hamburger
mDrawerToggle.setDrawerIndicatorEnabled(true);
// Remove the/any drawer toggle listener
mDrawerToggle.setToolbarNavigationClickListener(null);
mToolBarNavigationListenerIsRegistered = false;
}
// So, one may think "Hmm why not simplify to:
// .....
// getSupportActionBar().setDisplayHomeAsUpEnabled(enable);
// mDrawer.setDrawerIndicatorEnabled(!enable);
// ......
// To re-iterate, the order in which you enable and disable views IS important #dontSimplify.
}
}
_
問題2:ページタイトル-フラグメントがプッシュおよびポップされるたびにページタイトルを変更する
基本的に、これは各onStart
のFragment
で処理できます。つまり、ListFragment、DetailsFragment、CommentsFragmentは次のようになります。
_@Override
public void onStart() {
super.onStart();
// where mText is the title you want on your toolbar/actionBar
getActivity().setTitle(mText);
}
_
おそらくフラグメントのonCreate
にもsetRetainInstance(true)
を含める価値があります。
tl; dr
これを見てください: https://youtu.be/ANpBWIT3vlU
これをクローン: https://github.com/shredderskelton/androidtemplate 。
これは本当に一般的な問題であり、新しいAndroidプロジェクトを開始するたびに使用する一種のテンプレートプロジェクトを作成することで克服しました。アイデアは、戻るボタン、「ハンバーガー」インジケータ、およびフラグメント管理を再利用可能なクラスに処理します。
BaseActivityおよびBaseFragmentクラスを作成することから始めます。これは、できるだけ多くの再利用可能なコードを使用する場所です。
BaseActivityから始めましょう
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
fragmentManager = getSupportFragmentManager();
fragmentHandler = new AddFragmentHandler(fragmentManager);
fragmentManager.addOnBackStackChangedListener(backStackListener);
}
FragmentManagerはバックスタックを所有するための鍵であるため、ここからバックスタックへの変更をリッスンする必要があります。 AddFramentHandlerは、FragmentsからFragmentsを簡単に追加できるようにするために作成した小さなクラスです。それについては後で詳しく説明します。
@Override
public void onBackPressed() {
if (sendBackPressToDrawer()) {
//the drawer consumed the backpress
return;
}
if (sendBackPressToFragmentOnTop()) {
// fragment on top consumed the back press
return;
}
//let the Android system handle the back press, usually by popping the fragment
super.onBackPressed();
//close the activity if back is pressed on the root fragment
if (fragmentManager.getBackStackEntryCount() == 0) {
finish();
}
}
onBackPressedは、ほとんどの魔法が発生する場所です。メソッドのプレーンテキスト形式に気づきました。私は巨大な Clean Code ファンです-コメントを書く必要がある場合、コードはきれいではありません。基本的に、戻るボタンを押しても期待どおりにならない理由がわからない場合は、実行できる中心的な場所が必要です。この方法がその場所です。
private void syncDrawerToggleState() {
ActionBarDrawerToggle drawerToggle = getDrawerToggle();
if (getDrawerToggle() == null) {
return;
}
if (fragmentManager.getBackStackEntryCount() > 1) {
drawerToggle.setDrawerIndicatorEnabled(false);
drawerToggle.setToolbarNavigationClickListener(navigationBackPressListener); //pop backstack
} else {
drawerToggle.setDrawerIndicatorEnabled(true);
drawerToggle.setToolbarNavigationClickListener(drawerToggle.getToolbarNavigationClickListener()); //open nav menu drawer
}
}
これは、BaseActivityのもう1つの重要な部分です。基本的に、このメソッドはルートフラグメントにいるかどうかを確認し、それに応じてインジケーターを設定します。バックスタックにあるフラグメントの数に応じてリスナーが変更されることに注意してください。
次に、BaseFragmentがあります。
@Override
public void onResume() {
super.onResume();
getActivity().setTitle(getTitle());
}
protected abstract String getTitle();
上記のコードは、タイトルがフラグメントによってどのように処理されるかを示しています。
次のようなものを試してください:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
if (getSupportActionBar()!=null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
final ActionBarDrawerToggle drawerToggle = new ActionBarDrawerToggle(
this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
drawer.addDrawerListener(drawerToggle);
drawerToggle.syncState();
final View.OnClickListener originalToolbarListener = drawerToggle.getToolbarNavigationClickListener();
final View.OnClickListener navigationBackPressListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
getFragmentManager().popBackStack();
}
};
getFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
@Override
public void onBackStackChanged() {
if (getFragmentManager().getBackStackEntryCount() > 0) {
drawerToggle.setDrawerIndicatorEnabled(false);
drawerToggle.setToolbarNavigationClickListener(navigationBackPressListener);
} else {
drawerToggle.setDrawerIndicatorEnabled(true);
drawerToggle.setToolbarNavigationClickListener(originalToolbarListener);
}
}
});
// Though below steps are not related but I have included to show drawer close on Navigation Item click.
navigationView = (NavigationView) findViewById(R.id.nav_view);
navigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(MenuItem item) {
int id = item.getItemId();
/**
* handle item clicks using id
*/
drawer.closeDrawer(GravityCompat.START);
return true;
}
});
}
引き出しの状態を処理するonBackPressed
:
@Override
public void onBackPressed() {
if (drawer.isDrawerOpen(GravityCompat.START)) {
drawer.closeDrawer(GravityCompat.START);
} else {
super.onBackPressed();
}
}
バックプレスで以前のfragment
をリロードするには、常に次のようにバックスタックにフラグメントトランザクションを追加します。
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
SomeFragment fragmentToBeLoaded = new SomeFragment();
fragmentTransaction.replace(R.id.fragment_container, fragmentToBeLoaded,
fragmentToBeLoaded.getName());
fragmentTransaction.addToBackStack(fragmentToBeLoaded.getName());
fragmentTransaction.commit();
ページタイトルを動的に変更するには、すべてのFragment
s onStart
またはonResume
メソッドからこれを呼び出すことができます。
@Override
public void onStart() {
super.onStart();
getActivity().setTitle("Title for fragment");
}
注:標準のレイアウト宣言を検討したため、レイアウトを含めていません。
フラグメントを削除する場合、isRemoving()
メソッドがあります。タイトルを元に戻すのに役立ちます。
_@Override
public void onStop() {
super.onStop();
if (isRemoving()) {
// Change your title here
}
}
_
提案:デフォルトのAndroidナビゲーションシステム。addToBackStack()
を使用する場合、フラグメントについては、理論的にはonBackPressed()をオーバーライドする必要はまったくありません。
コアアプリの品質: https://developer.Android.com/distribute/essentials/quality/core.html
混乱を避けるために、「MainActivityDetailFragment」の代わりにアクティビティを使用することをお勧めします。
これを、フラグメントを呼び出すMainActivityに追加します。 getBackStackEntryCount() バックスタック内のフラグメントの数を返します。スタックの一番下のフラグメントのインデックスは0です。 popBackStack() 一番上のフラグメントをバックスタックからポップします。
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == Android.R.id.home) {
if (getSupportFragmentManager().getBackStackEntryCount() == 1) {
getSupportFragmentManager().popBackStack();
} else {
super.onBackPressed();
}
}
return true;
}
そして、戻りたいフラグメントでこの関数を使用します
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == Android.R.id.home) {
getActivity().onBackPressed();
}
return true;
}
さて、多くのテストを経て、ようやく適切なナビゲーションのセットアップに成功しました。私はあなたとまったく同じものが必要でした。唯一の違いは、v4 Fragmentsを使用していることです。しかし、これがここで何かを変えるとは思いません。
Googleの最新の例ではこのコンポーネントを使用しないため、ActionBarDrawerToggle
は使用していません。
以下のソリューションは、ディープナビゲーションでも機能します。親アクティビティ->フラグメント->フラグメントなど。
フラグメントで必要な唯一の変更は、タイトルを変更することです:
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
getActivity().setTitle(R.string.targets);
}
親Activity onCreate
メソッドで、次を初期化します。
mNavigationView = (NavigationView) findViewById(R.id.navigation_view);
setupDrawerContent(mNavigationView);
final Toolbar toolbar = (Toolbar) findViewById(R.id.drawer_toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_menu_24);// Set the hamburger icon
getSupportActionBar().setDisplayHomeAsUpEnabled(true);// Set home button pressable
// Handle the changes on the actionbar
getSupportFragmentManager().addOnBackStackChangedListener(
new FragmentManager.OnBackStackChangedListener() {
public void onBackStackChanged() {
// When no more fragments to remove, we display back the hamburger icon and the original activity title
if (getSupportFragmentManager().getBackStackEntryCount() <= 0) {
getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_menu_24);
setTitle(R.string.app_name);
}
// Else displays the back arrow
else {
getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_arrow_back_24);
}
}
});
次に、ホームボタンのアクションを処理するコードを示します。
@Override
public boolean onOptionsItemSelected(MenuItem item){
// Close the soft keyboard right away
Tools.setSoftKeyboardVisible(mViewPager, false);
switch (item.getItemId()) {
case Android.R.id.home:
// When no more fragments to remove, open the navigation drawer
if (getSupportFragmentManager().getBackStackEntryCount() <= 0) {
mDrawerLayout.openDrawer(GravityCompat.START);
}
// Removes the latest fragment
else {
getSupportFragmentManager().popBackStack();
}
return true;
}
return super.onOptionsItemSelected(item);
}
最後に、バックプレスアクションを処理するコード:
@Override
public void onBackPressed() {
// When no more fragments to remove, closes the activity
if (getSupportFragmentManager().getBackStackEntryCount() <= 0) {
super.onBackPressed();
}
// Else removes the latest fragment
else {
getSupportFragmentManager().popBackStack();
}
}
[〜#〜] note [〜#〜]:AppCompatActivity
、NavigationView
、およびテーマTheme.AppCompat.Light.NoActionBar
。