Ngăn xếp lại riêng cho từng tab trong Android bằng Fragment


158

Tôi đang cố gắng thực hiện các tab để điều hướng trong ứng dụng Android. Vì TabActivity và Activitygroup không được dùng nữa nên tôi muốn triển khai nó bằng Fragment thay thế.

Tôi biết cách thiết lập một đoạn cho mỗi tab và sau đó chuyển các đoạn khi một tab được nhấp. Nhưng làm thế nào tôi có thể có một ngăn xếp lại riêng cho mỗi tab?

Ví dụ: Đoạn A và B sẽ nằm dưới Tab 1 và Đoạn C và D trong Tab 2. Khi ứng dụng được khởi động Đoạn A được hiển thị và Tab 1 được chọn. Sau đó, Đoạn A có thể được thay thế bằng Đoạn B. Khi Tab 2 được chọn Đoạn C sẽ được hiển thị. Nếu Tab 1 được chọn thì Đoạn B sẽ được hiển thị lại. Tại thời điểm này, có thể sử dụng nút quay lại để hiển thị Fragment A.

Ngoài ra, điều quan trọng là trạng thái cho mỗi tab được duy trì khi xoay thiết bị.

BR Martin

Câu trả lời:


23

Khung hiện tại sẽ không làm điều này cho bạn tự động. Bạn sẽ cần xây dựng và quản lý các ngăn xếp riêng của mình cho mỗi tab.

Thành thật mà nói, đây có vẻ như là một điều thực sự đáng ngờ. Tôi không thể tưởng tượng nó dẫn đến một giao diện người dùng tốt - nếu phím trở lại sẽ làm những việc khác nhau tùy thuộc vào tab tôi, đặc biệt là nếu phím lùi cũng có hành vi bình thường là đóng toàn bộ hoạt động khi ở trên cùng của chồng ... nghe có vẻ khó chịu.

Nếu bạn đang cố gắng xây dựng một cái gì đó giống như giao diện người dùng trình duyệt web, để có được một UX tự nhiên cho người dùng sẽ có rất nhiều điều chỉnh tinh vi tùy thuộc vào ngữ cảnh, vì vậy bạn chắc chắn sẽ cần phải thực hiện ngăn xếp của riêng mình quản lý hơn là dựa vào một số thực hiện mặc định trong khung. Để biết ví dụ, hãy chú ý đến cách phím trở lại tương tác với trình duyệt tiêu chuẩn theo nhiều cách khác nhau mà bạn có thể vào và ra khỏi nó. (Mỗi "cửa sổ" trong trình duyệt về cơ bản là một tab.)


7
Đừng làm vậy. Và khuôn khổ hầu như không vô dụng. Nó không cung cấp cho bạn sự hỗ trợ tự động cho loại điều này, như tôi đã nói tôi không thể tưởng tượng được sẽ mang lại trải nghiệm tốt cho người dùng ngoại trừ trong các tình huống rất đặc biệt khi bạn sẽ cần phải kiểm soát cẩn thận hành vi trở lại.
hackbod

9
Loại điều hướng này, sau đó bạn có các tab và phân cấp các trang trên mỗi tab rất phổ biến cho các ứng dụng iPhone chẳng hạn (bạn có thể kiểm tra ứng dụng App Store và iPod). Tôi thấy trải nghiệm người dùng của họ khá tốt.
Dmitry Ryadnenko

13
Điều này là điên rồ. IPhone thậm chí không có nút quay lại. Có các bản trình diễn API hiển thị mã rất đơn giản để thực hiện các đoạn trong tab. Câu hỏi được đặt ra là về việc có các ngăn xếp khác nhau cho mỗi tab và câu trả lời của tôi là khung không cung cấp điều này một cách tự động bởi vì về mặt ngữ nghĩa, nút quay lại làm gì có thể là trải nghiệm người dùng nhảm nhí. Bạn có thể dễ dàng thực hiện trở lại ngữ nghĩa nếu bạn muốn.
hackbod

4
Một lần nữa, iPhone không có nút quay lại, do đó, về mặt ngữ nghĩa, nó không có hành vi xếp chồng lại như Android. Ngoài ra, "tốt hơn là chỉ gắn bó với các hoạt động và tiết kiệm thời gian cho bản thân" không ý nghĩa gì ở đây, bởi vì các hoạt động không cho phép bạn duy trì các tab trong giao diện người dùng với các ngăn xếp khác nhau của riêng họ; trong thực tế, việc quản lý ngăn xếp lại các hoạt động kém linh hoạt hơn so với những gì được cung cấp bởi khung Fragment.
hackbod

22
@hackbod Tôi đang cố gắng làm theo điểm của bạn, nhưng đã gặp sự cố khi thực hiện hành vi xếp chồng ngược tùy chỉnh. Tôi nhận ra rằng đã tham gia vào việc thiết kế này, bạn sẽ có cái nhìn sâu sắc về việc nó có thể dễ dàng như thế nào. Có thể có một ứng dụng demo cho trường hợp sử dụng của OP không, vì đây thực sự là một tình huống rất phổ biến, đặc biệt đối với những người trong chúng ta phải viết và chuyển các ứng dụng iOS cho các khách hàng thực hiện các yêu cầu này .... quản lý riêng backstacks mảnh trong mỗi FragmentActivity.
Richard Le Mesurier

138

Tôi cực kỳ muộn với câu hỏi này. Nhưng vì chủ đề này đã rất nhiều thông tin và hữu ích cho tôi, tôi nghĩ rằng tôi nên đăng hai xu của mình ở đây.

Tôi cần một luồng màn hình như thế này (Một thiết kế tối giản với 2 tab và 2 lượt xem trong mỗi tab),

tabA
    ->  ScreenA1, ScreenA2
tabB
    ->  ScreenB1, ScreenB2

Tôi đã có những yêu cầu tương tự trong quá khứ và tôi đã thực hiện nó bằng cách sử dụng TabActivityGroup (điều đó cũng không được chấp nhận vào thời điểm đó) và Hoạt động. Lần này tôi muốn sử dụng Fragment.

Vì vậy, đây là cách tôi thực hiện nó.

1. Tạo một lớp phân mảnh cơ sở

public class BaseFragment extends Fragment {
    AppMainTabActivity mActivity;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mActivity = (AppMainTabActivity) this.getActivity();
    }

    public void onBackPressed(){
    }

    public void onActivityResult(int requestCode, int resultCode, Intent data){
    }
}

Tất cả các đoạn trong ứng dụng của bạn có thể mở rộng lớp Cơ sở này. Nếu bạn muốn sử dụng các đoạn đặc biệt như ListFragmentbạn cũng nên tạo một lớp cơ sở cho điều đó. Bạn sẽ rõ về việc sử dụng onBackPressed()onActivityResult() nếu bạn đọc bài viết đầy đủ ..

