Navigation drawer lags when opening and closing android

11,679

Solution 1

The lag is caused by two heavy operations (fragment replacement and drawer animation) happening in the same thread (main thread a.k.a. UI thread).

One way to address this problem is to call the replacement of fragments after the drawer completely closes. In this way the two operations will not happen simultaneously.

In addition, it's a nice design implementation to cross fade a progress bar with the fragment container.

Inside your Activity you should have something like this:

override fun onNavigationItemSelected(item: MenuItem): Boolean {

    // Show progress bar and hide content container
    crossfade(progressBar, container, false)

    drawerLayout?.addDrawerListener(object : DrawerLayout.DrawerListener {
        override fun onDrawerSlide(drawerView: View, slideOffset: Float) {}
        override fun onDrawerOpened(drawerView: View) {}
        override fun onDrawerStateChanged(newState: Int) {}
        override fun onDrawerClosed(drawerView: View) {
            // This method will be called after drawer animation finishes
            // Perform the fragment replacement
            when (item.itemId) {
                R.id.drawer_item_1 -> {
                    supportFragmentManager.beginTransaction()
                            .replace(R.id.container, MyFragmentOne.newInstance())
                            .addToBackStack(null)
                            .commit()
                }
                R.id.drawer_item_2 -> {
                    supportFragmentManager.beginTransaction()
                            .replace(R.id.container, MyFragmentTwo.newInstance())
                            .addToBackStack(null)
                            .commit()
                }
            }

            // Cross fade back the content container and hide progress bar
            crossfade(container, progressBar, false)

            // Remove this listener so close by, for example, swiping do not call it again
            drawerLayout.removeDrawerListener(this)
        }
    })

    // Closes the drawer, triggering the listener above
    drawerLayout.closeDrawer(GravityCompat.START)
    return true
}

private fun crossfade(viewIn: View, viewOut: View, animateViewOut: Boolean = true) {

    val crossfadeDuration = 200L

    // Set the content view to 0% opacity but visible, so that it is visible
    // (but fully transparent) during the animation.
    viewIn.alpha = 0f
    viewIn.visibility = View.VISIBLE
    viewIn.bringToFront()

    // Animate the in view to 100% opacity, and clear any animation
    // listener set on the view.
    viewIn.animate()
            .alpha(1f)
            .setDuration(crossfadeDuration)
            .setListener(null)

    // Animate the out view to 0% opacity. After the animation ends,
    // set its visibility to GONE as an optimization step (it won't
    // participate in layout passes, etc.)
    viewOut.animate()
            .alpha(0f)
            .setDuration(if (animateViewOut) crossfadeDuration else 0)
            .setListener(object : AnimatorListenerAdapter() {
                override fun onAnimationEnd(animation: Animator) {
                    viewOut.visibility = GONE
                }
            })

}

Inside your xml, you should have something like this:

...
<android.support.v4.widget.DrawerLayout
    android:id="@+id/drawerLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:openDrawer="start">

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <FrameLayout
            android:id="@+id/container"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:visibility="gone" />

        <ProgressBar
            android:id="@+id/progressBar"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:visibility="gone" />

    </FrameLayout>

    <android.support.design.widget.NavigationView
        android:id="@+id/navigationView"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        app:menu="@menu/drawer" />

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

Solution 2

Documentation: Click

Try this:
//Use Handler().postDelayed

  @Override
  public boolean onNavigationItemSelected(MenuItem item) {
    new Handler().postDelayed(new Runnable() {
        @Override
        public void run() {
             switch (item.getItemId()) {
                 case: R.id.example1:
                       // do something
                       break;
                 case: R.id.example2:
                       // do something
                       break;
                 default: // do something
        }
     }, 200);
     drawer.closeDrawer(GravityCompat.START);
     return true;
  }

In my humble opinion you need to avoid calling 'new' every click.

To fix this you can use constant values to navigation drawer's android:layout_width and android:layout_height attributes ie.

        android:layout_width="@dimen/navigation_drawer_width"
        android:layout_height="match_parent"

You may also want to enable hardware acceleration on activity in AndroidManifest.xml

          <activity
              android:name=".ui.SomeActivity"
              android:hardwareAccelerated="true" />
Share:
11,679
mard dean
Author by

mard dean

Updated on June 19, 2022

Comments

  • mard dean
    mard dean almost 2 years

    I am at trying to to get my navigation drawer to run smoothly as there is a stutter/delay in transition when opening and closing the drawer. I would like this to run perfectly. Any help?

    public class MainActivity extends AppCompatActivity
            implements NavigationView.OnNavigationItemSelectedListener {
    
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
            setSupportActionBar(toolbar);
    
            final MediaPlayer mp = MediaPlayer.create(this, R.raw.firsteps);
            mp.start();
    
    
            DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
            ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
                    this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
            drawer.setDrawerListener(toggle);
            toggle.syncState();
    
            NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
            navigationView.setItemIconTintList(null);
            navigationView.setNavigationItemSelectedListener(this);
    
    
            FragmentManager fm = getSupportFragmentManager();
            fm.beginTransaction().replace(R.id.content_frame, new MainFragment()).commit();
    
        }
    
        @Override
        public void onBackPressed() {
            DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
            if (drawer.isDrawerOpen(GravityCompat.START)) {
                drawer.closeDrawer(GravityCompat.START);
            } else {
                super.onBackPressed();
            }
        }
    
        @Override
        public boolean onCreateOptionsMenu(Menu menu) {
            // Inflate the menu; this adds items to the action bar if it is present.
            getMenuInflater().inflate(R.menu.main, menu);
            return true;
        }
    
        @Override
        public boolean onOptionsItemSelected(MenuItem item) {
            // Handle action bar item clicks here. The action bar will
            // automatically handle clicks on the Home/Up button, so long
            // as you specify a parent activity in AndroidManifest.xml.
            int id = item.getItemId();
    
            //noinspection SimplifiableIfStatement
            if (id == R.id.action_settings) {
                startActivity(new Intent(getApplicationContext(),Testing.class));
                return true;
            }
    
            return super.onOptionsItemSelected(item);
        }
    
        @SuppressWarnings("StatementWithEmptyBody")
        @Override
        public boolean onNavigationItemSelected(MenuItem item) {
    
            //final MediaPlayer mp = MediaPlayer.create(this, R.raw.xlophone);
    
            FragmentManager fm = getSupportFragmentManager();
    
            int id = item.getItemId();
    
            if (id == R.id.car_stories) {
    
                fm.beginTransaction().replace(R.id.content_frame,new ChildrensList()).commit();
                //mp.start();
            } else if (id == R.id.car_places) {
    
                fm.beginTransaction().replace(R.id.content_frame,new ImportFragment()).commit();
                //mp.start();
    
            } else if (id == R.id.nav_slideshow) {
    
            } else if (id == R.id.nav_manage) {
    
            } else if (id == R.id.nav_share) {
    
            } else if (id == R.id.nav_send) {
    
            }
    
            DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
            drawer.closeDrawer(GravityCompat.START);
            return true;
        }
    }
    
  • Milos
    Milos over 4 years
    Yeah handler helped me so I upvoted you, but this acceleration is not enhancing at all so that's it. I didnt understand you well with this calling new and const values.
  • Adi
    Adi over 4 years
    I went through various similiar questions in SO. But I found this answer to be perfect.
  • Vishal Vaishnav
    Vishal Vaishnav over 3 years
    OnBackPressed not working properly by using this code @Adi
  • Adi
    Adi over 3 years
    What do you mean by 'not properly'?