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

Я использую Google DrawerLayout.

При щелчке по элементу ящик плавно закрывается и запускается Activity. Превратить эти действия в Fragments — не вариант. Из-за этого запуск активности, а затем закрытие ящика также не вариант. Закрытие ящика и одновременный запуск активности приведет к заиканию анимации закрытия.

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

Вот как выглядит прослушиватель кликов для каждого элемента.

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 до момента завершения onDrawerClosed.

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

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

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

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

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

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

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

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


person yarian    schedule 20.08.2013    source источник
comment
Как я могу максимально сократить время перехода? — вы можете использовать onDrawerSlide() следующим образом: gist.github.com/luksprog/6316295 , я не знаю, сколько это вас спасет. Кроме того, что вы делаете в scheduleLaunchAndCloseDrawer(v); и в onPause() активности ящика?   -  person user    schedule 23.08.2013
comment
inScheduleLaunchAndCloseDrawer я просто сохраняю ссылку на представление. Позже я сопоставляю его идентификатор, чтобы определить, какую активность запускать. Я ничего не делаю на паузе. Я пытался сделать это в onDrawerSlide, но он также заикался. Я пытался сделать это выше определенного порога в 80%.   -  person yarian    schedule 23.08.2013
comment
У меня есть кое-что, что я хочу попробовать завтра, а именно публикация runnable для запуска активности с некоторой заранее определенной задержкой, скажем, 350-400 мс. Это все еще может заикаться, по сути, цель будет состоять в том, чтобы уменьшить задержку между закрытием ящика и запуском слушателя до нуля. Я обновлю вопрос, когда попробую.   -  person yarian    schedule 23.08.2013
comment
Это может быть решением, но публикация runnables через произвольные промежутки времени не кажется такой уж хорошей идеей. Вы также можете попробовать использовать Handler.postAtFrontOfQueue(Runnable) для публикации Runnable запуска действия в обратном вызове onDrawerClosed().   -  person user    schedule 23.08.2013
comment
Я согласен, что это звучит хакерски. Я попробую оба. Я подозреваю, что большая часть задержки связана с тем, что я визуально воспринимаю ящик как закрытый, когда Android вызывает onDrawerClosed(), что, я думаю, postAtFrontOfQueue не поможет исправить. Но я сделаю оба выстрела и отчитаюсь.   -  person yarian    schedule 24.08.2013
comment
Я должен упомянуть, что если я делаю публикацию с интервалом, это будет сделано для обработчика, принадлежащего DrawerActivity, таким образом, двойной щелчок не приведет к срабатыванию двух вещей и так далее.   -  person yarian    schedule 24.08.2013
comment
@Luksprog закончил публикацию. Проблема с postAtFrontOfQueue заключается в том, что он зависит от вызова onDrawerClosed, который был большой причиной задержки. Это решение отлично сработало бы, если бы onDrawerClosed вызывался сразу, но в цикле событий было много вещей, но в данном случае это не было большой проблемой. Смотрите ответ для более подробной информации.   -  person yarian    schedule 27.08.2013
comment
пожалуйста, найдите мой ответ на эту проблему [Открыть следующее действие только после того, как навигационный ящик завершит анимацию закрытия] [1] [1]: stackoverflow.com /a/29049221/2626901   -  person Vishal Makasana    schedule 14.03.2015


Ответы (7)


Согласно документам,

Избегайте выполнения дорогостоящих операций, таких как компоновка, во время анимации, так как это может вызвать заикание; попытаться выполнить дорогостоящие операции в состоянии 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;
        }
    }
}
person TheGreatOne    schedule 22.03.2015
comment
Я считаю, что это лучший ответ. Он ссылается на документы и реализует метод, основанный на рекомендациях, сделанных в документации. +1 - person drees; 29.07.2015
comment
Но при использовании этого вы должны немедленно запустить первый фрагмент, чтобы работать с запуском первого действия. - person Sheychan; 09.11.2015
comment
какая функция вызывает selectItem() ? это переопределяется суперклассом? - person Libathos; 13.02.2017
comment
@libathos mDrawerToggle.runWhenIdle(...) и mDrawerLayout.closeDrawers() - это точки, и неважно, где их использовать. - person Chan Chun Him; 27.03.2017
comment
@Zhang NS, вы вызываете runnable.run(), используя Runnable как простой интерфейс для функции, возможно, используете что-то еще или запускаете runnable в Handler.post, как и должно быть. - person alekshandru; 30.05.2018

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

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

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

Если вы ссылаетесь на пример приложения Android для 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 миллисекунд. и тогда мой обработчик вызовет фрагмент. Который работает гладко и согласно требованию.

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

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

person Shreyash Mahajan    schedule 28.08.2013
comment
Спасибо за ваш ответ, по сути, это то, что я сделал, за исключением того, что я повторно использовал тот же обработчик, чтобы при необходимости я мог удалить опубликованный Runnable. Смотрите мой ответ. - person yarian; 29.08.2013
comment
надеюсь, это также поможет @yarian. В любом случае спасибо за комментарий. - person Shreyash Mahajan; 02.09.2013
comment
Хорошо, это тоже поможет - person kevingoos; 18.04.2014

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

Наибольшим источником заметной задержки была задержка между визуальным закрытием ящика и вызовом 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();
    }
}
person yarian    schedule 27.08.2013
comment
ваша проблема решена с этим? Мой ответ работает хорошо в соответствии с вашим требованием. - person Shreyash Mahajan; 29.08.2013
comment
Что ты имеешь в виду? Я разместил этот ответ перед вашим. Он очень похож на ваш, но не создает каждый раз Handler, что позволяет отменить предыдущие запланированные запуски. - person yarian; 29.08.2013
comment
Вы создали отдельный класс для его обработки, а у меня есть собственный класс, создав только обработчик. Вы правы на своем пути, и я тоже на своем пути. Наслаждайтесь кодированием... - person Shreyash Mahajan; 29.08.2013
comment
Будьте осторожны с фиксацией транзакции фрагмента в onDrawerItemSelection(v), так как это вызовет исключение IllegalStateException, когда приложение останавливается сразу после того, как пользователь выбирает элемент ящика, но непосредственно перед закрытием ящика. - person Ciske; 25.03.2014
comment
@CiskeBoekelo Вы совершенно правы. Это решение специально для запуска действий. Если бы я использовал ящик для переключения между фрагментами, я бы, вероятно, немедленно выполнил транзакцию фрагмента, а затем скрыл ящик. - person yarian; 26.03.2014

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.

person Warpzit    schedule 20.10.2015

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

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);
person marioosh    schedule 22.04.2016
comment
Мне не нравится подход с задержкой публикации, я думаю, что ваше решение лучше ;-) - person Anton Makov; 25.03.2017

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

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

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

person oracleicom    schedule 08.04.2014

Этот ответ предназначен для тех, кто использует 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 {

          }
        });
  }
}
person Ruwanka Madhushan    schedule 11.11.2017