2. Tạo một số định danh Tab, có thể truy cập ở mọi nơi trong dự án

public class AppConstants{
    public static final String TAB_A  = "tab_a_identifier";
    public static final String TAB_B  = "tab_b_identifier";

    //Your other constants, if you have them..
}

không có gì để giải thích ở đây ..

3. Ok, Hoạt động của Tab chính- Vui lòng xem qua các nhận xét trong mã ..

public class AppMainFragmentActivity extends FragmentActivity{
    /* Your Tab host */
    private TabHost mTabHost;

    /* A HashMap of stacks, where we use tab identifier as keys..*/
    private HashMap<String, Stack<Fragment>> mStacks;

    /*Save current tabs identifier in this..*/
    private String mCurrentTab;

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.app_main_tab_fragment_layout);

        /*  
         *  Navigation stacks for each tab gets created.. 
         *  tab identifier is used as key to get respective stack for each tab
         */
        mStacks             =   new HashMap<String, Stack<Fragment>>();
        mStacks.put(AppConstants.TAB_A, new Stack<Fragment>());
        mStacks.put(AppConstants.TAB_B, new Stack<Fragment>());

        mTabHost                =   (TabHost)findViewById(android.R.id.tabhost);
        mTabHost.setOnTabChangedListener(listener);
        mTabHost.setup();

        initializeTabs();
    }


    private View createTabView(final int id) {
        View view = LayoutInflater.from(this).inflate(R.layout.tabs_icon, null);
        ImageView imageView =   (ImageView) view.findViewById(R.id.tab_icon);
        imageView.setImageDrawable(getResources().getDrawable(id));
        return view;
    }

    public void initializeTabs(){
        /* Setup your tab icons and content views.. Nothing special in this..*/
        TabHost.TabSpec spec    =   mTabHost.newTabSpec(AppConstants.TAB_A);
        mTabHost.setCurrentTab(-3);
        spec.setContent(new TabHost.TabContentFactory() {
            public View createTabContent(String tag) {
                return findViewById(R.id.realtabcontent);
            }
        });
        spec.setIndicator(createTabView(R.drawable.tab_home_state_btn));
        mTabHost.addTab(spec);


        spec                    =   mTabHost.newTabSpec(AppConstants.TAB_B);
        spec.setContent(new TabHost.TabContentFactory() {
            public View createTabContent(String tag) {
                return findViewById(R.id.realtabcontent);
            }
        });
        spec.setIndicator(createTabView(R.drawable.tab_status_state_btn));
        mTabHost.addTab(spec);
    }


    /*Comes here when user switch tab, or we do programmatically*/
    TabHost.OnTabChangeListener listener    =   new TabHost.OnTabChangeListener() {
      public void onTabChanged(String tabId) {
        /*Set current tab..*/
        mCurrentTab                     =   tabId;

        if(mStacks.get(tabId).size() == 0){
          /*
           *    First time this tab is selected. So add first fragment of that tab.
           *    Dont need animation, so that argument is false.
           *    We are adding a new fragment which is not present in stack. So add to stack is true.
           */
          if(tabId.equals(AppConstants.TAB_A)){
            pushFragments(tabId, new AppTabAFirstFragment(), false,true);
          }else if(tabId.equals(AppConstants.TAB_B)){
            pushFragments(tabId, new AppTabBFirstFragment(), false,true);
          }
        }else {
          /*
           *    We are switching tabs, and target tab is already has atleast one fragment. 
           *    No need of animation, no need of stack pushing. Just show the target fragment
           */
          pushFragments(tabId, mStacks.get(tabId).lastElement(), false,false);
        }
      }
    };


    /* Might be useful if we want to switch tab programmatically, from inside any of the fragment.*/
    public void setCurrentTab(int val){
          mTabHost.setCurrentTab(val);
    }


    /* 
     *      To add fragment to a tab. 
     *  tag             ->  Tab identifier
     *  fragment        ->  Fragment to show, in tab identified by tag
     *  shouldAnimate   ->  should animate transaction. false when we switch tabs, or adding first fragment to a tab
     *                      true when when we are pushing more fragment into navigation stack. 
     *  shouldAdd       ->  Should add to fragment navigation stack (mStacks.get(tag)). false when we are switching tabs (except for the first time)
     *                      true in all other cases.
     */
    public void pushFragments(String tag, Fragment fragment,boolean shouldAnimate, boolean shouldAdd){
      if(shouldAdd)
          mStacks.get(tag).push(fragment);
      FragmentManager   manager         =   getSupportFragmentManager();
      FragmentTransaction ft            =   manager.beginTransaction();
      if(shouldAnimate)
          ft.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left);
      ft.replace(R.id.realtabcontent, fragment);
      ft.commit();
    }


    public void popFragments(){
      /*    
       *    Select the second last fragment in current tab's stack.. 
       *    which will be shown after the fragment transaction given below 
       */
      Fragment fragment             =   mStacks.get(mCurrentTab).elementAt(mStacks.get(mCurrentTab).size() - 2);

      /*pop current fragment from stack.. */
      mStacks.get(mCurrentTab).pop();

      /* We have the target fragment in hand.. Just show it.. Show a standard navigation animation*/
      FragmentManager   manager         =   getSupportFragmentManager();
      FragmentTransaction ft            =   manager.beginTransaction();
      ft.setCustomAnimations(R.anim.slide_in_left, R.anim.slide_out_right);
      ft.replace(R.id.realtabcontent, fragment);
      ft.commit();
    }   


    @Override
    public void onBackPressed() {
        if(mStacks.get(mCurrentTab).size() == 1){
          // We are already showing first fragment of current tab, so when back pressed, we will finish this activity..
          finish();
          return;
        }

        /*  Each fragment represent a screen in application (at least in my requirement, just like an activity used to represent a screen). So if I want to do any particular action
         *  when back button is pressed, I can do that inside the fragment itself. For this I used AppBaseFragment, so that each fragment can override onBackPressed() or onActivityResult()
         *  kind of events, and activity can pass it to them. Make sure just do your non navigation (popping) logic in fragment, since popping of fragment is done here itself.
         */
        ((AppBaseFragment)mStacks.get(mCurrentTab).lastElement()).onBackPressed();

        /* Goto previous fragment in navigation stack of this tab */
            popFragments();
    }


    /*
     *   Imagine if you wanted to get an image selected using ImagePicker intent to the fragment. Ofcourse I could have created a public function
     *  in that fragment, and called it from the activity. But couldn't resist myself.
     */
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if(mStacks.get(mCurrentTab).size() == 0){
            return;
        }

        /*Now current fragment on screen gets onActivityResult callback..*/
        mStacks.get(mCurrentTab).lastElement().onActivityResult(requestCode, resultCode, data);
    }
}

