2011-08-28 35 views
14

Đây là vấn đề của tôi. Tôi có một ứng dụng mà tôi đang sử dụng ActionBar Sherlock với các tab, các đoạn với các menu tùy chọn. Mỗi khi tôi xoay trình mô phỏng, các menu được thêm vào cho tất cả các phân đoạn, ngay cả những phần được đặt/xóa (tôi đã thử cả hai).onCreateOptionsMenu đang được gọi quá nhiều lần trong ActionBar bằng cách sử dụng các tab

Đây là cài đặt: Một FragmentActivity, có một ActionBar với

final ActionBar bar = getSupportActionBar(); 

    bar.addTab(bar.newTab() 
     .setText("1") 
     .setTabListener(new MyTabListener(new FragmentList1()))); 

    bar.addTab(bar.newTab() 
     .setText("2") 
     .setTabListener(new MyTabListener(new FragmentList2()))); 

    bar.addTab(bar.newTab() 
     .setText("3") 
     .setTabListener(new MyTabListener(new FragmentList3()))); 

    bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); 
    bar.setDisplayShowHomeEnabled(true); 
    bar.setDisplayShowTitleEnabled(true); 

Các tab đều sử dụng Listener tương tự:

private class MyTabListener implements ActionBar.TabListener { 
    private final FragmentListBase m_fragment; 


    public MyTabListener(FragmentListBase fragment) { 
    m_fragment = fragment; 
    } 


    public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft) { 
    FragmentManager fragmentMgr = ActivityList.this.getSupportFragmentManager(); 
    FragmentTransaction transaction = fragmentMgr.beginTransaction(); 

     transaction.add(R.id.frmlyt_list, m_fragment, m_fragment.LIST_TAG); 

    transaction.commit(); 
    } 


    public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) { 
    FragmentManager fragmentMgr = ActivityList.this.getSupportFragmentManager(); 
    FragmentTransaction transaction = fragmentMgr.beginTransaction(); 

    transaction.remove(m_fragment); 
    transaction.commit(); 
    } 


    public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) { 
    } 
} 

Mỗi lớp con của FragmentListBase có thực đơn riêng của mình và do đó tất cả 3 lớp con có:

setHasOptionsMenu(true); 

và thích hợp

public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 
    Log.d(TAG, "OnCreateOptionsMenu"); 

    inflater.inflate(R.menu.il_options_menu, menu); 
} 

Khi tôi chạy ứng dụng tôi có thể thấy rằng onCreateOptionsMenu đang được gọi nhiều lần, cho tất cả các đoạn khác nhau.

Tôi hoàn toàn bối rối.

Tôi đã cố gắng đăng nhiều mã nhất có thể mà không bị áp đảo, nếu bạn thấy có điều gì đó bị thiếu, vui lòng khuyên.

[Chỉnh sửa] Tôi đã thêm ghi nhật ký nhiều hơn và hóa ra đoạn đó đang được đính kèm hai lần (hoặc nhiều hơn) khi xoay vòng. Một điều mà tôi nhận thấy là mọi thứ được gọi nhiều lần ngoại trừ phương thức onCreate() đang được gọi chỉ một lần.

06.704:/WindowManager(72): Setting rotation to 0, animFlags=0 
06.926:/ActivityManager(72): Config changed: { scale=1.0 imsi=310/260 loc=en_US touch=3 keys=1/1/2 nav=1/2 orien=L layout=0x10000014 uiMode=0x11 seq=35} 
07.374:/FragmentList1(6880): onAttach 
07.524:/FragmentList1(6880): onCreateView 
07.564:/FragmentList1(6880): onAttach 
07.564:/FragmentListBase(6880): onCreate 
07.564:/FragmentList1(6880): OnCreateOptionsMenu 
07.574:/FragmentList1(6880): OnCreateOptionsMenu 
07.604:/FragmentList1(6880): onCreateView 

[Chỉnh sửa 2]

Ok, tôi bắt đầu truy tìm trở lại vào mã Android và tìm thấy phần này ở đây (mà tôi chỉnh sửa để rút ngắn bài này).

/com_actionbarsherlock/src/android/support/v4/app/FragmentManager.java

