53

我正在使用谷歌DrawerLayout

当一个项目被点击时,抽屉会平滑关闭并Activity启动。将这些活动变成Fragments不是一种选择。因此,启动活动然后关闭抽屉也不是一种选择。同时关闭抽屉并启动 Activity 会使关闭动画卡顿。

鉴于我想先平稳地关闭它,然后启动活动,我遇到了用户单击抽屉项目和他们看到他们想要去的活动之间的延迟问题。

这就是每个项目的点击监听器的样子。

final View.OnClickListener mainItemClickListener = new View.OnClickListener() {
    @Override
    public void onClick(final View v) {
        mViewToLaunch = v;
        mDrawerLayout.closeDrawers();
    }
};

我的activity也是DrawerListener,它的onDrawerClosed方法如下:

@Override
public synchronized void onDrawerClosed(final View view) {
    if (mViewToLaunch != null) {
        onDrawerItemSelection(mViewToLaunch);
        mViewToLaunch = null;
    }
}

onDrawerItemSelection只是启动了五项活动之一。

我什么都不onPauseDrawerActivity

我正在对此进行检测,从调用 onClick 的那一刻到 onDrawerClosed 结束的那一刻平均需要 500-650 毫秒。

一旦抽屉关闭,在相应的活动启动之前会有一个明显的延迟。

我意识到有几件事正在发生:

  • 关闭动画发生,这是几毫秒的时间(比如说 300)。

  • 然后抽屉在视觉上关闭和它的监听器被触发之间可能存在一些延迟。我试图通过查看DrawerLayout源代码来弄清楚到底发生了多少,但还没有弄清楚。

  • 然后是启动的活动执行其启动生命周期方法所需的时间,包括onResume. 我还没有对此进行检测,但我估计大约需要 200-300 毫秒。

这似乎是一个问题,如果走错路会非常昂贵,所以我想确保我完全理解它。

一种解决方案是跳过结束动画,但我希望保留它。

如何尽可能减少过渡时间?

4

7 回答 7

42

根据文档

避免在动画过程中执行昂贵的操作,例如布局,因为它会导致卡顿;尝试在 STATE_IDLE 状态期间执行昂贵的操作。

您可以覆盖(实现)的方法,而不是使用 aHandler和硬编码时间延迟,这样您就可以在抽屉完全关闭时执行昂贵的操作。onDrawerStateChangedActionBarDrawerToggleDrawerLayout.DrawerListener

在 MainActivity 内部,

private class SmoothActionBarDrawerToggle extends ActionBarDrawerToggle {

    private Runnable runnable;

    public SmoothActionBarDrawerToggle(Activity activity, DrawerLayout drawerLayout, Toolbar toolbar, int openDrawerContentDescRes, int closeDrawerContentDescRes) {
        super(activity, drawerLayout, toolbar, openDrawerContentDescRes, closeDrawerContentDescRes);
    }

    @Override
    public void onDrawerOpened(View drawerView) {
        super.onDrawerOpened(drawerView);
        invalidateOptionsMenu();
    }
    @Override
    public void onDrawerClosed(View view) {
        super.onDrawerClosed(view);
        invalidateOptionsMenu();
    }
    @Override
    public void onDrawerStateChanged(int newState) {
        super.onDrawerStateChanged(newState);
        if (runnable != null && newState == DrawerLayout.STATE_IDLE) {
            runnable.run();
            runnable = null;
        }
    }

    public void runWhenIdle(Runnable runnable) {
        this.runnable = runnable;
    }
}

设置DrawerListeneronCreate

mDrawerToggle = new SmoothActionBarDrawerToggle(this, mDrawerLayout, mToolbar, R.string.open, R.string.close);
mDrawerLayout.setDrawerListener(mDrawerToggle);

最后,

