使用片段时在Android导航抽屉图像和Up插入符之间切换


177

当使用导航抽屉时,Android开发人员建议在ActionBar中“仅导航抽屉中表示的那些屏幕实际上应该具有导航抽屉图像”,而“所有其他屏幕都具有传统的上克拉”。

有关详细信息,请参见此处:http: //youtu.be/F5COhlbpIbY

我正在使用一项活动来控制片段的多个级别,并且可以使Navigation Drawer图像在所有级别上显示和运行。

创建较低级别的片段时,我可以调用ActionBarDrawerToggle setDrawerIndicatorEnabled(false)来隐藏导航抽屉图像并显示Up插入符号

LowerLevelFragment lowFrag = new LowerLevelFragment();

//disable the toggle menu and show up carat
theDrawerToggle.setDrawerIndicatorEnabled(false);
getSupportFragmentManager().beginTransaction().replace(R.id.frag_layout, 
lowFrag, "lowerFrag").addToBackStack(null).commit();

我遇到的问题是,当我导航回到Up克拉的顶层片段时,仍然显示而不是原始的Navigation Drawer图像。关于如何“刷新”顶层片段上的ActionBar以重新显示导航抽屉图像的任何建议?


汤姆的建议对我有用。这是我所做的:

主要活动

此活动控制应用程序中的所有片段。

在准备替换其他片段的新片段时,我setDrawerIndicatorEnabled(false)像这样设置DrawerToggle :

LowerLevelFragment lowFrag = new LowerLevelFragment();

//disable the toggle menu and show up carat
theDrawerToggle.setDrawerIndicatorEnabled(false);
getSupportFragmentManager().beginTransaction().replace(R.id.frag_layout,   
lowFrag).addToBackStack(null).commit();

接下来,在的替代中onBackPressed,我通过将DrawerToggle设置为如下来还原了上述内容setDrawerIndicatorEnabled(true)

@Override
public void onBackPressed() {
    super.onBackPressed();
    // turn on the Navigation Drawer image; 
    // this is called in the LowerLevelFragments
    setDrawerIndicatorEnabled(true)
}

在较低的片段中

在我修改过的片段中onCreateonOptionsItemSelected像这样:

onCreate添加setHasOptionsMenu(true)以启用配置选项菜单。同时设置setDisplayHomeAsUpEnabled(true)为在操作栏中启用<

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // needed to indicate that the fragment would 
    // like to add items to the Options Menu        
    setHasOptionsMenu(true);    
    // update the actionbar to show the up carat/affordance 
    getActivity().getActionBar().setDisplayHomeAsUpEnabled(true);
}

然后,每onOptionsItemSelected按一次<,它就会onBackPressed()从活动中调用,以在层次结构中上移一个级别并显示导航抽屉图像:

@Override
public boolean onOptionsItemSelected(MenuItem item) {   
    // Get item selected and deal with it
    switch (item.getItemId()) {
        case android.R.id.home:
            //called when the up affordance/carat in actionbar is pressed
            getActivity().onBackPressed();
            return true;
        … 
    }

2
同样,在onBackPressed()方法中,您可以使用getFragmentManager()。getBackStackEntryCount()方法检查后退堆栈中有多少条目,并仅在结果为0时启用抽屉指示器。在这种情况下,不必在每个LowerLevelFragments中启用homeAsUpIndicator。
pvshnik

2
这非常有用!您应该移动帖子的“解决方案”部分,并使其成为实际的“答案”。您将获得更多点赞,这毕竟一个答案
Oleksiy

您为什么在这里替换片段:.replace(R.id.frag_layout。如果这是一个更高的层次结构级别,那么我希望您将.add它放到Backstack中。
JJD

5
兄弟,您如何参考theDrawerToggle.setDrawerIndicatorEnabled(false);片段的内部?我认为它是在Main Activity类文件中声明的。我找不到引用此方法。有什么提示吗?
天网2014年

1
使用工具栏时,我必须切换显示选项,以同时不使用房屋。否则,(内部android.support.v7.internal.widget包中)的setDisplayOptions()方法ToolbarWidgetWrapper第二次输入相同片段时将不会重新创建图标。当其他人也偶然发现此问题时,只需将其留在这里。
Wolfram Rittmeyer

Answers:


30

您已经写道,要实现较低级别的片段,您要替换现有的片段,而不是在新活动中实现较低级别的片段。

我认为您随后必须手动实现后退功能:当用户按下时,您将拥有弹出堆栈的代码(例如,Activity::onBackPressed覆盖)。因此,无论您在何处执行操作,都可以反转setDrawerIndicatorEnabled


谢谢汤姆,这很有效!我已经用我使用的解决方案更新了原始帖子。
EvilAsh 2013年

6
如何在较低级别的片段中引用theDrawerToggle?它已在Main活动中定义,无法解决这个问题!
天网2014年

1
您可以从片段中获取活动。因此,只需在您的主要活动中添加吸气剂即可访问抽屉式切换器。
Raphael Royer-Rivard

83

很容易像1-2-3。

如果要实现:

1)抽屉指示器-当“后方堆栈”中没有碎片或抽屉打开时

2)箭头-当某些碎片在后堆叠中时

