web-dev-qa-db-ja.com

引き出しとアクティビティの起動速度を最適化する

Google DrawerLayoutを使用しています。

アイテムをクリックすると、引き出しがスムーズに閉じられ、Activityが起動します。これらのアクティビティをFragmentsに変えることは、notオプションです。このため、アクティビティを起動してからドロワーを閉じることもオプションではありません。ドロワーを閉じてアクティビティを同時に起動すると、アニメーションが閉じてしまいます。

最初にスムーズに閉じてからアクティビティを起動したい場合、ユーザーが引き出しアイテムをクリックしてから、行きたいアクティビティが表示されるまでの待ち時間に問題があります。

これが、各アイテムのクリックリスナーの外観です。

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は、5つのアクティビティのいずれかを起動するだけです。

onPauseDrawerActivityには何もしません。

これを実装しており、onClickが呼び出されてからonDrawerClosedが終了するまで、平均で500〜650ミリ秒かかります。

ドロワーを閉じると、対応するアクティビティが起動する前に、顕著な遅延があります。

いくつかのことが起こっていることに気付きました。

  • 終了アニメーションが発生します。これは、数ミリ秒です(たとえば300)。

  • おそらく、ドロワーが視覚的に閉じてからリスナーが起動されるまでに、おそらくある程度の遅延があります。私はこれがどれだけ起こっているのかを正確に把握しようとしています DrawerLayout sourceを見て まだ理解していません。

  • 次に、起動されたアクティビティがonResumeまでの起動ライフサイクルメソッドを実行するのにかかる時間があります。私はまだこれを計装していませんが、約200〜300ミリ秒と推定しています。

これは、間違ったパスをたどることが非常にコストがかかる問題のように思えます。

解決策の1つは、終了アニメーションをスキップすることですが、それを維持したいと考えていました。

移行時間をできるだけ短くするにはどうすればよいですか?

52
yarian

docs によると、

アニメーション中にレイアウトなどの高価な操作を実行すると、音が途切れることがあります。 STATE_IDLE状態の間に高価な操作を実行してみてください。

Handlerを使用して時間遅延をハードコーディングする代わりに、onDrawerStateChangedActionBarDrawerToggleメソッド(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;
    }
}

DrawerListeneronCreateを設定します。

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

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ミリ秒かかります。その後、ハンドラーがフラグメントを呼び出します。これはスムーズに、要件に従って動作します。

それがあなたにも役立つことを願っています。

コーディングをお楽しみください... :)

27

だから私は合理的な解決策で問題を解決したようです。

知覚可能な遅延の最大の原因は、ドロワーが視覚的に閉じられてからonDrawerClosedが呼び出されるまでの遅延でした。これを解決するには、RunnableをプライベートHandlerに投稿し、特定の遅延で意図したアクティビティを起動します。この遅延は、ドロワーのクローズに対応するように選択されます。

80%の進捗後にonDrawerSlideを起動しようとしましたが、これには2つの問題があります。 1つは、音が途切れることです。 2つ目は、パーセンテージを90%または95%に増やした場合、アニメーションの性質によりまったく呼び出されない可能性が高くなり、onDrawerClosedにフォールバックする必要があったことです。 、これは目的に反します。

このソリューションには、特に古い携帯電話でutter音が発生する可能性がありますが、遅延を十分に大きくするだけで、可能性を0に減らすことができます。 250msは、スタッターとレイテンシーの適切なバランスだと思いました。

コードの関連部分は次のようになります。

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は非常にスムーズに実行されます(設定を除く)。その理由は、ドロワーの実装方法と起動方法です。

最初に、ハンドラーを使用して遅延起動します:

        // 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 に移動します。

8
Warpzit

私は以下のようなアプローチを使用しています。スムーズに動作します。

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

より良いアプローチは、onDrawerSlide(View、float)メソ​​ッドを使用して、slideOffsetが0になったらアクティビティを開始することです。以下を参照してください。

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

引き出しのListView.OnItemClickListener onItemClickメソッドでmPendingDrawerIntentを設定するだけです。

2
oracleicom

この回答は、 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 {

          }
        });
  }
}
0

遅延の種類を定義せずにそれを行う方法は次のとおりです。

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