Оптимизация скорости запуска и активности
Я использую Google DrawerLayout
.
Когда элемент получает щелчок, ящик плавно закрывается и запускается Activity
. Включение этих действий в Fragment
не является вариантом. Из-за этого запуск активности, а затем закрытие ящика также не является вариантом. Закрытие ящика и запуск активности в то же время заставят закрытие анимации заикаться.
Учитывая, что я хочу сначала сгладить его, а затем запустить действие, у меня возникла проблема с задержкой между тем, когда пользователь нажимает на элемент ящика, и когда они видят активность, к которой они хотели перейти.
Это то, что выглядит слушатель кликов для каждого элемента.
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-650 мс с момента вызова onClick, до момента окончания концаDrawerClosed.
Наблюдается заметное отставание, когда ящик закрывается, до запуска соответствующего действия.
Я понимаю, что происходит несколько вещей:
-
Заключительная анимация имеет место, что составляет пару миллисекунд прямо (скажем 300).
-
Затем, вероятно, есть некоторое задержка между визуальным закрытием ящика и его прослушивателем. Я пытаюсь выяснить, сколько из этого происходит посмотрев DrawerLayout
источник, но пока не понял.
-
Затем потребуется время, затрачиваемое на запущенную активность для выполнения своих жизненных циклов запуска, вплоть до onResume
. Я пока ничего не сделал, но я оцениваю около 200-300 м.
Это похоже на проблему, когда спуск по неправильному пути будет довольно дорогостоящим, поэтому я хочу убедиться, что полностью его понимаю.
Одним из решений является просто пропустить закрытие анимации, но я надеялся сохранить его.
Как я могу максимально уменьшить время перехода?
Ответы
Ответ 1
В соответствии с 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;
}
}
}
Ответ 2
Я столкнулся с той же проблемой с DrawerLayout.
У меня есть исследование для этого, а затем найдите для него одно приятное решение.
То, что я делаю, это.....
Если вы обратитесь к приложению Android Sample для DrawerLayout, тогда проверьте код для selectItem (position);
В этой функции на основе фрагмента выбора позиции вызывается. Я изменил его с помощью ниже кода в соответствии с моей потребностью и отлично работает без анимации, заикающейся.
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 миллисекунд. а затем мой обработчик вызовет фрагмент. Который работает гладко и согласно требованию.
Надеюсь, что это также поможет вам.
Наслаждайтесь кодированием...:)
Ответ 3
Итак, я, кажется, решил проблему с разумным решением.
Наибольшим источником воспринимаемой задержки была задержка между тем, когда ящик визуально закрыт, и когда был вызван 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();
}
}
Ответ 4
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.
Ответ 5
Я использую подход, как показано ниже. Работает плавно.
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);
Ответ 6
Лучшим подходом было бы использовать метод onDrawerSlide (View, float) и запустить Activity, как только slideOffset будет равен 0. См. ниже
public void onDrawerSlide(View drawerView, float slideOffset) {
if (slideOffset <= 0 && mPendingDrawerIntent != null) {
startActivity(mPendingDrawerIntent);
mPendingDrawerIntent = null;
}
}
Просто установите параметр mPendingDrawerIntent в методе Drawer ListView.OnItemClickListener onItemClick.
Ответ 7
Вот как я это делаю, не задавая какой-либо задержки,
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;
}
}
}
}
Ответ 8
Этот ответ предназначен для парней, которые используют 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 {
}
});
}
}