private FragmentManager.OnBackStackChangedListener
        mOnBackStackChangedListener = new FragmentManager.OnBackStackChangedListener() {
    @Override
    public void onBackStackChanged() {
        syncActionBarArrowState();
    }
};

@Override
protected void onCreate(Bundle savedInstanceState) {
    getSupportActionBar().setDisplayShowHomeEnabled(true);
    getSupportActionBar().setDisplayHomeAsUpEnabled(true);
    mDrawerToggle = new ActionBarDrawerToggle(
            this,             
            mDrawerLayout,  
            R.drawable.ic_navigation_drawer, 
            0, 
            0  
    ) {

        public void onDrawerClosed(View view) {
            syncActionBarArrowState();
        }

        public void onDrawerOpened(View drawerView) {
            mDrawerToggle.setDrawerIndicatorEnabled(true);
        }
    };

    mDrawerLayout.setDrawerListener(mDrawerToggle);
    getSupportFragmentManager().addOnBackStackChangedListener(mOnBackStackChangedListener);
}

@Override
protected void onDestroy() {
    getSupportFragmentManager().removeOnBackStackChangedListener(mOnBackStackChangedListener);
    super.onDestroy();
}

private void syncActionBarArrowState() {
    int backStackEntryCount = 
        getSupportFragmentManager().getBackStackEntryCount();
    mDrawerToggle.setDrawerIndicatorEnabled(backStackEntryCount == 0);
}

3)两种指示器均根据其形状起作用

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    if (mDrawerToggle.isDrawerIndicatorEnabled() && 
        mDrawerToggle.onOptionsItemSelected(item)) {
        return true;
    } else if (item.getItemId() == android.R.id.home && 
               getSupportFragmentManager().popBackStackImmediate()) {
        return true;
    } else {
        return super.onOptionsItemSelected(item);
    }
}

PS请参阅有关3行指示器行为的其他技巧,在Android Developers创建导航抽屉


3
我最终发现了与您相似的东西。我认为这种解决方案(使用BackStackChangedListener在显示的内容之间进行切换)是最优雅的。但是,我有两个更改:1)我不调用/更改onDrawerClosed / onDrawerOpened调用中的抽屉指示器-不需要,并且2)我让Fragments自身通过使它们都继承的AbstractFragment来处理Up导航。实现onOptionsItemSelected(..),并且始终调用setHasOptionsMenu(true);。
Espen Riskedal 2013年

2
您还应该在箭头模式下锁定导航抽屉的滑动手势:mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED); 并在抽屉指示器模式下再次启用它
artkoenig 2014年

5
我最终在NavigationDrawer片段中完成了所有这些操作。顺便说一句,谢谢。
2014年

1
setActionBarArrowDependingOnFragmentsBackStack()...这么长的名字:P
S.Thiongane 2014年

2
当我从片段A移到B并将A添加到后向堆栈时,这不会为我显示向上按钮。:(
aandis

14

我用了下一个东西:

getSupportFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
            @Override
            public void onBackStackChanged() {
                if(getSupportFragmentManager().getBackStackEntryCount() > 0){
                    mDrawerToggle.setDrawerIndicatorEnabled(false);
                    getSupportActionBar().setDisplayHomeAsUpEnabled(true);
                }
                else {
                    getSupportActionBar().setDisplayHomeAsUpEnabled(false);
                    mDrawerToggle.setDrawerIndicatorEnabled(true);
                }
            }
        });

1
非常感谢,这是唯一有效的方法。添加drawerToggle.setToolbarNavigationClickListener(

感谢@Yuriy,这有助于我解决问题
Muhammad Shoaib Murtaza

12

如果您的向上操作栏按钮不起作用,请不要忘记添加侦听器:

// Navigation back icon listener
mDrawerToggle.setToolbarNavigationClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            onBackPressed();
        }
});

使用主页按钮实现抽屉导航时,我遇到了一些麻烦,除了操作按钮外,其他所有功能都起作用。


10