4. app_main_tab_fragment_layout.xml (Trong trường hợp bất kỳ ai quan tâm.)

<?xml version="1.0" encoding="utf-8"?>
<TabHost
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@android:id/tabhost"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent">

        <FrameLayout
            android:id="@android:id/tabcontent"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:layout_weight="0"/>

        <FrameLayout
            android:id="@+android:id/realtabcontent"
            android:layout_width="fill_parent"
            android:layout_height="0dp"
            android:layout_weight="1"/>

        <TabWidget
            android:id="@android:id/tabs"
            android:orientation="horizontal"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_weight="0"/>

    </LinearLayout>
</TabHost>

5. AppTabAFirstFragment.java (Đoạn đầu tiên trong Tab A, simliar cho tất cả các Tab)

public class AppTabAFragment extends BaseFragment {
    private Button mGotoButton;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        View view       =   inflater.inflate(R.layout.fragment_one_layout, container, false);

        mGoToButton =   (Button) view.findViewById(R.id.goto_button);
        mGoToButton.setOnClickListener(listener);

        return view;
    }

    private OnClickListener listener        =   new View.OnClickListener(){
        @Override
        public void onClick(View v){
            /* Go to next fragment in navigation stack*/
            mActivity.pushFragments(AppConstants.TAB_A, new AppTabAFragment2(),true,true);
        }
    }
}

Đây có thể không phải là cách đánh bóng và chính xác nhất. Nhưng nó đã làm việc rất đẹp trong trường hợp của tôi. Ngoài ra tôi chỉ có yêu cầu này trong chế độ dọc. Tôi chưa bao giờ phải sử dụng mã này trong một dự án hỗ trợ cả hai định hướng. Vì vậy, không thể nói những loại thách thức tôi phải đối mặt ở đó ..

BIÊN TẬP :

Nếu bất cứ ai muốn một dự án đầy đủ, tôi đã đẩy một dự án mẫu lên github .


2
Lưu trữ dữ liệu cho mọi phân đoạn, tạo lại mỗi một trong số chúng, xây dựng lại các ngăn xếp ... rất nhiều công việc cho một sự thay đổi định hướng đơn giản.
Michael Eilers Smith

3
@omegatai hoàn toàn đồng ý với bạn .. Tất cả các vấn đề phát sinh kể từ khi Android không quản lý ngăn xếp cho chúng ta ( mà iOS không và thay đổi hướng hoặc tab với nhiều mảnh vỡ là một làn gió ) và đó lấy lại chúng ta đến cuộc thảo luận ban đầu trong Q này / Một chủ đề. Không có gì tốt để quay lại bây giờ ..
Krishnabhadra

1
@Renjith Điều này là do đoạn được tạo lại mỗi lần, khi bạn chuyển tab .. Đừng nghĩ dù chỉ một lần, đoạn của bạn được sử dụng lại qua chuyển đổi tab. Khi tôi chuyển từ tab A sang B, tab A được giải phóng khỏi bộ nhớ. Vì vậy, hãy lưu dữ liệu của bạn vào hoạt động và mỗi lần kiểm tra xem hoạt động có dữ liệu hay không trước khi thử lấy dữ liệu từ máy chủ.
Krishnabhadra

2
@Krishnabhadra Ok, nghe hay hơn nhiều. Hãy để tôi sửa trong trường hợp tôi sai. Theo ví dụ của bạn, chỉ có một hoạt động và do đó một gói. Tạo các phiên bản bộ điều hợp trong BaseFragment (giới thiệu dự án của bạn) và lưu dữ liệu trong đó. Sử dụng chúng bất cứ khi nào xem được xây dựng.
Đổi mới

1
Có nó để làm việc. Cảm ơn rất nhiều. Tải lên toàn bộ dự án là một ý tưởng tốt! :-)
Vinay W

96

Chúng tôi đã phải thực hiện chính xác hành vi tương tự mà bạn mô tả cho một ứng dụng gần đây. Các màn hình và dòng chảy chung của ứng dụng đã được xác định nên chúng tôi phải gắn bó với nó (đó là một bản sao ứng dụng iOS ...). May mắn thay, chúng tôi đã tìm cách loại bỏ các nút quay lại trên màn hình :)

Chúng tôi đã hack giải pháp bằng cách sử dụng hỗn hợp TabActivity, FragmentActivities (chúng tôi đang sử dụng thư viện hỗ trợ cho các mảnh) và Fragment. Nhìn lại, tôi khá chắc chắn rằng đó không phải là quyết định kiến ​​trúc tốt nhất, nhưng chúng tôi đã xoay sở để khiến mọi thứ hoạt động. Nếu tôi phải làm lại, có lẽ tôi sẽ thử thực hiện một giải pháp dựa trên hoạt động nhiều hơn (không có đoạn) hoặc thử và chỉ có một Hoạt động cho các tab và để tất cả phần còn lại là lượt xem (mà tôi thấy là nhiều hơn tái sử dụng hơn các hoạt động tổng thể).

Vì vậy, các yêu cầu là phải có một số tab và màn hình có thể lồng trong mỗi tab:

tab 1
  screen 1 -> screen 2 -> screen 3
tab 2
  screen 4
tab 3
  screen 5 -> 6

Vân vân...

Vì vậy, nói: người dùng bắt đầu trong tab 1, điều hướng từ màn hình 1 đến màn hình 2 rồi đến màn hình 3, sau đó anh ta chuyển sang tab 3 và điều hướng từ màn hình 4 đến 6; nếu chuyển trở lại tab 1, anh ta sẽ nhìn thấy màn hình 3 lần nữa và nếu anh ta nhấn Back, anh ta sẽ quay lại màn hình 2; Quay lại lần nữa và anh ta ở màn hình 1; chuyển sang tab 3 và anh ta lại ở màn hình 6.

Hoạt động chính trong ứng dụng là MainTabActivity, mở rộng TabActivity. Mỗi tab được liên kết với một hoạt động, giả sử ActivityInTab1, 2 và 3. Và sau đó mỗi màn hình sẽ là một đoạn:

MainTabActivity
  ActivityInTab1
    Fragment1 -> Fragment2 -> Fragment3
  ActivityInTab2
    Fragment4
  ActivityInTab3
    Fragment5 -> Fragment6

Mỗi ActivityInTab chỉ giữ một đoạn duy nhất tại một thời điểm và biết cách thay thế một đoạn này cho một đoạn khác (khá giống với Actvitygroup). Điều thú vị là nó khá dễ dàng để sắp xếp các ngăn xếp riêng biệt cho mỗi tab theo cách này.

