优化抽屉和活动发射速度

时间:2013-08-20 19:01:00

标签: android navigation-drawer

我正在使用Google DrawerLayout

单击某个项目后,抽屉将平稳关闭,并将启动Activity。将这些活动转换为Fragment s 不是选项。因此,启动活动然后关闭抽屉也不是一种选择。关闭抽屉并同时启动活动将使关闭动画口吃。

鉴于我希望首先顺利关闭它,然后启动活动,我遇到了用户点击抽屉项目与他们看到他们想要去的活动之间的延迟问题。

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

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

我的活动也是DrawerListener,其onDrawerClosed方法如下:

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

onDrawerItemSelection只启动五项活动之一。

我对onPause的{​​{1}}无效。

我正在检测这个,从onClick调用的那一刻开始平均需要500-650ms,直到onDrawerClosed结束。

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

我意识到发生了一些事情:

  • 结束动画发生,就在那里几毫秒(比方说300)。

  • 然后在抽屉可视关闭和听众被解雇之间可能存在一些延迟。我正在试图弄清楚究竟发生了多少事情by looking at DrawerLayout source但尚未弄明白。

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

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

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

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

8 个答案:

答案 0 :(得分:42)

根据docs

  

避免在动画期间执行昂贵的操作,例如布局,因为它可能导致口吃;尝试在STATE_IDLE状态期间执行昂贵的操作。

您可以覆盖Handler onDrawerStateChanged(实现ActionBarDrawerToggle)的DrawerLayout.DrawerListener方法,而不是使用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; } } 并对时间延迟进行硬编码,这样您就可以在抽屉完全关闭时执行昂贵的操作。

在MainActivity中,

DrawerListener

onCreate中设置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;
        }
    }
}

最后,

{{1}}

答案 1 :(得分:27)

我在DrawerLayout面临同样的问题。

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

我在做的是......

如果您为DrawerLayout推荐Android Sample app,请检查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毫秒。然后我的处理程序将调用该片段。哪个工作顺利,并按照要求。

希望它对你也有帮助。

享受编码......:)

答案 2 :(得分:19)

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

可感知延迟的最大来源是抽屉在视觉上关闭和调用onDrawerClosed之间的延迟。我通过将Runnable发布到私有Handler来解决此问题,该私有onDrawerSlide会在指定的延迟时间内启动预期的活动。选择此延迟以与抽屉关闭相对应。

我尝试在80%的进度后启动onDrawerClosed,但这有两个问题。首先是它结结巴巴。第二个问题是,如果你将百分比增加到90%或95%,那么由于动画的性质增加,它根本不会被调用的可能性 - 然后你不得不回到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(); } } ,这违背了目的。

此解决方案可能会出现断断续续的情况,特别是在较旧的手机上,但仅通过将延迟增加到足够高就可以将可能性降低到0。我认为250ms是口吃和延迟之间的合理平衡。

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

{{1}}

答案 3 :(得分:8)

谷歌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

答案 4 :(得分: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);

答案 5 :(得分: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。

答案 6 :(得分:0)

以下是我如何做而不必定义任何延迟,

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
        LazyNavigationItemSelectedListener lazyNavigationItemSelectedListener =
            new LazyNavigationItemSelectedListener(this, drawer, "drawer_open", "drawer_close");
        drawer.addDrawerListener(navigationItemSelectedListener);
        lazyNavigationItemSelectedListener.syncState();

        NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
        navigationView.setNavigationItemSelectedListener(lazyNvigationItemSelectedListener);
    }
  .
  .
  .

}

并且LazyNavigationItemSelectedListener可以是MainActivity的内部类。

private class LazyNavigationItemSelectedListener extends ActionBarDrawerToggle
        implements NavigationView.OnNavigationItemSelectedListener {
    private int selectedMenuItemID;
    DrawerLayout drawer;

    private LazyNavigationItemSelectedListener(Activity activity, DrawerLayout drawerLayout, 
                                              @StringRes int openDrawerContentDescRes, 
                                              @StringRes int closeDrawerContentDescRes) {
        super(activity, drawerLayout, openDrawerContentDescRes, closeDrawerContentDescRes);
        this.drawer = drawerLayout;
    }

    @Override
    public boolean onNavigationItemSelected(@NonNull MenuItem item) {
        if (drawer.isDrawerOpen(GravityCompat.START)) {
            drawer.closeDrawer(GravityCompat.START);
        }
        selectedMenuItemID = item.getItemId();

        if (!(drawer.isDrawerOpen(GravityCompat.START))) {//only respond to call if drawer is closed.

            switch (selectedMenuItem) {

                case R.id.menu_item_id:
                    Intent intent1 = new Intent() //build your intent
                    startActivity(intent1);
                    break;
            }
        }
        return true;
    }

    @Override
    public void onDrawerClosed(View drawerView) {
        if (selectedMenuItemID > 0) {
            if (drawerView instanceof NavigationView) {
                NavigationView navigationView = (NavigationView) drawerView;
                //perform click on navigation item.
                navigationView.getMenu().performIdentifierAction(selectedMenuItemID, 0);
                selectedMenuItemID = -1;
            }
        }
    }
}

答案 7 :(得分: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 {

          }
        });
  }
}