尝试根据DrawerToggle的状态处理MainActivity中的Home项目选择。这样,您不必向每个片段添加相同的代码。

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    // Only handle with DrawerToggle if the drawer indicator is enabled.
    if (mDrawerToggle.isDrawerIndicatorEnabled() &&
            mDrawerToggle.onOptionsItemSelected(item)) {
        return true;
    }
    // Handle action buttons
    switch (item.getItemId()) {
        // Handle home button in non-drawer mode
        case android.R.id.home:
            onBackPressed();
            return true;

        default:
            return super.onOptionsItemSelected(item);
    }
}

+1整洁的解决方案。为了使答案完全可用,您应该在后堆栈上添加一个检查。当它为空时,自动将抽屉指示器设置为true。
HpTerm

@HpTerm我处理了backstack,onBackPressed()因为我希望两者都具有相同的行为。
dzeikei

6

跟进

@dzeikei提供的解决方案很简洁,但是当使用片段时,可以扩展该解决方案,以在后堆栈为空时自动处理退回抽屉指示器的问题。

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    // Only handle with DrawerToggle if the drawer indicator is enabled.
    if (mDrawerToggle.isDrawerIndicatorEnabled() &&
            mDrawerToggle.onOptionsItemSelected(item)) {
        return true;
    }
    // Handle action buttons
    switch (item.getItemId()) {
        // Handle home button in non-drawer mode
        case android.R.id.home:
            // Use getSupportFragmentManager() to support older devices
            FragmentManager fragmentManager = getFragmentManager();
            fragmentManager.popBackStack();
            // Make sure transactions are finished before reading backstack count
            fragmentManager.executePendingTransactions();
            if (fragmentManager.getBackStackEntryCount() < 1){
                mDrawerToggle.setDrawerIndicatorEnabled(true);  
            }
            return true;

        default:
            return super.onOptionsItemSelected(item);
    }
}

编辑

对于@JJD的问题。

片段在活动中被保存/管理。上面的代码在该活动中只编写了一次,但仅处理的插入符onOptionsItemSelected

在我的一个应用程序中,当按下后退按钮时,我还需要处理向上插入符号的行为。这可以通过重写来处理onBackPressed

@Override
public void onBackPressed() {
    // Use getSupportFragmentManager() to support older devices
    FragmentManager fragmentManager = getFragmentManager();
    fragmentManager.executePendingTransactions();
    if (fragmentManager.getBackStackEntryCount() < 1){
        super.onBackPressed();
    } else {
        fragmentManager.executePendingTransactions();
        fragmentManager.popBackStack();
        fragmentManager.executePendingTransactions();
        if (fragmentManager.getBackStackEntryCount() < 1){
            mDrawerToggle.setDrawerIndicatorEnabled(true);
        }
    }
};

请注意,在onOptionsItemSelected和之间的代码重复onBackPressed可以通过创建方法并在两个地方都调用该方法来避免。

还要注意,我又添加了两次,executePendingTransactions这在我的情况下是必需的,否则我有时会出现向上插入符号的奇怪行为。


这是我需要添加以实现Up caret行为的完整代码,还是您引用的是其他帖子之一?请澄清这一点。
JJD 2014年

感谢您的修改。实际上,我维持mDrawerToggle在一个NavigationDrawerFragment班级内。为了使其正常工作,我还需要切换主页按钮/指示灯的状态-参见:NavigationDrawerFragment#toggleDrawerIndicator。此外,我不确定您是否需要初始签入onOptionsItemSelected:我未对其进行评论。-简化示例
JJD,2014年

修订的实施:您对最初的入住是正确的onOptionsItemSelected。这样可以确保导航抽屉仍在顶层层次结构中打开。但是,我将代码移至NavigationDrawerFragment#onOptionsItemSelected。这有助于我不要暴露mDrawerToggleMainActivity
JJD

@JJD我记得在每个层次的层次结构中都需要一切工作。只要它也对您有效,那很好。当然,正如您所说,您可以将代码移到其他地方而不暴露它。
HpTerm

2

我为托管活动创建了一个界面,以更新汉堡菜单的视图状态。对于顶层片段,我将切换设置为true,对于要显示其上<箭头的片段,我将切换设置为false

public class SomeFragment extends Fragment {

    public interface OnFragmentInteractionListener {
        public void showDrawerToggle(boolean showDrawerToggle);
    }

    private OnFragmentInteractionListener mListener;

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        try {
            this.mListener = (OnFragmentInteractionListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString() + " must implement OnFragmentInteractionListener");
        }
    }

    @Override
    public void onResume() {
        super.onResume();
        mListener.showDrawerToggle(false);
    }
}

然后在我的活动中...

public class MainActivity extends Activity implements SomeFragment.OnFragmentInteractionListener {

    private ActionBarDrawerToggle mDrawerToggle;

