Android 上导航抽屉的卡顿问题

24

我在使用导航抽屉时遇到了问题,它太慢了。我正在寻找的解决方案是先关闭抽屉然后再显示活动页面,但是它没有起作用,显然我漏掉了什么。

private class DrawerItemClickListener implements ListView.OnItemClickListener {
        @Override
        public void onItemClick(AdapterView<?> parent, View view, int posicao, long id) {
            setLastPosition(posicao);
            setFragmentList(lastPosition);
            layoutDrawer.closeDrawer(linearDrawer);
        }
    }

    private OnClickListener userOnClick = new OnClickListener() {
        @Override
        public void onClick(View v) {
            layoutDrawer.closeDrawer(linearDrawer);
        }
    };

    private void setFragmentList(int posicao) {

        FragmentManager fragmentManager = getSupportFragmentManager();
        Fragment fragment = new FragmentViagens();

        switch (posicao) {

            case 0:
                fragmentManager.beginTransaction().replace(R.id.content_frame, fragment).commit();
                break;
            case 1:
                fragmentManager.beginTransaction().replace(R.id.content_frame, new FragmentPedidos()).commit();
                break;
            case 2:
                fragmentManager.beginTransaction().replace(R.id.content_frame, new FragmentClientes()).commit();
                break;

        }

        navigationAdapter.setChecked(posicao, true);
        setTitleFragments(lastPosition);
        navigationAdapter.resetarCheck();
        layoutDrawer.closeDrawer(linearDrawer);

    }
9个回答

43

你可以通过以下方式避免抽屉延迟,改变你的onItemClick

layoutDrawer.closeDrawer(linearDrawer);
setLastPosition(posicao);
new Handler().postDelayed(new Runnable() {
        @Override
        public void run() {
            setFragmentList(lastPosition);
        }
    }, 200);

编辑: 更好的做法应该是在DrawerLayout上设置DrawerListener,并将您的片段设置在 onDrawerClosed 方法中,像这样:

Fragment mFragmentToSet = null;

@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
    // Handle navigation view item clicks here.
    switch (item.getItemId()) {
        case R.id.nav_home:
            mFragmentToSet = HomeFragment.newInstance(); 
            break;
    }

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

mDrawerLayout.addDrawerListener(new DrawerLayout.DrawerListener() {
        @Override public void onDrawerSlide(View drawerView, float slideOffset) {}
        @Override public void onDrawerOpened(View drawerView) {}
        @Override public void onDrawerStateChanged(int newState) {}

        @Override
        public void onDrawerClosed(View drawerView) {
          //Set your new fragment here
          if (mFragmentToSet != null) {
            getSupportFragmentManager()
                  .beginTransaction()
                  .replace(FRAGMENT_CONTAINER_ID, mFragmentToSet)
                  .commit();
            mFragmentToSet = null;
          }
        }
    });

谢谢你的帮助,现在更快了,但仍然有一些延迟,这不应该发生。我怎样才能关闭抽屉然后展示活动呢?你能帮我吗? - AND4011002849
是的,你可以尝试将延迟从200改为250或300。 - Yuraj
它不应该因为这个延迟而出现滞后,问题可能出在其他地方。 - Yuraj
你也可以这样做,而且不需要延迟 https://dev59.com/OWMm5IYBdhLWcg3wm_7z#18979249 - Yuraj
1
不要使用postDelayed,它可能会导致app.FragmentManagerImpl.checkStateLoss异常。查看此博客 - 博客 - Pradip Vaghasiya

20

为什么不在DrawerLayout.DrawerListener的onDrawerClosed中执行事务,而不是在onClick中执行呢?


1
非常抱歉,我不理解,因为我在 Android 开发方面非常新手。 - AND4011002849
你也可以用另一种方式实现,但我更喜欢简单的解决方案——postDelay。 - Yuraj
我认为这种方式是正确的解决方案。使用延迟取决于设备性能。 - uDevel
如果您已经对自己正在做的事情有一些了解,则此答案是最好的。 - mdikici
不要忘记调用 drawerLayout.setDrawerListener(this),否则 onDrawerClosed() 方法都不会被调用!! - IgorGanapolsky

9
我认为我已经找到了最好的解决方案!
首先,像这样进行片段事务:
new Handler().post(new Runnable() {
                @Override
                public void run() {
                    getSupportFragmentManager()
                            .beginTransaction()
                            .setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out)
                            .replace(R.id.container, finalMenuFragment)
                            .commit();
                }
            });

我知道发布一个Runnable看起来毫无用处,但这个技巧可以避免抽屉(click animation on the drawer)的点击动画卡顿,特别是当你为可点击元素使用android:background="?attr/selectableItemBackground"时。然后在您的片段(onResume() function of your fragment)结尾关闭该抽屉,就像这样:(在这个示例中是“finalMenuFragment”)。
 new Handler().post(new Runnable() {
        public void run() {

            mDrawerLayout.closeDrawer(mFragmentContainerView);
        }
    });

我知道发布一个Runnable似乎很愚蠢,但它可以使关闭动画更加流畅。

