DrawerLayout 的项目点击 - 啥时候更换片段合适?

Posted

技术标签:

【中文标题】DrawerLayout 的项目点击 - 啥时候更换片段合适?【英文标题】:DrawerLayout's item click - When is the right time to replace fragment?DrawerLayout 的项目点击 - 什么时候更换片段合适? 【发布时间】:2013-07-03 17:09:09 【问题描述】:

我正在开发一个使用抽屉式导航模式(With DrawerLayout)的应用程序。

每次点击抽屉的项目,都会替换主容器中的片段。

但是,我不确定何时是进行片段交易的合适时间? 抽屉什么时候开始关闭?还是关门后?

在google的documentaion example,可以看到他们在做交易 在项目单击后立即关闭抽屉。 结果,抽屉显得迟钝且不光滑,而且看起来很糟糕(我的应用程序中也出现过这种情况)。

在Gmail 和Google Drive 应用程序中,另一方面,他们似乎在抽屉关闭后进行交易(我是对的吗?)。 因此,抽屉并不滞后且非常流畅,但至少需要大约 1 秒(抽屉关闭所需的时间)才能看到下一个片段。

在立即进行分片交易时,抽屉似乎不可能平滑。

你怎么看?

提前致谢!

【问题讨论】:

我同意!当我的应用程序从抽屉的onItemClick() 中启动一个新活动时,我看到了抽屉关闭动画。这可能是因为 UI 线程上发生了很多活动。所以现在我必须从DrawerLayout.DrawerListener 内部启动一个新活动......就像你提到的那样,这会减慢用户体验。 是否可以改变抽屉关闭动画的速度?速度似乎是要公开的重要属性......但我在文档中找不到它!)developer.android.com/reference/android/support/v4/widget/… @SomeoneSomewhere 您可以更改动画长度,但这无论如何都不是一个优雅的解决方案:***.com/questions/19460683/… @SomeoneSomewhere 我不喜欢生涩的关闭动画,但我不喜欢让用户等待更长的时间。 你可以看看这个解决方案:***.com/a/32455989/879154 【参考方案1】:

是的,完全同意,执行片段(带视图)事务会导致布局传递,这会导致动画视图上出现 janky 动画,引用 DrawerLayout docs:

DrawerLayout.DrawerListener 可用于监控抽屉视图的状态和运动。避免在动画过程中执行昂贵的操作,例如布局,因为它会导致卡顿;尝试在 STATE_IDLE 状态期间执行昂贵的操作。

所以请在抽屉关闭或有人修补支持库以某种方式修复该问题后执行您的片段事务:)

【讨论】:

只是支持库有这个问题吗? 如何等到状态变为 STATE_IDLE ?【参考方案2】:

另一种解决方案是创建一个Handler,并在关闭抽屉后发布一个延迟的Runnable,如下所示:https://***.com/a/18483633/769501。这种方法的好处是,与等待DrawerListener#onDrawerClosed() 相比,您的片段将被替换得更快,但当然,任意延迟并不能 100% 保证抽屉动画会及时完成。

也就是说,我使用了 200 毫秒的延迟,效果非常好。

private class DrawerItemClickListener implements OnItemClickListener 
    @Override
    public void onItemClick(AdapterView<?> parent, View view, final int position, long id) 
        drawerLayout.closeDrawer(drawerList);
        new Handler().postDelayed(new Runnable() 
            @Override
            public void run() 
                switchFragments(position); // your fragment transactions go here
            
        , 200);
    

【讨论】:

这种方法会产生另一个问题,描述为here 在提交过程中崩溃然后按任务菜单 你先生,是救生员:D【参考方案3】:

这是我为实现类似于 Gmail 应用程序的流畅交易动画所做的:

activity_drawer.xml

<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/drawer_layout"
    android:layout_
    android:layout_ >

    <!-- The main content view -->
    <FrameLayout
        android:id="@+id/content_frame"
        android:layout_
        android:layout_ />

    <!-- The navigation drawer -->
    <ListView 
    android:id="@+id/left_drawer"
        android:layout_
        android:layout_
        android:layout_gravity="left"
        android:choiceMode="singleChoice" />

</android.support.v4.widget.DrawerLayout>

DrawerActivity.java

private Fragment mContentFragment;
private Fragment mNextContentFragment;
private boolean mChangeContentFragment = false;

private Handler mHandler = new Handler();

...

@Override
public void onCreate(Bundle savedInstanceState) 
    ...

    mDrawerLayout.setDrawerListener(new DrawerListener());

    mDrawerList.setOnItemClickListener(new DrawerItemClickListener());

    ...


....