Chức năng cho mỗi ActivityInTab khá giống nhau: biết cách điều hướng từ đoạn này sang đoạn khác và duy trì ngăn xếp phía sau, vì vậy chúng tôi đặt nó vào một lớp cơ sở. Hãy gọi nó đơn giản là ActivityInTab:

abstract class ActivityInTab extends FragmentActivity { // FragmentActivity is just Activity for the support library.

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_in_tab);
    }

    /**
     * Navigates to a new fragment, which is added in the fragment container
     * view.
     * 
     * @param newFragment
     */
    protected void navigateTo(Fragment newFragment) {
        FragmentManager manager = getSupportFragmentManager();
        FragmentTransaction ft = manager.beginTransaction();

        ft.replace(R.id.content, newFragment);

        // Add this transaction to the back stack, so when the user presses back,
        // it rollbacks.
        ft.addToBackStack(null);
        ft.commit();
    }

}

Activity_in_tab.xml chỉ là thế này:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/content"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:isScrollContainer="true">
</RelativeLayout>

Như bạn có thể thấy, bố cục khung nhìn cho mỗi tab là như nhau. Đó là bởi vì nó chỉ là một FrameLayout được gọi là nội dung sẽ chứa từng phân đoạn. Các mảnh vỡ là những mảnh có mỗi màn hình.

Chỉ để nhận điểm thưởng, chúng tôi cũng đã thêm một số mã nhỏ để hiển thị hộp thoại xác nhận khi người dùng nhấn Back và không còn đoạn nào để quay lại:

// In ActivityInTab.java...
@Override
public void onBackPressed() {
    FragmentManager manager = getSupportFragmentManager();
    if (manager.getBackStackEntryCount() > 0) {
        // If there are back-stack entries, leave the FragmentActivity
        // implementation take care of them.
        super.onBackPressed();
    } else {
        // Otherwise, ask user if he wants to leave :)
        showExitDialog();
    }
}

Đó là khá nhiều thiết lập. Như bạn có thể thấy, mỗi FragmentActivity (hoặc chỉ đơn giản là Hoạt động trong Android> 3) đang chăm sóc tất cả các bản sao lưu với FragmentManager của chính nó.

Một hoạt động như ActivityInTab1 sẽ rất đơn giản, nó sẽ chỉ hiển thị đoạn đầu tiên (tức là màn hình):

public class ActivityInTab1 extends ActivityInTab {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        navigateTo(new Fragment1());
    }
}

Sau đó, nếu một mảnh cần điều hướng đến một mảnh khác, nó phải thực hiện một vài thao tác khó chịu ... nhưng điều đó không tệ:

// In Fragment1.java for example...
// Need to navigate to Fragment2.
((ActivityIntab) getActivity()).navigateTo(new Fragment2());

Vì vậy, đó là khá nhiều đó. Tôi khá chắc chắn rằng đây không phải là một giải pháp rất kinh điển (và hầu như chắc chắn không tốt lắm), vì vậy tôi muốn hỏi các nhà phát triển Android dày dạn về cách tiếp cận tốt hơn để đạt được chức năng này và nếu đây không phải là "cách nó thực hiện" trong Android, tôi đánh giá cao nếu bạn có thể chỉ cho tôi một số liên kết hoặc tài liệu nào giải thích đó là các cách Android tiếp cận này (tab, màn hình lồng nhau trong các tab, vv). Hãy xé ra câu trả lời này trong các ý kiến ​​:)

Như một dấu hiệu cho thấy giải pháp này không tốt lắm là gần đây tôi đã phải thêm một số chức năng điều hướng vào ứng dụng. Một số nút kỳ quái sẽ đưa người dùng từ tab này sang tab khác và vào màn hình lồng nhau. Làm điều đó theo lập trình là một nỗi đau ở mông, bởi vì ai biết vấn đề và xử lý khi nào các mảnh và hoạt động thực sự được khởi tạo và khởi tạo. Tôi nghĩ mọi chuyện sẽ dễ dàng hơn nhiều nếu những màn hình và tab đó chỉ là Lượt xem thực sự.


Cuối cùng, nếu bạn cần tồn tại thay đổi định hướng, điều quan trọng là các mảnh của bạn được tạo bằng setArgument / getArgument. Nếu bạn đặt các biến đối tượng trong các hàm tạo của đoạn của bạn, bạn sẽ bị vặn. Nhưng may mắn thay, điều đó thực sự dễ sửa: chỉ cần lưu mọi thứ trong setArgument trong hàm tạo và sau đó lấy những thứ đó bằng getArgument trong onCreate để sử dụng chúng.


13
Câu trả lời tuyệt vời nhưng tôi nghĩ rất ít người sẽ thấy điều này. Tôi đã chọn chính xác cùng một con đường (như bạn có thể thấy từ cuộc trò chuyện trong câu trả lời trước) và tôi không hài lòng với nó giống như bạn. Tôi nghĩ rằng Google thực sự gặp rắc rối với các đoạn này vì API này không bao gồm các trường hợp sử dụng chính. Một vấn đề khác bạn có thể gặp phải là không thể nhúng mảnh này vào mảnh khác.
Dmitry Ryadnenko

Cảm ơn các bình luận. Vâng, tôi không thể đồng ý nhiều hơn về API phân đoạn. Tôi đã gặp phải vấn đề về các mảnh được lồng vào nhau (đó là lý do tại sao chúng tôi đã đi theo cách tiếp cận "thay thế một mảnh bằng một mảnh khác" hehe).
dịch

1
Tôi đã thực hiện điều này thông qua TẤT CẢ các hoạt động. Tôi không thích những gì tôi có và tôi sẽ thử Fragment. Điều đó trái ngược với kinh nghiệm của bạn! Có rất nhiều triển khai với Hoạt động để xử lý vòng đời của chế độ xem trẻ em trong mỗi tab và cũng để thực hiện nút quay lại của riêng bạn. Ngoài ra, bạn không thể chỉ giữ một tham chiếu cho tất cả các chế độ xem hoặc bạn sẽ làm nổ tung bộ nhớ. Tôi hy vọng Fragment sẽ: 1) Hỗ trợ vòng đời của Fragment với việc phân tách bộ nhớ rõ ràng và 2) giúp thực hiện chức năng nút quay lại Plus, nếu bạn sử dụng các đoạn cho quá trình này thì có dễ chạy hơn trên Tablets không?
gregm

Điều gì xảy ra khi người dùng chuyển tab? Liệu backstack Fragment có bị xóa không? Làm thế nào để đảm bảo backstack vẫn còn?
gregm