public boolean dispatchCreateOptionsMenu(Menu menu, MenuInflater inflater) { 
    if (mActive != null) { 
     for (int i=0; i<mAdded.size(); i++) { 
      Fragment f = mAdded.get(i); 
      if (f != null && !f.mHidden && f.mHasMenu) { 
       f.onCreateOptionsMenu(menu, inflater); 
      } 
     } 
    } 

Vấn đề là mAdded không thực sự có nhiều trường hợp của FragmentList1 trong nó, vì vậy phương pháp onCreateOptionsMenu() là " chính xác "được gọi là 3 lần, nhưng đối với các trường hợp khác nhau của lớp FragmentList1. Điều tôi không hiểu là tại sao lớp học đó lại được thêm vào nhiều lần ... Nhưng đó là một địa ngục dẫn đầu tốt.

Trả lời

7

Tôi dường như đã tìm thấy (các) vấn đề. Tôi nói vấn đề (s) vì trên đầu trang của vô số các menu, bây giờ cũng có một ngoại lệ.

1) cuộc gọi đến

bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); 

đó là sau các cuộc gọi đến addTab() có một tác dụng phụ gọi onTabSelected(). Sau đó, TabListener của tôi sẽ thêm FragmentList1 vào FragmentManager

2) xoay thiết bị sẽ hủy Hoạt động như mong đợi nhưng sẽ không phá hủy Phân đoạn. Khi Hoạt động mới được tạo sau khi xoay, thao tác này sẽ thực hiện hai việc:

  1. tạo một tập hợp Fragment khác mà nó sẽ thêm vào FragmentManager. Đây là những gì đã gây ra vô số Menus
  2. gọi onTabSelected (thông qua setNavigationMode()) trong đó sẽ thực hiện đoạn mã sau:

    if (null != fragmentMgr.findFragmentByTag(m_fragment.LIST_TAG)) { 
        transaction.attach(m_fragment); 
        transaction.show(m_fragment); 
    } 
    else { 
        transaction.add(R.id.frmlyt_list, m_fragment, m_fragment.LIST_TAG); 
    } 
    

Về cơ bản nếu đoạn là đã có trong FragmentManager không có cần phải thêm nó, chỉ cần hiển thị nó. Nhưng có vấn đề. Nó không phải là cùng một mảnh! Đó là Fragment được tạo bởi phiên bản trước đó của Activity. Vì vậy, nó sẽ cố gắng đính kèm và hiển thị Fragment mới được tạo này sẽ gây ra một ngoại lệ

Giải pháp.

Có một số việc cần làm để khắc phục tất cả điều này.

1) Tôi đã chuyển setNavigationMode() phía trên addTab() s.

2) này là thế nào bây giờ tôi có thể tạo các tab của tôi:

FragmentListBase fragment = (FragmentListBase)fragmentMgr.findFragmentByTag(FragmentList1.LIST_TAG_STATIC); 
    if (null == fragment) { 
    fragment = new FragmentList1(); 
    } 
    bar.addTab(bar.newTab() 
     .setText("1") 
     .setTabListener(new MyTabListener(fragment))); 

Vì vậy, khi tạo Hoạt động Tôi phải kiểm tra để xem nếu mảnh vỡ đã có trong FragmentManager. Nếu họ là tôi sử dụng những trường hợp, nếu không thì tôi tạo ra những trường hợp mới. Điều này được thực hiện cho cả ba tab.

Bạn có thể nhận thấy rằng có hai nhãn tương tự: m_fragment.LIST_TAG và FragmentList1.LIST_TAG_STATIC. Ah, đây là đáng yêu ... (< - mỉa mai)

Trong ordrer sử dụng TagListener tôi polymorphically Tôi đã tuyên bố như sau biến tĩnh không trong lớp cơ sở:

public class FragmentListBase extends Fragment { 
    public String LIST_TAG = null; 
} 

Nó được gán từ bên trong hậu duệ và cho phép tôi xem trong FragmentManager cho các hậu duệ khác nhau của FragmentListBase. Nhưng tôi cũng cần phải tìm kiếm con cháu cụ thể TRƯỚC KHI chúng được tạo ra (vì tôi cần phải biết nếu tôi phải tạo ra chúng hay không), vì vậy tôi cũng phải khai báo biến tĩnh sau đây.

public class FragmentList1 extends FragmentListBase { 
    public final static String LIST_TAG_STATIC = "TAG_LIST_1"; 

    public FragmentList1() { 
     LIST_TAG = LIST_TAG_STATIC; 
    }; 
} 

đủ để nói rằng tôi disapointed mà không ai đã đưa ra giải pháp đơn giản và thanh lịch này (< - mỉa mai hơn)

Cảm ơn rất nhiều đến Jake Wharton người dành thời gian để xem xét điều này đối với tôi :)

+1