    public void showDrawerToggle(boolean showDrawerIndicator) {
        mDrawerToggle.setDrawerIndicatorEnabled(showDrawerIndicator);
    }

}

2

这个答案是有效的,但是有一点问题。在getSupportActionBar().setDisplayHomeAsUpEnabled(false)没有明确要求,它是导致即使有在堆栈中没有项目,以便改变抽屉图标被隐藏setActionBarArrowDependingOnFragmentsBackStack()方法为我工作。

private void setActionBarArrowDependingOnFragmentsBackStack() {
        int backStackEntryCount = getSupportFragmentManager()
                .getBackStackEntryCount();
        // If there are no items in the back stack
        if (backStackEntryCount == 0) {
            // Please make sure that UP CARAT is Hidden otherwise Drawer icon
            // wont display
            getSupportActionBar().setDisplayHomeAsUpEnabled(false);
            // Display the Drawer Icon
            mDrawerToggle.setDrawerIndicatorEnabled(true);
        } else {
            // Show the Up carat
            getSupportActionBar().setDisplayHomeAsUpEnabled(true);
            // Hide the Drawer Icon
            mDrawerToggle.setDrawerIndicatorEnabled(false);
        }

    }

就我而言,严格的解决方案仅使用actionBarDrawerToggle.setDrawerIndicatorEnabled(getSupportFragmentManager()。getBackStackEntryCount()<0);
Gorets '16

1

逻辑很清楚。如果清除了片段返回堆栈,则显示返回按钮。如果片段堆栈不清楚,则显示材质的汉堡包动画。

getSupportFragmentManager().addOnBackStackChangedListener(
    new FragmentManager.OnBackStackChangedListener() {
        @Override
        public void onBackStackChanged() {
            syncActionBarArrowState();
        }
    }
);


private void syncActionBarArrowState() {
    int backStackEntryCount = getSupportFragmentManager().getBackStackEntryCount();
    mNavigationDrawerFragment.setDrawerIndicatorEnabled(backStackEntryCount == 0);
}

//add these in Your NavigationDrawer fragment class

public void setDrawerIndicatorEnabled(boolean flag){
    ActionBar actionBar = getActionBar();
    if (!flag) {
        mDrawerToggle.setDrawerIndicatorEnabled(false);
        actionBar.setDisplayHomeAsUpEnabled(true);
        mDrawerToggle.setHomeAsUpIndicator(getColoredArrow());
    } else {
        mDrawerToggle.setDrawerIndicatorEnabled(true);
    }
    mDrawerToggle.syncState();
    getActivity().supportInvalidateOptionsMenu();
}

//download back button from this(https://www.google.com/design/icons/) website and add to your project

private Drawable getColoredArrow() {
    Drawable arrowDrawable = ContextCompat.getDrawable(getActivity(), R.drawable.ic_arrow_back_black_24dp);
    Drawable wrapped = DrawableCompat.wrap(arrowDrawable);

    if (arrowDrawable != null && wrapped != null) {
        // This should avoid tinting all the arrows
        arrowDrawable.mutate();
        DrawableCompat.setTint(wrapped, Color.GRAY);
    }
    return wrapped;
}

1

如果您查看GMAIL应用程序,然后来这里搜索carret / affordance图标。

我想请你这样做,以上答案都不清楚。我能够修改接受的答案。

  • NavigationDrawer-> Listview包含子片段。


  • 亚片段将像这样列出

  • firstFragment ==位置0 --->这将有亚片段->片段

  • secondFragment
  • thirdFragment等...

在firstFragment中,您还有其他片段。

在DrawerActivity上调用它

getFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
        @Override
        public void onBackStackChanged() {
            if (getFragmentManager().getBackStackEntryCount() > 0) {
                mDrawerToggle.setDrawerIndicatorEnabled(false);
            } else {
                mDrawerToggle.setDrawerIndicatorEnabled(true);
            }
        }
    });

并且零碎

    setHasOptionsMenu(true);    

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    // Get item selected and deal with it
    switch (item.getItemId()) {
        case android.R.id.home:
            //called when the up affordance/carat in actionbar is pressed
            activity.onBackPressed();
            return true;
    }
    return false;
}

在OnBackPressed Drawer活动方法上,将抽屉开关设置为true,以再次启用导航列表图标。

谢谢,Pusp



0

IMO,使用onNavigateUp()(如图所示这里)在riwnodennyk的或汤姆的解决方案是更清洁,似乎更好地工作。只需将onOptionsItemSelected代码替换为此:

@Override
public boolean onSupportNavigateUp() {
    if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
        // handle up navigation
        return true;
    } else {
        return super.onSupportNavigateUp();
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.