1
@gregm Nếu bạn đi 1 tab <-> 1 hoạt động như tôi đã làm, backstack cho mỗi tab sẽ vẫn còn khi các tab được chuyển đổi vì các hoạt động thực sự được giữ nguyên; họ chỉ được tạm dừng và tiếp tục. Tôi không biết có cách nào để làm cho các hoạt động bị hủy và được tạo lại khi các tab được chuyển sang TabActivity. Tuy nhiên, nếu bạn làm cho các mảnh vỡ bên trong các hoạt động được thay thế như tôi đề xuất, chúng sẽ bị hủy (và được tạo lại khi backstack được bật lên). Vì vậy, bạn sẽ có nhiều nhất một mảnh còn sống trên mỗi tab bất cứ lúc nào.
dịch


6

Lưu trữ các tài liệu tham khảo mạnh mẽ cho các mảnh vỡ không phải là cách chính xác.

FragmentManager cung cấp putFragment(Bundle, String, Fragment)saveFragmentInstanceState(Fragment).

Một trong hai là đủ để thực hiện một backstack.


Sử dụng putFragment, thay vì thay thế một Mảnh vỡ, bạn tách rời cái cũ và thêm cái mới. Đây là những gì khuôn khổ thực hiện đối với một giao dịch thay thế được thêm vào backstack. putFragmentlưu trữ một chỉ mục vào danh sách các Đoạn đang hoạt động hiện tại và các Đoạn đó được lưu bởi khung trong khi thay đổi định hướng.

Cách thứ hai, sử dụng saveFragmentInstanceState, lưu toàn bộ trạng thái phân đoạn vào một Gói cho phép bạn thực sự loại bỏ nó, thay vì tách ra. Sử dụng phương pháp này làm cho ngăn xếp phía sau dễ thao tác hơn, vì bạn có thể bật một mảnh vỡ bất cứ khi nào bạn muốn.


Tôi đã sử dụng phương thức thứ hai cho usecase này:

SignInFragment ----> SignUpFragment ---> ChooseBTDeviceFragment
               \                          /
                \------------------------/

Tôi không muốn người dùng quay lại màn hình Đăng ký, từ cái thứ ba, bằng cách nhấn nút quay lại. Tôi cũng làm hoạt hình lật giữa chúng (sử dụngonCreateAnimation ), vì vậy các giải pháp hacky sẽ không hoạt động, ít nhất là không có người dùng nhận thấy rõ ràng có gì đó không đúng.

Đây là trường hợp sử dụng hợp lệ cho backstack tùy chỉnh, thực hiện những gì người dùng mong đợi ...

private static final String STATE_BACKSTACK = "SetupActivity.STATE_BACKSTACK";

private MyBackStack mBackStack;

@Override
protected void onCreate(Bundle state) {
    super.onCreate(state);

    if (state == null) {
        mBackStack = new MyBackStack();

        FragmentManager fm = getSupportFragmentManager();
        FragmentTransaction tr = fm.beginTransaction();
        tr.add(R.id.act_base_frg_container, new SignInFragment());
        tr.commit();
    } else {
        mBackStack = state.getParcelable(STATE_BACKSTACK);
    }
}

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putParcelable(STATE_BACKSTACK, mBackStack);
}

private void showFragment(Fragment frg, boolean addOldToBackStack) {
    final FragmentManager fm = getSupportFragmentManager();
    final Fragment oldFrg = fm.findFragmentById(R.id.act_base_frg_container);

    FragmentTransaction tr = fm.beginTransaction();
    tr.replace(R.id.act_base_frg_container, frg);
    // This is async, the fragment will only be removed after this returns
    tr.commit();

    if (addOldToBackStack) {
        mBackStack.push(fm, oldFrg);
    }
}

@Override
public void onBackPressed() {
    MyBackStackEntry entry;
    if ((entry = mBackStack.pop()) != null) {
        Fragment frg = entry.recreate(this);

        FragmentManager fm = getSupportFragmentManager();
        FragmentTransaction tr = fm.beginTransaction();
        tr.replace(R.id.act_base_frg_container, frg);
        tr.commit();

        // Pop it now, like the framework implementation.
        fm.executePendingTransactions();
    } else {
        super.onBackPressed();
    }
}

public class MyBackStack implements Parcelable {

    private final List<MyBackStackEntry> mList;

    public MyBackStack() {
        mList = new ArrayList<MyBackStackEntry>(4);
    }

    public void push(FragmentManager fm, Fragment frg) {
        push(MyBackStackEntry.newEntry(fm, frg);
    }

    public void push(MyBackStackEntry entry) {
        if (entry == null) {
            throw new NullPointerException();
        }
        mList.add(entry);
    }

    public MyBackStackEntry pop() {
        int idx = mList.size() - 1;
        return (idx != -1) ? mList.remove(idx) : null;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        final int len = mList.size();
        dest.writeInt(len);
        for (int i = 0; i < len; i++) {
            // MyBackStackEntry's class is final, theres no
            // need to use writeParcelable
            mList.get(i).writeToParcel(dest, flags);
        }
    }

    protected MyBackStack(Parcel in) {
        int len = in.readInt();
        List<MyBackStackEntry> list = new ArrayList<MyBackStackEntry>(len);
        for (int i = 0; i < len; i++) {
            list.add(MyBackStackEntry.CREATOR.createFromParcel(in));
        }
        mList = list;
    }

    public static final Parcelable.Creator<MyBackStack> CREATOR =
        new Parcelable.Creator<MyBackStack>() {

            @Override
            public MyBackStack createFromParcel(Parcel in) {
                return new MyBackStack(in);
            }

            @Override
            public MyBackStack[] newArray(int size) {
                return new MyBackStack[size];
            }
    };
}

public final class MyBackStackEntry implements Parcelable {

    public final String fname;
    public final Fragment.SavedState state;
    public final Bundle arguments;

    public MyBackStackEntry(String clazz, 
            Fragment.SavedState state,
            Bundle args) {
        this.fname = clazz;
        this.state = state;
        this.arguments = args;
    }

    public static MyBackStackEntry newEntry(FragmentManager fm, Fragment frg) {
        final Fragment.SavedState state = fm.saveFragmentInstanceState(frg);
        final String name = frg.getClass().getName();
        final Bundle args = frg.getArguments();
        return new MyBackStackEntry(name, state, args);
    }

    public Fragment recreate(Context ctx) {
        Fragment frg = Fragment.instantiate(ctx, fname);
        frg.setInitialSavedState(state);
        frg.setArguments(arguments);
        return frg;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(fname);
        dest.writeBundle(arguments);

        if (state == null) {
            dest.writeInt(-1);
        } else if (state.getClass() == Fragment.SavedState.class) {
            dest.writeInt(0);
            state.writeToParcel(dest, flags);
        } else {
            dest.writeInt(1);
            dest.writeParcelable(state, flags);
        }
    }