这样,如果您想要流畅的动画,并且您的片段中没有很多视图,则抽屉将尽可能快地关闭,仍然不会出现延迟。

如果有人测试了这个解决方案,请给予一些反馈,希望能够帮到您!


3
FYI:Handler#postDelayed(Runnable, 0) 相当于 Handler#post(Runnable)。 - Julian Os

5

我只用一行代码就解决了这个问题:

在你的抽屉(Activity)中的清单文件(Menifest)中加入HardwareAccelerated true,防止打开抽屉时出现卡顿。

<activity
        android:name=".activity.YourDrawerActivity"
        android:configChanges="keyboardHidden|orientation"
        android:screenOrientation="portrait"
        android:hardwareAccelerated="true"  //important 
        android:theme="@style/AppTheme.NoActionBar"
        android:windowSoftInputMode="stateHidden" />

4
如果出现卡顿,您可以请求硬件加速。 如果清单中存在,请将其添加到您的活动中。
 android:hardwareAccelerated="true"

如果您的oncreate方法添加了这段代码:
getWindow().setFlags(
    WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
    WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);

现在您的活动中具备硬件加速功能。 来源:https://developer.android.com/guide/topics/graphics/hardware-accel.html


3

好的,你可以在关闭抽屉之前延迟几秒钟!

`new Handler().postDelayed(new Runnable() {
       @Override
          public void run() {

               mDrawerLayout.closeDrawer(containerView)
          }
   }, 300)`

对我有用!


0

试试这个。使用广播告诉抽屉关闭。这是为了确保在关闭抽屉之前,加载的活动或片段已经完成。

在MainActivity内部

private BroadcastReceiver xBroadcastReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        if (Drawer.isDrawerOpen(GravityCompat.START)){
            Drawer.closeDrawers();
        }
    }
};

@Override
protected void onStart() {
    super.onStart();
    IntentFilter filter = new IntentFilter();
    filter.addAction("my.drawer.listener");
    this.registerReceiver(xBroadcastReceiver, filter);
}

@Override
public void onDestroy() {
    super.onDestroy();
    this.unregisterReceiver(xBroadcastReceiver);
}

活动或片段内部

@Override
public void onStart() {
    super.onStart();
    Intent intent = new Intent();
    intent.setAction("my.drawer.listener");
    getActivity().sendBroadcast(intent);
}

你可以从onCreate或onAttach更改它到之前的进程,但这取决于你的应用程序。


0

我认为使用延迟来进行片段事务是最不稳定的解决方案。

在onDrawerClosed中执行它们会在关闭和片段出现之间产生轻微的延迟。

我更喜欢在导航单击后立即执行事务,然后在postDelayed 200ms(在可运行的检查中)后关闭抽屉。片段在抽屉开始关闭之前打开。

不仅从点击到抽屉关闭的延迟时间比抽屉关闭后下一个片段出现的时间短,而且对用户体验更好,因为用户正在从屏幕上移开手指-延迟时间不太明显。


0
请注意,当您的实现无法处理正确的情况时,您的OnBackpressed()方法将无法正常工作,就像一些人所经历的那样。
算法
1.声明一个类变量来检查是否选择了导航项。如果选择了一个项目,则将true分配给该变量,并在导航后将变量设置为false。
2.在您的类中实现NavigationView.OnNavigationItemSelectedListener并在onNavigationItemSelected()方法中处理您的导航。请注意,为了防止滞后,在onNavigationItemSelected()方法中添加DrawerListener(),最后在DrawerListener()的onDrawerClosed()方法中处理您的导航。
通过这种方式,只有在抽屉成功关闭时,您的片段或活动才会打开,这将防止不必要的滞后。
Java代码实现。
 public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener {

   private static boolean itemSelected;

   NavigationView navigationView;

   DrawerLayout drawer;


@Override
protected void onCreate(Bundle savedInstanceState){
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    Toolbar toolbar = findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);


    itemSelected =false;

    NavigationView navigationView = findViewById(R.id.your_navigation_view_id);

    DrawerLayout drawer = findViewById(R.id.your_drawer_id);


    }


@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {

    itemSelected = true;

    drawer.closeDrawer(GravityCompat.START);

    drawer.addDrawerListener(new DrawerLayout.DrawerListener() {
        @Override
        public void onDrawerSlide(@NonNull View drawerView, float slideOffset) {

        }

        @Override
        public void onDrawerOpened(@NonNull View drawerView) {

        }

        @Override
        public void onDrawerClosed(@NonNull View drawerView) {

            if(itemSelected) {

                if (item.getItemId() == R.id.profile_menu) {

                  //Open your Activity or Fragment here
                  startActivity(new Intent(MainActivity.this, ProfileActivity.class));

                } else if (item.getItemId() == R.id.nav_services_menu) {
           
                  //Open your Activity or Fragment here
                  startActivity(new Intent(MainActivity.this, Services.class));

              }

                itemSelected = false;

            }

        }

        @Override
        public void onDrawerStateChanged(int newState) {

        }
    });

    return true;

  }

}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接