Оптимизация скорости запуска выдвижного ящика и активности


Я использую 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 на DrawerActivity.

Я инструментирование этого и занимает в среднем от 500-650ms с момента вызова onClick, до момента ondrawerclosed заканчивается.

существует заметное отставание, как только ящик закрывается, перед запуском соответствующей деятельности.

Я понимаю, что происходит несколько вещей:

  • анимация закрытия происходит, что составляет пару миллисекунд прямо там (скажем, 300).

  • то есть вероятно, некоторая задержка между визуальным закрытием ящика и увольнением его слушателя. Я пытаюсь выяснить, сколько именно это происходит глядя на DrawerLayout источник но еще не понял этого.

  • тогда есть количество времени, которое требуется для запущенного действия, чтобы выполнить его методы жизненного цикла запуска до, и в том числе,onResume. Я еще не инструментировал это, но я оцениваю около 200-300мс.

это похоже на проблему, когда идти по неправильному пути было бы довольно дорого, поэтому я хочу убедиться, что полностью понимаю это.

одно решение - просто пропустить заключительную анимацию, но я надеялся сохранить ее.

как я могу максимально уменьшить время перехода?

8 52

8 ответов:

по docs,

избегайте выполнения дорогостоящих операций, таких как макет во время анимации, поскольку это может вызвать заикание; попробуйте выполнить дорогостоящие операции во время состояния STATE_IDLE.

вместо Handler и жесткое кодирование временной задержки, вы можете переопределить onDrawerStateChanged метод ActionBarDrawerToggle (реализующий DrawerLayout.DrawerListener), так что вы можете выполнять дорогостоящие операции, когда ящик полностью закрытый.

Внутри 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;
    }
}

установить 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;
        }
    }
}

я столкнулся с той же проблемой с DrawerLayout.

У меня есть исследования для этого, а затем найти одно хорошее решение для него.

что я делаю.....

Если вы ссылаетесь на Android пример приложения для DrawerLayout затем проверьте код для selectItem (позиция);

в этой функции на основе выбора позиции вызывается фрагмент. Я изменил его с помощью кода ниже в соответствии с моей потребностью и отлично работает без анимации близко заикаться.

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 миллисекунд. и тогда мой обработчик вызовет фрагмент. Что работает ровно и согласно требованию.

надеюсь, что это будет полезно для вас.

Наслаждайтесь Кодирования... :)

так, я, кажется, решил проблему с разумным решением.

самым большим источником воспринимаемой задержки была задержка между тем, когда ящик был визуально закрыт, и когда onDrawerClosed называлась. Я решил это, разместив Runnable частный Handler который запускает запланированную деятельность с некоторой заданной задержкой. Эта задержка выбирается в соответствии с закрытием ящика.

Я пытался сделать запуск onDrawerSlide после 80% прогресса, но это имеет два проблемы. Во-первых, он заикался. Во-вторых, если вы увеличили процент до 90% или 95%, вероятность того, что он вообще не будет вызван из-за природы анимации, увеличилась-и вам пришлось вернуться к onDrawerClosed, который побеждает цель.

это решение имеет возможность заикаться, особенно на старых телефонах, но вероятность может быть уменьшена до 0, просто увеличив задержку достаточно высоко. Я думал, что 250 мс-это разумный баланс между заикание и задержка.

соответствующие части кода выглядят так:

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 работает чрезвычайно гладко (за исключением настроек) причина этого заключается в том, как они реализовали ящик и как они запускают материал.

первый из них использует обработчик для запуска с задержкой:

        // 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.

Я использую подход, как показано ниже. Работать гладко.

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

лучшим подходом было бы использовать метод onDrawerSlide(View, float) и запустить действие, как только slideOffset равен 0. Смотрите ниже

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

просто установите mPendingDrawerIntent в ListView ящика.Метод onItemClick OnItemClickListener.

вот как я это делаю без необходимости определять какие-либо задержки,

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

этот ответ для парней, которые используют RxJava и RxBinding. Идея состоит в том, чтобы предотвратить запуск активности до тех пор, пока ящик не закроется. 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 {

          }
        });
  }
}