    protected MyBackStackEntry(Parcel in) {
        final ClassLoader loader = getClass().getClassLoader();
        fname = in.readString();
        arguments = in.readBundle(loader);

        switch (in.readInt()) {
            case -1:
                state = null;
                break;
            case 0:
                state = Fragment.SavedState.CREATOR.createFromParcel(in);
                break;
            case 1:
                state = in.readParcelable(loader);
                break;
            default:
                throw new IllegalStateException();
        }
    }

    public static final Parcelable.Creator<MyBackStackEntry> CREATOR =
        new Parcelable.Creator<MyBackStackEntry>() {

            @Override
            public MyBackStackEntry createFromParcel(Parcel in) {
                return new MyBackStackEntry(in);
            }

            @Override
            public MyBackStackEntry[] newArray(int size) {
                return new MyBackStackEntry[size];
            }
    };
}

2

Tuyên bố từ chối trách nhiệm:


Tôi cảm thấy đây là nơi tốt nhất để đăng một giải pháp liên quan mà tôi đã làm cho một loại vấn đề tương tự có vẻ là công cụ Android khá chuẩn. Nó sẽ không giải quyết vấn đề cho tất cả mọi người, nhưng nó có thể giúp một số người.


Nếu sự khác biệt chính giữa các mảnh của bạn chỉ là dữ liệu sao lưu chúng (nghĩa là không có nhiều khác biệt về bố cục lớn), thì bạn có thể không cần thực sự thay thế đoạn đó, mà chỉ trao đổi dữ liệu bên dưới và làm mới chế độ xem.

Dưới đây là mô tả về một ví dụ có thể có cho phương pháp này:

Tôi có một ứng dụng sử dụng ListViews. Mỗi mục trong danh sách là một phụ huynh có một số con. Khi bạn nhấn vào mục đó, một danh sách mới cần mở với những đứa trẻ đó, trong cùng tab ActionBar như danh sách ban đầu. Các danh sách lồng nhau này có bố cục rất giống nhau (một số điều chỉnh có điều kiện ở đây và có lẽ), nhưng dữ liệu thì khác.

Ứng dụng này có một số lớp con bên dưới danh sách cha mẹ ban đầu và chúng tôi có thể có hoặc không có dữ liệu từ máy chủ vào thời điểm người dùng cố gắng truy cập bất kỳ độ sâu nhất định nào ngoài độ sâu đầu tiên. Bởi vì danh sách được xây dựng từ một con trỏ cơ sở dữ liệu và các đoạn sử dụng trình tải con trỏ và bộ điều hợp con trỏ để điền vào chế độ xem danh sách với các mục danh sách, tất cả những gì cần xảy ra khi đăng ký nhấp là:

1) Tạo bộ điều hợp mới với các trường 'đến' và 'từ' phù hợp sẽ khớp với các chế độ xem mục mới được thêm vào danh sách và các cột được trả về bởi con trỏ mới.

2) Đặt bộ điều hợp này làm bộ điều hợp mới cho ListView.

3) Xây dựng một URI mới dựa trên mục được nhấp và khởi động lại trình tải con trỏ bằng URI mới (và phép chiếu). Trong ví dụ này, URI được ánh xạ tới các truy vấn cụ thể với các đối số lựa chọn được truyền từ UI.

4) Khi dữ liệu mới đã được tải từ URI, hoán đổi con trỏ được liên kết với bộ điều hợp sang con trỏ mới và sau đó danh sách sẽ được làm mới.

Không có backstack liên quan đến điều này vì chúng tôi không sử dụng giao dịch, vì vậy bạn sẽ phải tự xây dựng hoặc chơi các truy vấn ngược lại khi thoát khỏi hệ thống phân cấp. Khi tôi thử điều này, các truy vấn đủ nhanh để tôi chỉ thực hiện lại chúng trong oNBackPression () cho đến khi tôi ở trên cùng của hệ thống phân cấp, lúc đó khung sẽ chiếm lại nút quay lại.

Nếu bạn thấy mình trong một tình huống tương tự, hãy đảm bảo đọc các tài liệu: http://developer.android.com/guide/topics/ui/layout/listview.html

http://developer.android.com/reference/android/support/v4/app/LoaderManager.LoaderCallbacks.html

Tôi hi vọng điêu nay se giup được ai đo!


Trong trường hợp bất cứ ai đang làm điều này, và cũng sử dụng Trình phân tích mục (chẳng hạn như Bảng chữ cái), bạn có thể nhận thấy rằng sau khi thay thế bộ điều hợp, cuộn nhanh của bạn không hoạt động. Một loại lỗi đáng tiếc, nhưng thay thế bộ điều hợp, ngay cả với một bộ chỉ mục hoàn toàn mới, không cập nhật danh sách các phần được sử dụng bởi FastScroll. Có một cách giải quyết, vui lòng xem: mô tả vấn đềcách giải quyết
Courtf

2

Tôi đã có chính xác cùng một vấn đề và đã triển khai một dự án github mã nguồn mở bao gồm tab được xếp chồng, điều hướng quay lại và lên và được kiểm tra và ghi lại tốt:

https://github.com/SebastianBaltesObjectCode/PersistentFragmentTabs

Đây là một khung đơn giản và nhỏ cho các tab điều hướng và chuyển đổi đoạn và xử lý điều hướng lên và xuống. Mỗi tab có chồng mảnh vỡ riêng. Nó sử dụng ActionBarSherlock và tương thích trở lại API cấp 8.


2

Đây là một vấn đề phức tạp vì Android chỉ xử lý 1 stack back, nhưng điều này là khả thi. Tôi đã mất nhiều ngày để tạo ra một thư viện có tên Tab Stacker thực hiện chính xác những gì bạn đang tìm kiếm: một lịch sử phân đoạn cho mỗi tab. Nó là nguồn mở và được ghi lại đầy đủ, và có thể được bao gồm dễ dàng với lớp. Bạn có thể tìm thấy thư viện trên github: https://github.com/smart-fun/TabStacker

Bạn cũng có thể tải xuống ứng dụng mẫu để thấy rằng hành vi tương ứng với nhu cầu của bạn:

https://play.google.com/apps/testing/fr.arnaudguyon.tabstackerapp

Nếu bạn có bất kỳ câu hỏi nào, đừng ngần ngại bỏ thư.


2

Tôi muốn đề xuất giải pháp của riêng tôi trong trường hợp ai đó đang tìm kiếm và muốn thử và chọn giải pháp tốt nhất cho nhu cầu của anh ấy / cô ấy.

