web-dev-qa-db-ja.com

Androidでツールバーを使用して適切な戻るナビゲーションとホームボタンの処理を実装する

シームレスなナビゲーションを提供するために、同じアクティビティ内で単一のアクティビティと複数のフラグメント(スクリーンショットが添付されています)を使用しています。しかし、最新のツールバーとナビゲーションビューを実装した後は、ナビゲーションボタンとホームボタンを処理するのが難しいようです。次のことで問題があります。

  • 左上のハンバーガー/戻るボタンの管理。アイコンと機能を[メニュー]と[戻る]に切り替える
    • ページタイトル-フラグメントがプッシュおよびポップされるたびにページタイトルを変更します。

OnBackPressed()、setHomeAsUpIndicatorのオーバーライド、フラグメントの手動ポップなど、いくつかのことを試しました。以前、私はこれを処理するためにActionBarDrawerトグルを使用していましたが、今では何とか失敗しています。ほとんどの場所で別々のアクティビティを使用していると思われるGoogleサンプルを確認しました。

誰でも、NavigationView、内部フラグメントおよびページタイトルの戻るボタンを処理するための適切な戻るナビゲーションを実装する方法をガイドできますか?私はAppCompatActivityAndroid.app.FragmentNavigationViewおよびToolbar.

Fragment 1 -> Fragment 2 -> Fragment 3

19
Ajith Memana

あなたのActivityFragmentに対する責任のある種の分割で説明する方がはるかに簡単です。

Division of responsibilities for Activity and 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:ページタイトル-フラグメントがプッシュおよびポップされるたびにページタイトルを変更する

基本的に、これは各onStartFragmentで処理できます。つまり、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)を含める価値があります。

16
ade.akinyede

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();

上記のコードは、タイトルがフラグメントによってどのように処理されるかを示しています。

5
shredder

次のようなものを試してください:

@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();

ページタイトルを動的に変更するには、すべてのFragments onStartまたはonResumeメソッドからこれを呼び出すことができます。

@Override
public void onStart() {
   super.onStart();
   getActivity().setTitle("Title for fragment");
}

注:標準のレイアウト宣言を検討したため、レイアウトを含めていません。

1
Rohit Arya

「ページタイトル-フラグメントがプッシュおよびポップされるたびにページタイトルを変更する」

フラグメントを削除する場合、isRemoving()メソッドがあります。タイトルを元に戻すのに役立ちます。

_@Override
public void onStop() {
    super.onStop();
    if (isRemoving()) {
        // Change your title here
    }
}
_

「メニューおよび戻るナビゲーションの機能」

提案:デフォルトのAndroidナビゲーションシステム。addToBackStack()を使用する場合、フラグメントについては、理論的にはonBackPressed()をオーバーライドする必要はまったくありません。

  1. 「アプリは、システムアイコン([戻る]ボタンなど)の期待される機能を再定義しません。
  2. 「アプリは標準システムの戻るボタンナビゲーションをサポートし、画面上のカスタムの「戻るボタン」プロンプトを使用しません。」

コアアプリの品質: https://developer.Android.com/distribute/essentials/quality/core.html

「左上のハンバーガー/戻るボタンの管理」

混乱を避けるために、「MainActivityDetailFragment」の代わりにアクティビティを使用することをお勧めします。

1
Dmitry

これを、フラグメントを呼び出す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;
    }
0
Umer

さて、多くのテストを経て、ようやく適切なナビゲーションのセットアップに成功しました。私はあなたとまったく同じものが必要でした。唯一の違いは、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 [〜#〜]AppCompatActivityNavigationView、およびテーマTheme.AppCompat.Light.NoActionBar

0
Yoann Hercouet