private void selectItem(int position) {
    switch (position) {
        case DRAWER_ITEM_SETTINGS: {
            mDrawerToggle.runWhenIdle(new Runnable() {
                @Override
                public void run() {
                    Intent intent = new Intent(MainActivity.this, SettingsActivity.class);
                    startActivity(intent);
                }
            });
            mDrawerLayout.closeDrawers();
            break;
        }
        case DRAWER_ITEM_HELP: {
            mDrawerToggle.runWhenIdle(new Runnable() {
                @Override
                public void run() {
                    Intent intent = new Intent(MainActivity.this, HelpActivity.class);
                    startActivity(intent);
                }
            });
            mDrawerLayout.closeDrawers();
            break;
        }
    }
}
于 2015-03-22T11:14:21.173 回答
27

我在使用 DrawerLayout 时遇到了同样的问题。

我对此进行了研究,然后找到了一个不错的解决方案。

我正在做的是......

如果您为 DrawerLayout 引用 Android 示例应用程序,请检查 selectItem(position); 的代码。

在这个函数中基于位置选择片段被调用。我已根据需要使用以下代码对其进行了修改,并且工作正常,没有动画关闭口吃。

private void selectItem(final int position) {
    //Toast.makeText(getApplicationContext(), "Clicked", Toast.LENGTH_SHORT).show();
    mDrawerLayout.closeDrawer(drawerMain);
    new Handler().postDelayed(new Runnable() {
        @Override
        public void run() {
            Fragment fragment = new TimelineFragment(UserTimeLineActivity.this);
            Bundle args = new Bundle();
            args.putInt(TimelineFragment.ARG_PLANET_NUMBER, position);
            fragment.setArguments(args);

            FragmentManager fragmentManager = getSupportFragmentManager();
            fragmentManager.beginTransaction().replace(R.id.content_frame, fragment).commit();

            // update selected item and title, then close the drawer
            mCategoryDrawerList.setItemChecked(position, true);

            setTitle("TimeLine: " + mCategolyTitles[position]);
        }
    }, 200);


    // update the main content by replacing fragments


}

在这里,我首先关闭 DrawerLayout。这大约需要 250 毫秒。然后我的处理程序将调用片段。哪个工作顺利且符合要求。

希望对您也有帮助。

享受编码... :)

于 2013-08-28T09:06:19.857 回答
20

所以我似乎已经用一个合理的解决方案解决了这个问题。

可感知延迟的最大来源是抽屉在视觉上关闭和onDrawerClosed被调用之间的延迟。我通过Runnable向一个私人发布一个Handler在某个特定延迟时启动预期活动的私人解决了这个问题。选择此延迟以对应抽屉关闭。

我尝试onDrawerSlide在 80% 的进度后进行启动,但这有两个问题。首先是它结结巴巴。第二个是,如果你将百分比增加到 90% 或 95%,由于动画的性质,它根本不会被调用的可能性会增加——然后你不得不退回到onDrawerClosed,这违背了目的。

这种解决方案有可能出现口吃,特别是在旧手机上,但只需将延迟提高到足够高,就可以将这种可能性降低到 0。我认为 250 毫秒是卡顿和延迟之间的合理平衡。

代码的相关部分如下所示:

public class DrawerActivity extends SherlockFragmentActivity {
    private final Handler mDrawerHandler = new Handler();

    private void scheduleLaunchAndCloseDrawer(final View v) {
        // Clears any previously posted runnables, for double clicks
        mDrawerHandler.removeCallbacksAndMessages(null); 

        mDrawerHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                onDrawerItemSelection(v);
            }
        }, 250);
        // The millisecond delay is arbitrary and was arrived at through trial and error

        mDrawerLayout.closeDrawer();
    }
}
于 2013-08-27T00:33:43.053 回答
10

Google IOsched 2015 运行非常顺利(设置除外),原因是他们如何实现抽屉以及如何启动东西。

首先他们使用处理程序延迟启动:

        // launch the target Activity after a short delay, to allow the close animation to play
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                goToNavDrawerItem(itemId);
            }
        }, NAVDRAWER_LAUNCH_DELAY);

延迟为:

private static final int NAVDRAWER_LAUNCH_DELAY = 250;