https://github.com/drusak/tabactivity

Mục đích của việc tạo thư viện khá tầm thường - thực hiện nó như iPhone.

Những ưu điểm chính:

  • sử dụng thư viện android.support.design với TabLayout;
  • mỗi tab có ngăn xếp riêng bằng FragmentManager (không lưu các tham chiếu của các đoạn);
  • hỗ trợ liên kết sâu (khi bạn cần mở tab cụ thể và cấp độ phân đoạn cụ thể trong đó);
  • lưu / khôi phục trạng thái của các tab;
  • phương pháp vòng đời thích ứng của các mảnh trong tab;
  • khá dễ dàng để thực hiện cho nhu cầu của bạn.

Cảm ơn bạn, điều này đã khá hữu ích. Tôi cần sử dụng ListFragments ngoài Fragments để tôi sao chép BaseTabFragment.java thành BaseTabListFragment.java và nó đã mở rộng ListFragment. Sau đó, tôi đã phải thay đổi các phần khác nhau trong mã nơi nó luôn được giả định để mong đợi một BaseTabFragment. Có cách nào tốt hơn?
Primehalo 30/03/2016

Thật không may, đã không nghĩ về ListFragment. Về mặt kỹ thuật, đây là giải pháp phù hợp, nhưng nó sẽ yêu cầu kiểm tra bổ sung cho TabFragment và instanceOf BaseTabListFragment của nó. Một cách tiếp cận khác để sử dụng Fragment với ListView bên trong (giống hệt như ListFragment đã ngụ ý). Tôi sẽ suy nghĩ về nó. Cảm ơn đã chỉ cho tôi về điều đó!
kasurd

1

Một giải pháp đơn giản:

Mỗi khi bạn thay đổi cuộc gọi tab / xem gốc:

fragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);

Nó sẽ xóa BackStack. Hãy nhớ gọi nó trước khi bạn thay đổi đoạn gốc.

Và thêm các đoạn với điều này:

FragmentTransaction transaction = getFragmentManager().beginTransaction();
NewsDetailsFragment newsDetailsFragment = NewsDetailsFragment.newInstance(newsId);
transaction.add(R.id.content_frame, newsDetailsFragment).addToBackStack(null).commit();

Lưu ý .addToBackStack(null)transaction.addví dụ có thể được thay đổi với transaction.replace.


-1

Chủ đề này rất rất thú vị và hữu ích.
Cảm ơn Krishnabhadra về lời giải thích và mã của bạn, tôi sử dụng mã của bạn và cải thiện một chút, cho phép duy trì các ngăn xếp, currentTab, v.v ... từ thay đổi cấu hình (chủ yếu là xoay vòng).
Đã thử nghiệm trên thiết bị 4.0.4 và 2.3.6 thực, không được thử nghiệm trên trình giả lập

Tôi thay đổi phần mã này trên "AppMainTabActivity.java", phần còn lại giữ nguyên. Có lẽ Krishnabhadra sẽ thêm điều này vào mã của mình.

Khôi phục dữ liệu trênCreate:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.app_main_tab_fragment_layout);

    /*  
     *  Navigation stacks for each tab gets created..
     *  tab identifier is used as key to get respective stack for each tab
     */

  //if we are recreating this activity...
    if (savedInstanceState!=null) {
         mStacks = (HashMap<String, Stack<Fragment>>) savedInstanceState.get("stack");
         mCurrentTab = savedInstanceState.getString("currentTab");
    }
    else {
    mStacks = new HashMap<String, Stack<Fragment>>();
    mStacks.put(AppConstants.TAB_A, new Stack<Fragment>());
    mStacks.put(AppConstants.TAB_B, new Stack<Fragment>());

    }

    mTabHost = (TabHost)findViewById(android.R.id.tabhost);
    mTabHost.setup();

    initializeTabs();

  //set the listener the last, to avoid overwrite mCurrentTab everytime we add a new Tab
    mTabHost.setOnTabChangedListener(listener);
}

Lưu các biến và đưa vào Gói:

 //Save variables while recreating
@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putSerializable("stack", mStacks);
    outState.putString("currentTab", mCurrentTab);
    //outState.putInt("tabHost",mTabHost);
}

Nếu tồn tại một CurrentTab trước đó, hãy đặt cái này, nếu không thì tạo một Tab_A mới:

public void initializeTabs(){
    /* Setup your tab icons and content views.. Nothing special in this..*/
    TabHost.TabSpec spec    =   mTabHost.newTabSpec(AppConstants.TAB_A);

    spec.setContent(new TabHost.TabContentFactory() {
        public View createTabContent(String tag) {
            return findViewById(R.id.realtabcontent);
        }
    });
    spec.setIndicator(createTabView(R.drawable.tab_a_state_btn));
    mTabHost.addTab(spec);


    spec                    =   mTabHost.newTabSpec(AppConstants.TAB_B);
    spec.setContent(new TabHost.TabContentFactory() {
        public View createTabContent(String tag) {
            return findViewById(R.id.realtabcontent);
        }
    });
    spec.setIndicator(createTabView(R.drawable.tab_b_state_btn));
    mTabHost.addTab(spec);

//if we have non default Tab as current, change it
    if (mCurrentTab!=null) {
        mTabHost.setCurrentTabByTag(mCurrentTab);
    } else {
        mCurrentTab=AppConstants.TAB_A;
        pushFragments(AppConstants.TAB_A, new AppTabAFirstFragment(), false,true);
    }
}

Tôi hy vọng điều này sẽ giúp những người khác.


Cái này sai. Khi onCreate được gọi với Bundle, các mảnh đó sẽ không giống với những mảnh sẽ xuất hiện trên màn hình và bạn sẽ rò rỉ những cái cũ, trừ khi bạn đang sử dụng setRetainInstance. Và nếu Trình quản lý hoạt động "lưu" Hoạt động của bạn, do một Đoạn không thể tuần tự hóa cũng không thể Parcelable, khi người dùng quay lại Hoạt động của bạn, nó sẽ bị sập.
sergio91pt

-1

Tôi khuyên bạn không nên sử dụng backstack dựa trên HashMap> có rất nhiều lỗi trong chế độ "không giữ hoạt động". Nó sẽ không khôi phục chính xác trạng thái trong trường hợp bạn nằm sâu trong ngăn xếp của mảnh. Và cũng sẽ được chia thành từng mảnh bản đồ lồng nhau (với ngoại lệ: Không tìm thấy đoạn nào cho ID). Coz HashMap> sau ứng dụng nền \ foreground sẽ là null

Tôi tối ưu hóa mã ở trên để làm việc với backstack của đoạn

