web-dev-qa-db-fra.com

Optimisation de la vitesse de lancement des tiroirs et des activités

J'utilise Google DrawerLayout.

Lorsqu'un élément est cliqué, le tiroir est bien fermé et un Activity sera lancé. Transformer ces activités en Fragments n'est pas pas une option. Pour cette raison, lancer une activité puis fermer le tiroir n'est pas non plus une option. La fermeture du tiroir et le lancement de l'activité en même temps rendra l'animation de fermeture saccadée.

Étant donné que je veux d'abord le fermer en douceur, puis lancer l'activité, j'ai un problème avec la latence entre le moment où un utilisateur clique sur l'élément du tiroir et le moment où il voit l'activité à laquelle il souhaite accéder.

Voici à quoi ressemble l'écouteur de clics pour chaque élément.

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

Mon activité est aussi le DrawerListener, sa méthode onDrawerClosed ressemble à:

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

onDrawerItemSelection lance juste l'une des cinq activités.

Je ne fais rien sur onPause du DrawerActivity.

J'instrumente cela et cela prend en moyenne de 500 à 650 ms à partir du moment où onClick est appelé, jusqu'au moment où onDrawerClosed se termine.

Il y a un décalage notable, une fois le tiroir fermé, avant le lancement de l'activité correspondante.

Je me rends compte que deux choses se produisent:

  • L'animation de fermeture a lieu, ce qui représente quelques millisecondes (disons 300).

  • Ensuite, il y a probablement une latence entre le tiroir qui se ferme visuellement et son auditeur qui se fait tirer. J'essaie de comprendre exactement combien cela se produit en regardant DrawerLayout source mais je ne l'ai pas encore compris.

  • Ensuite, il y a le temps nécessaire à l'activité lancée pour exécuter ses méthodes de cycle de vie de démarrage jusqu'à onResume inclus. Je n'ai pas encore instrumenté cela, mais j'estime environ 200 à 300 ms.

Cela semble être un problème où s'engager dans la mauvaise voie serait assez coûteux, donc je veux m'assurer de bien le comprendre.

Une solution consiste simplement à ignorer l'animation de clôture, mais j'espérais la conserver.

Comment puis-je réduire autant que possible mon temps de transition?

52
yarian

Selon le docs ,

Évitez d'effectuer des opérations coûteuses telles que la mise en page pendant l'animation car cela peut provoquer un bégaiement; essayez d'effectuer des opérations coûteuses pendant l'état STATE_IDLE.

Au lieu d'utiliser un Handler et de coder en dur la temporisation, vous pouvez remplacer la méthode onDrawerStateChanged de ActionBarDrawerToggle (qui implémente DrawerLayout.DrawerListener), afin que vous puissiez effectuer les opérations coûteuses lorsque le tiroir est complètement fermé.

Inside 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;
    }
}

Définissez DrawerListener dans onCreate:

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

Finalement,

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;
        }
    }
}
42
Zhang NS

J'étais confronté au même problème avec DrawerLayout.

J'ai des recherches pour cela et puis je trouve une bonne solution pour cela.

Ce que je fais c'est .....

Si vous faites référence à Android Exemple d'application pour DrawerLayout, vérifiez le code pour selectItem (position);

Dans cette fonction basée sur le fragment de sélection de position est appelé. Je l'ai modifié avec le code ci-dessous selon mes besoins et fonctionne très bien sans bégaiement d'animation proche.

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


}

Ici, je ferme d'abord le DrawerLayout. ce qui prend environ 250 millisecondes. puis mon gestionnaire appellera le fragment. Qui fonctionne en douceur et selon l'exigence.

J'espère que cela vous sera également utile.

Profitez du codage ... :)

27
Shreyash Mahajan

Il me semble donc avoir résolu le problème avec une solution raisonnable.

La plus grande source de latence perceptible était le délai entre le moment où le tiroir était fermé visuellement et le moment où onDrawerClosed était appelé. J'ai résolu ce problème en publiant un Runnable dans un Handler privé qui lance l'activité prévue à un certain délai spécifié. Ce délai est choisi pour correspondre à la fermeture du tiroir.

J'ai essayé de faire le lancement onDrawerSlide après 80% de progression, mais cela a deux problèmes. Le premier était qu'il bégayait. La seconde était que si vous augmentiez le pourcentage à 90% ou 95%, la probabilité qu'il ne soit pas appelé du tout en raison de la nature de l'animation augmentait - et vous deviez alors retomber sur onDrawerClosed , ce qui va à l'encontre de l'objectif.

Cette solution a la possibilité de bégayer, en particulier sur les téléphones plus anciens, mais la probabilité peut être réduite à 0 simplement en augmentant le délai suffisamment haut. Je pensais que 250 ms était un équilibre raisonnable entre le bégaiement et la latence.

Les parties pertinentes du code ressemblent à ceci:

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();
    }
}
19
yarian

Google IOsched 2015 fonctionne extrêmement bien (sauf à partir des paramètres), c'est pourquoi ils ont implémenté le tiroir et comment ils lancent des trucs.

Tout d'abord, ils utilisent un gestionnaire pour lancer différé:

        // 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);

avec retard étant:

private static final int NAVDRAWER_LAUNCH_DELAY = 250;

Une autre chose qu'ils font est de supprimer les animations des activités qui sont lancées avec le code suivant dans les activités onCreate ():

overridePendingTransition(0, 0);

Pour voir la source, allez à git .

8
Warpzit

J'utilise une approche comme ci-dessous. Fonctionne en douceur.

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;
    }
}

La même chose avec 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);
4
marioosh

Une meilleure approche serait d'utiliser la méthode onDrawerSlide (View, float) et de démarrer l'activité une fois que le slideOffset est à 0. Voir ci-dessous

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

Définissez simplement mPendingDrawerIntent dans la méthode ListView.OnItemClickListener du tiroir onItemClick.

2
oracleicom

Cette réponse s'adresse aux gars qui utilisent RxJava et RxBinding . L'idée est d'empêcher le lancement de l'activité jusqu'à la fermeture du tiroir. NavigationView est utilisé pour afficher le menu.

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 {

          }
        });
  }
}
0
Ruwanka Madhushan

Voici comment je le fais sans avoir à définir de retard,

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);
    }
  .
  .
  .

}

et le LazyNavigationItemSelectedListener peut être une classe interne de 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;
            }
        }
    }
}
0
Sabeeh