Câu trả lời này quá phức tạp. Chỉ cần di chuyển cuộc gọi đến 'bar.setNavigationMode (ActionBar.NAVIGATION_MODE_TABS)' trước khi thêm tab vào * ActionBar * đã khắc phục được sự cố của tôi. – Phil

6
public FragmentListBase() { 
    setRetainInstance(true); 
    setHasOptionsMenu(true); 
} 

Điều này sẽ lưu/khôi phục trạng thái riêng lẻ của từng đoạn khi xoay.


Một thay đổi đơn giản bạn có thể muốn làm là gọi transaction.replace(R.id.frmlyt_list, m_fragment, m_fragment.LIST_TAG) trong tab chọn gọi lại và loại bỏ các nội dung trong callback không được chọn.

+0

Cảm ơn cho câu trả lời Jake, tuy nhiên: nada. Tôi đã thêm vào mã nhưng không thay đổi. Tôi đã thêm nhiều thông tin vào câu hỏi của mình mặc dù ... – MikeWallaceDev

-4

ít nhất trên Honeycomb liên quan SDK là vấn đề được giải quyết bằng cách thêm

android:configChanges="orientation" 

đến việc khai hoạt động trong Andr của bạn tệp oidManifest.xml. Bạn vẫn có thể thêm và xóa các phân đoạn như được hiển thị trong phần Thêm tab của http://developer.android.com/guide/topics/ui/actionbar.html

0

Chỉ cần lưu ý khá về sự thất vọng về thẻ đa hình của bạn.

Khai báo lớp cơ sở của bạn như vậy:

public abstract class ListFragmentBase { 
    protected abstract String getListTag(); 
} 

Bây giờ tuyên bố lớp phụ một cái gì đó của bạn như thế này:

public class FragmentList1 extends ListFragmentBase { 
    public static final String LIST_TAG = "TAG_LIST_1"; 

    @Override 
    protected String getListTag() { 
     return LIST_TAG; 
    } 
} 

Bây giờ cách đa hình để có được thẻ dụ là như thế này:

ListFragmentBase frag = new FragmentList1(); 
frag.getListTag(); 

Lấy thẻ tĩnh như sau:

FragmentList1.LIST_TAG; 
+1

Bạn thậm chí có thể đặt chuỗi LIST_TAG tĩnh thành riêng tư để tránh nhầm lẫn về thuộc tính nào sẽ sử dụng. –

+0

@DominikvonWeber Bạn có thể đặt LIST_TAG thành riêng tư nhưng sau đó bạn không thể truy cập nó tĩnh. Nó sẽ phụ thuộc vào nhu cầu của bạn tôi đoán. Trong các giải pháp người dùng truy cập nó tĩnh – Dave

3

Tôi gặp vấn đề này rất giống với các menu "có thể xếp chồng" khi xoay. Tôi không sử dụng các tab nhưng tôi sử dụng ViewPager với FragmentStatePagerAdapter nên tôi không thể sử dụng lại các mảnh vỡ của mình. Sau khi đập đầu tôi trong 2 ngày, tôi tìm thấy giải pháp rất đơn giản. Thật vậy, vấn đề dường như là với onCreateOptionsMenu được gọi là nhiều lần. đoạn mã nhỏ này sẽ chăm sóc (mặt nạ?) của tất cả các vấn đề:

/** to prevent multiple calls to inflate menu */ 
private boolean menuIsInflated; 

@Override 
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) { 
    if (!menuIsInflated) { 
     inflater.inflate(R.menu.job_details_fragment_menu, menu); 
     menuIsInflated = true; 
    } 
} 
+0

sucks rằng chúng tôi đã phải làm điều này .. tôi thề đây là một lỗi về phía họ. – reidisaki

1

gì làm việc đối với tôi là cách di chuyển setHasMenuOptions (true) để hoạt động kêu gọi tức là hoạt động trong đó đoạn được tuyên bố. Trước đây tôi đã có nó trong phương thức onCreate của đoạn.

Dưới đây là đoạn mã:

@Override 
    protected void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 
     setContentView(R.layout.activity_main); 
     FragmentManager fragmentManager = getFragmentManager(); 
     FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); 

     ForecastFragment forecastFragment = new ForecastFragment(); 
     forecastFragment.setHasOptionsMenu(true); 
     fragmentTransaction.add(R.id.fragment, forecastFragment); 
     fragmentTransaction.commit(); 
    } 
+0

Không có câu trả lời nào ở trên phù hợp với tôi, nhưng câu trả lời của bạn đã làm. Cảm ơn! –

Các vấn đề liên quan