2013-08-20 47 views
50

我正在使用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剛剛推出五大活動之一。

我對DrawerActivity的沒做任何事。

我正在測試這個,從onClick被調用的那一刻開始,平均需要500-650ms,直到onDrawerClosed結束。

一旦抽屜關閉,在相應的活動啓動之前,會有明顯的滯後。

我認識一對夫婦的事情正在發生:

  • 閉幕動畫發生,這是幾毫秒就在那裏(假設300)。

  • 然後,抽屜的視覺關閉和其監聽器之間可能會出現一些延遲。我試圖找出究竟有多少這種情況by looking at DrawerLayout source,但還沒有弄清楚。

  • 然後,啓動的活動執行其啓動生命週期方法所需的時間量達到幷包括onResume。我還沒有檢測到這一點,但我估計大約200-300毫秒。

這看起來像是一個問題,走錯路將會非常昂貴,所以我想確保我完全理解它。

一個解決方案就是跳過關閉動畫,但我希望能夠保持它。

如何儘可能減少我的轉換時間?

+0

*如何儘可能多的減少我的過渡時間儘可能* - 您可以使用'onDrawerSlide()'這樣https://gist.github.com/luksprog/6316295,我不知道會爲你節省多少。另外,你在'scheduleLaunchAndCloseDrawer(v);'和抽屜活動的'onPause()'中做什麼? – Luksprog

+0

inScheduleLaunchAndCloseDrawer我只是存儲對視圖的引用。我後來匹配它的ID以確定要啓動哪個活動。我什麼也不做。我試過在onDrawerSlide中做,但它也會結結巴巴。我試圖超過80%的某個門檻。 – yarian

+0

我想明天嘗試一下,發佈一個可運行的程序,以在某個預定的延遲時間(比如說350-400ms)啓動該程序。這可能仍然是結結巴巴的,本質上,目標是減少抽屜關閉和聽衆被解僱爲零之間的延遲。當我嘗試時我會更新問題。 – yarian

回答

37

根據docs

避免執行昂貴的操作,例如動畫,因爲它可引起口吃期間佈局;嘗試在STATE_IDLE狀態期間執行昂貴的操作。

而不是使用一個Handler和硬編碼的時間延遲,可以覆蓋的ActionBarDrawerToggleonDrawerStateChanged方法(它實現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; 
     } 
    } 
} 
+5

我相信這是更好的答案。它引用文檔並根據文檔中的建議實施一種方法。 +1 – drees

+2

但是,當使用這個,你應該立即運行第一個片段在第一個活動啓動 – Sheychan

+1

工作調用selectItem()?它是否被超類重寫? – Libathos

19

所以我似乎用合理的解決方案解決了這個問題。

可感知延遲的最大來源是當抽屜被視覺關閉時和調用onDrawerClosed時之間的延遲。我通過將Runnable發佈到專用Handler來解決此問題,該專用程序在某個指定的延遲時間啓動了預期的活動。該延遲被選擇爲與抽屜關閉相對應。

我試過在80%進展後啓動onDrawerSlide,但這有兩個問題。首先是它結結巴巴。第二種情況是,如果將百分比提高到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(); 
    } 
} 
+0

是你的用這個問題解決?我的答案按照您的要求運作良好。 –

+2

你是什麼意思?我在你之前發佈了這個答案。它與你的非常相似,但不會每次都創建一個Handler,這可以讓你取消以前的預定啓動。 – yarian

+0

你已經創建了獨立的類來處理它,而我只有通過創建處理程序才擁有它自己的類。你是對的,我也是這樣。享受編碼... –

27

我面臨同樣的問題與DrawerLayout。

我有研究,然後找到一個很好的解決方案。

我在做什麼.....

如果你指的Android示例應用程序的DrawerLayout然後檢查代碼選擇信息(位置);

在這個函數中基於位置選擇片段被調用。我已根據需要使用下面的代碼進行修改,並且沒有動畫關閉效果,可以正常工作。

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毫秒。然後我的處理程序會調用這個片段。哪些工作順利,並根據要求。

希望它對你也有幫助。

享受編碼... :)

+1

感謝您的回覆,這基本上就是我所做的,除非我重複使用相同的處理程序,以便在必要時刪除發佈的Runnable。看到我的答案。 – yarian

+0

這可以幫助我很多。 – rahstame

+0

希望它也能幫助@yarian。無論如何感謝評論。 –

2

更好的方法是使用onDrawerSlide(查看和float)方法,並開始活動,一旦slideOffset爲0。見下面

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

剛在Drawer的ListView.OnItemClickListener onItemClick方法中設置mPendingDrawerIntent。

6

谷歌IOsched 2015年運行的很平穩(除了設置)的原因這就是他們如何實施抽屜和他們如何拉unch的東西。

首先,他們使用處理程序來啓動延遲:

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

4

我正在使用類似下面的方法。工作順利。

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

我不喜歡延遲發佈的方法,我認爲你的解決方案更好;-) –

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

這個答案是誰使用RxJavaRxBinding傢伙。想法是爲了防止活動啓動,直到抽屜關閉。 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 { 

      } 
     }); 
    } 
}