private class DrawerItemClickListener implements ListView.OnItemClickListener 

    @Override
    public void onItemClick(AdapterView parent, View view, int position, long id) 
        getSupportFragmentManager().beginTransaction().remove(mContentFragment).commit();

        switch (position) 
            case 0:
                mNextContentFragment = new Fragment1();
                break;

            case 1:
                mNextContentFragment = new Fragment2();
                break;

            case 2:
                mNextContentFragment = new Fragment3();
                break;
        

        mChangeContentFragment = true;

        mDrawerList.setItemChecked(position, true);

        mHandler.postDelayed(new Runnable() 

            @Override
            public void run() 
                mDrawerLayout.closeDrawer(mDrawerList);
                       
        , 150);
    


private class DrawerListener implements android.support.v4.widget.DrawerLayout.DrawerListener 

    @Override
    public void onDrawerClosed(View view) 
        if (mChangeContentFragment) 
             getSupportFragmentManager().beginTransaction().setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN).replace(R.id.content_frame, mNextContentFragment).commit();

             mContentFragment = mNextContentFragment;           
             mNextContentFragment = null;

             mChangeContentFragment = false;
         
     
 

希望对您有所帮助! :-)

【讨论】:

我喜欢这个解决方案,但我真的不知道如何让后台堆栈行为正常工作。我可以在替换事务中添加“addToBackStack”,但由于先前的内容已被删除,因此无法按预期工作。我尝试隐藏而不是删除,但这也不起作用。 @pbergqvist 您可能会采取哪些措施来获得回栈行为: 1. 在删除“旧内容片段”之前使用另一个变量来保存它。 2.从您的活动中覆盖 onBackPressed 方法,并用您的“旧内容片段”替换内容片段。 @pbergqvist 更好的东西。如果您将“删除片段”事务、mChangeContentFragment=true 和处理程序发布一起放在一个名为 setContentFragment(Fragment fragment) 的方法中,您可以在 onBackPressed 中使用此方法,以免重复两次重写相同的代码。它对你有用吗? 我想这会奏效,但只能退一步。否则我会自己维护一个堆栈。 后退堆栈不需要更改 - Google 的任何应用程序都不会通过后退按钮切换片段。这令人困惑。【参考方案4】:

我知道这个问题很老,但我遇到了同样的问题,我想我会发布我的解决方案,因为我认为它比添加硬编码延迟时间更好。我所做的是在执行我的任务之前使用onDrawerClosed 函数来验证抽屉是否已关闭。

//on button click...
private void displayView(int position) 
    switch (position) 
    //if item 1 is selected, update a global variable `"int itemPosition"` to be 1
    case 1:
        itemPosition = 1;
        //();
        break;
    default:
        break;
    

    // update selected item and title, then close the drawer
    mDrawerList.setItemChecked(position, true);
    mDrawerList.setSelection(position);
    mDrawerLayout.closeDrawer(mDrawerList); //close drawer

然后在onDrawerClosed,打开对应的activity。

public void onDrawerClosed(View view) 
    getSupportActionBar().setTitle(mTitle);
    // calling onPrepareOptionsMenu() to show action bar icons
    supportInvalidateOptionsMenu();
    if (itemPosition == 1) 
        Intent intent = new Intent(BaseActivity.this, SecondActivity.class);
        startActivity(intent);
    

【讨论】:

抽屉关闭和开始新活动之间仍有延迟。【参考方案5】:

只需在处理程序中编写代码并延迟 200 毫秒。

 new Handler().postDelayed(new Runnable() 
  @Override
   public void run() 
       openSelectionDrawerItem(position);          
   
 , 200);

【讨论】:

【参考方案6】:

而不是延迟您的项目点击,这可能会让您的应用感觉缓慢。我只会延迟关闭 mDrawerLayout。我也不会使用DrawerLayout.OnDrawerListener onClose(...),因为调用这些回调太慢了。

new Handler().postDelayed(new Runnable() 
    @Override
    public void run() 
        mDrawerLayout.closeDrawer(GravityCompat.START);
    
, 200);

【讨论】:

【参考方案7】:

如果您希望它顺利且没有任何延迟,请让抽屉保持打开状态,然后在返回时将其关闭(在 onRestart() 方法中)。

@Override
protected void onRestart() 
    // TODO Auto-generated method stub
    super.onRestart();
    mDrawerLayout.closeDrawer(mDrawerList);     

返回时的副作用是(快速)动画,但这可能是可以接受的。

【讨论】:

从什么归来?我只想关闭抽屉,让我的屏幕转到另一个 Activity。 @Igor 使用后退按钮从另一个活动返回后抽屉仍将打开(或任何其他允许您在不重新创建活动的情况下返回活动的方法)。

以上是关于DrawerLayout 的项目点击 - 啥时候更换片段合适?的主要内容,如果未能解决你的问题,请参考以下文章

DrawerLayout(抽屉效果)

Android:具有动态 actionBar 颜色和 DrawerLayout 的透明状态栏

自定义DrawerLayout抽屉布局

为啥我不能用布局重力锁定 DrawerLayout

android官方侧滑菜单DrawerLayout详解

idea的项目打包以后点击没有啥效果