他们做的另一件事是从使用以下代码在活动 onCreate() 中启动的活动中删除动画:

overridePendingTransition(0, 0);

要查看源代码,请转到git

于 2015-10-20T12:15:05.497 回答
4

我正在使用如下方法。工作顺利。

public class MainActivity extends BaseActivity implements NavigationView.OnNavigationItemSelectedListener {

    private DrawerLayout drawerLayout;
    private MenuItem menuItemWaiting;

    /* other stuff here ... */

    private void setupDrawerLayout() {

        /* other stuff here ... */

        drawerLayout.addDrawerListener(new DrawerLayout.SimpleDrawerListener() {
            @Override
            public void onDrawerClosed(View drawerView) {
                super.onDrawerClosed(drawerView);
                if(menuItemWaiting != null) {
                    onNavigationItemSelected(menuItemWaiting);
                }
            }
        });

    }

    @Override
    public boolean onNavigationItemSelected(MenuItem menuItem) {

        menuItemWaiting = null;
        if(drawerLayout.isDrawerOpen(GravityCompat.START)) {
            menuItemWaiting = menuItem;
            drawerLayout.closeDrawers();
            return false;
        };

        switch(menuItem.getItemId()) {
            case R.id.drawer_action:
                startActivity(new Intent(this, SecondActivity.class));

            /* other stuff here ... */

        }
        return true;
    }
}

ActionBarDrawerToggle相同:

drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, R.string.drawer_open, R.string.drawer_close){
    @Override
    public void onDrawerClosed(View drawerView) {
        super.onDrawerClosed(drawerView);
        if(menuItemWaiting != null) {
            onNavigationItemSelected(menuItemWaiting);
        }
    }
};
drawerLayout.setDrawerListener(drawerToggle);
于 2016-04-22T13:08:45.483 回答
2

更好的方法是使用 onDrawerSlide(View, float) 方法并在 slideOffset 为 0 时启动 Activity。见下文

public void onDrawerSlide(View drawerView, float slideOffset) {
    if (slideOffset <= 0 && mPendingDrawerIntent != null) {
        startActivity(mPendingDrawerIntent);
        mPendingDrawerIntent = null;
    }
}

只需在 Drawer 的 ListView.OnItemClickListener onItemClick 方法中设置 mPendingDrawerIntent 即可。

于 2014-04-08T16:46:28.117 回答
0

这个答案适用于使用RxJavaRxBinding的人。想法是防止活动启动,直到抽屉关闭。NavigationView用于显示菜单。

public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener{

  private DrawerLayout drawer;

  private CompositeDisposable compositeDisposable;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // setup views and listeners (NavigationView.OnNavigationItemSelectedListener)

    compositeDisposable = new CompositeDisposable();
    compositeDisposable.add(observeDrawerClose());

  }

  // uncomment if second activitiy comes back to this one again
  /*
  @Override
  protected void onPause() {
      super.onPause();
      compositeDisposable.clear();
  }

  @Override
  protected void onResume() {
     super.onResume();
     compositeDisposable.add(observeDrawerClose());
  }*/

  @Override
  protected void onDestroy() {
    super.onDestroy();
    compositeDisposable.clear();
  }

  @Override
  public boolean onNavigationItemSelected(MenuItem item) {
    // Handle navigation view item clicks here.
    int id = item.getItemId();

    navSubject.onNext(id);

    drawer.closeDrawer(GravityCompat.START);
    return true;
  }

  private Disposable observeDrawerClose() {
    return RxDrawerLayout.drawerOpen(drawer, GravityCompat.START)
        .skipInitialValue() // this is important otherwise caused to zip with previous drawer event
        .filter(open -> !open)
        .zipWith(navSubject, new BiFunction<Boolean, Integer, Integer>() {
          @Override
          public Integer apply(Boolean aBoolean, Integer u) throws Exception {
            return u;
          }
        }).subscribe(id -> {
          if (id == R.id.nav_home) {
            // Handle the home action
          } else {

          }
        });
  }
}
于 2017-11-11T12:19:59.250 回答