Đây là TabView dưới cùng

Lớp hoạt động chính

import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.Window;
import android.widget.ImageView;
import android.widget.TabHost;
import android.widget.TextView;

import com.strikersoft.nida.R;
import com.strikersoft.nida.abstractActivity.BaseActivity;
import com.strikersoft.nida.screens.tags.mapTab.MapContainerFragment;
import com.strikersoft.nida.screens.tags.searchTab.SearchFragment;
import com.strikersoft.nida.screens.tags.settingsTab.SettingsFragment;

public class TagsActivity extends BaseActivity {
    public static final String M_CURRENT_TAB = "M_CURRENT_TAB";
    private TabHost mTabHost;
    private String mCurrentTab;

    public static final String TAB_TAGS = "TAB_TAGS";
    public static final String TAB_MAP = "TAB_MAP";
    public static final String TAB_SETTINGS = "TAB_SETTINGS";

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getWindow().requestFeature(Window.FEATURE_ACTION_BAR);
        getActionBar().hide();
        setContentView(R.layout.tags_activity);

        mTabHost = (TabHost) findViewById(android.R.id.tabhost);

        mTabHost.setup();

        if (savedInstanceState != null) {
            mCurrentTab = savedInstanceState.getString(M_CURRENT_TAB);
            initializeTabs();
            mTabHost.setCurrentTabByTag(mCurrentTab);
            /*
            when resume state it's important to set listener after initializeTabs
            */
            mTabHost.setOnTabChangedListener(listener);
        } else {
            mTabHost.setOnTabChangedListener(listener);
            initializeTabs();
        }
    }

    private View createTabView(final int id, final String text) {
        View view = LayoutInflater.from(this).inflate(R.layout.tabs_icon, null);
        ImageView imageView = (ImageView) view.findViewById(R.id.tab_icon);
        imageView.setImageDrawable(getResources().getDrawable(id));
        TextView textView = (TextView) view.findViewById(R.id.tab_text);
        textView.setText(text);
        return view;
    }

    /*
    create 3 tabs with name and image
    and add it to TabHost
     */
    public void initializeTabs() {

        TabHost.TabSpec spec;

        spec = mTabHost.newTabSpec(TAB_TAGS);
        spec.setContent(new TabHost.TabContentFactory() {
            public View createTabContent(String tag) {
                return findViewById(R.id.realtabcontent);
            }
        });
        spec.setIndicator(createTabView(R.drawable.tab_tag_drawable, getString(R.string.tab_tags)));
        mTabHost.addTab(spec);

        spec = mTabHost.newTabSpec(TAB_MAP);
        spec.setContent(new TabHost.TabContentFactory() {
            public View createTabContent(String tag) {
                return findViewById(R.id.realtabcontent);
            }
        });
        spec.setIndicator(createTabView(R.drawable.tab_map_drawable, getString(R.string.tab_map)));
        mTabHost.addTab(spec);


        spec = mTabHost.newTabSpec(TAB_SETTINGS);
        spec.setContent(new TabHost.TabContentFactory() {
            public View createTabContent(String tag) {
                return findViewById(R.id.realtabcontent);
            }
        });
        spec.setIndicator(createTabView(R.drawable.tab_settings_drawable, getString(R.string.tab_settings)));
        mTabHost.addTab(spec);

    }

    /*
    first time listener will be trigered immediatelly after first: mTabHost.addTab(spec);
    for set correct Tab in setmTabHost.setCurrentTabByTag ignore first call of listener
    */
    TabHost.OnTabChangeListener listener = new TabHost.OnTabChangeListener() {
        public void onTabChanged(String tabId) {

            mCurrentTab = tabId;

            if (tabId.equals(TAB_TAGS)) {
                pushFragments(SearchFragment.getInstance(), false,
                        false, null);
            } else if (tabId.equals(TAB_MAP)) {
                pushFragments(MapContainerFragment.getInstance(), false,
                        false, null);
            } else if (tabId.equals(TAB_SETTINGS)) {
                pushFragments(SettingsFragment.getInstance(), false,
                        false, null);
            }

        }
    };

/*
Example of starting nested fragment from another fragment:

Fragment newFragment = ManagerTagFragment.newInstance(tag.getMac());
                TagsActivity tAct = (TagsActivity)getActivity();
                tAct.pushFragments(newFragment, true, true, null);
 */
    public void pushFragments(Fragment fragment,
                              boolean shouldAnimate, boolean shouldAdd, String tag) {
        FragmentManager manager = getFragmentManager();
        FragmentTransaction ft = manager.beginTransaction();
        if (shouldAnimate) {
            ft.setCustomAnimations(R.animator.fragment_slide_left_enter,
                    R.animator.fragment_slide_left_exit,
                    R.animator.fragment_slide_right_enter,
                    R.animator.fragment_slide_right_exit);
        }
        ft.replace(R.id.realtabcontent, fragment, tag);

        if (shouldAdd) {
            /*
            here you can create named backstack for realize another logic.
            ft.addToBackStack("name of your backstack");
             */
            ft.addToBackStack(null);
        } else {
            /*
            and remove named backstack:
            manager.popBackStack("name of your backstack", FragmentManager.POP_BACK_STACK_INCLUSIVE);
            or remove whole:
            manager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
             */
            manager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
        }
        ft.commit();
    }

    /*
    If you want to start this activity from another
     */
    public static void startUrself(Activity context) {
        Intent newActivity = new Intent(context, TagsActivity.class);
        newActivity.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(newActivity);
        context.finish();
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        outState.putString(M_CURRENT_TAB, mCurrentTab);
        super.onSaveInstanceState(outState);
    }

    @Override
    public void onBackPressed(){
        super.onBackPressed();
    }
}

tags_activity.xml

<

?xml version="1.0" encoding="utf-8"?>
<TabHost
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@android:id/tabhost"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <FrameLayout
            android:id="@android:id/tabcontent"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:layout_weight="0"/>
        <FrameLayout
            android:id="@+android:id/realtabcontent"
            android:background="@drawable/bg_main_app_gradient"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"/>
        <TabWidget
            android:id="@android:id/tabs"
            android:background="#EAE7E1"
            android:orientation="horizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_weight="0"/>
    </LinearLayout>
</TabHost>

tags_icon.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/tabsLayout"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="@drawable/bg_tab_gradient"
    android:gravity="center"
    android:orientation="vertical"
    tools:ignore="contentDescription" >

    <ImageView
        android:id="@+id/tab_icon"
        android:layout_marginTop="4dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <TextView 
        android:id="@+id/tab_text"
        android:layout_marginBottom="3dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="@color/tab_text_color"/>

</LinearLayout>

nhập mô tả hình ảnh ở đây

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.