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 Fragment
s 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?
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;
}
}
}
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 ... :)
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();
}
}
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 .
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);
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.
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 {
}
});
}
}
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;
}
}
}
}