Phương pháp hay nhất cho các đoạn lồng nhau trong Android 4.0, 4.1 (<4.2) mà không cần sử dụng thư viện hỗ trợ


115

Tôi đang viết một ứng dụng cho máy tính bảng 4.0 và 4.1, mà tôi không muốn sử dụng các thư viện hỗ trợ (nếu không cần thiết) nhưng chỉ có api 4.x.

Vì vậy, nền tảng mục tiêu của tôi được xác định rất rõ ràng là:> = 4.0 và <= 4.1

Ứng dụng có bố cục nhiều ngăn (hai phân đoạn, một phân đoạn nhỏ ở bên trái, một phân đoạn nội dung ở bên phải) và một thanh tác vụ với các tab.

Tương tự như thế này:

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

Nhấp vào một tab trên thanh tác vụ sẽ thay đổi phân đoạn 'bên ngoài' và phân đoạn bên trong sau đó là một phân đoạn có hai đoạn lồng nhau (1. phân đoạn danh sách nhỏ bên trái, 2. phân đoạn nội dung rộng).

Bây giờ tôi đang tự hỏi phương pháp tốt nhất để thay thế các đoạn và đặc biệt là các đoạn lồng nhau. ViewPager là một phần của thư viện hỗ trợ, không có giải pháp thay thế 4.x gốc nào cho lớp này. Có vẻ như là 'không được chấp nhận' theo cảm nhận của tôi. - http://developer.android.com/reference/android/support/v4/view/ViewPager.html

Sau đó, tôi đọc các ghi chú phát hành cho Android 4.2, liên quan đến ChildFragmentManager, điều này sẽ rất phù hợp, nhưng tôi đang nhắm mục tiêu 4.0 và 4.1, vì vậy điều này cũng không thể được sử dụng.

ChildFragmentManager chỉ có sẵn trong 4.2

Thật không may, hầu như không có bất kỳ ví dụ tốt nào hiển thị các phương pháp hay nhất cho cách sử dụng phân đoạn mà không có thư viện hỗ trợ, ngay cả trong toàn bộ hướng dẫn dành cho nhà phát triển Android; và đặc biệt là không có gì liên quan đến các đoạn lồng nhau.

Vì vậy, tôi tự hỏi: có phải đơn giản là không thể viết ứng dụng 4.1 với các đoạn lồng nhau mà không sử dụng thư viện hỗ trợ và mọi thứ đi kèm với nó không? (cần sử dụng FragmentActivity thay vì Fragment, v.v.?) Hoặc cách tốt nhất sẽ là gì?


Vấn đề mà tôi hiện đang gặp phải trong quá trình phát triển chính xác là câu này:

Thư viện hỗ trợ Android hiện cũng hỗ trợ các phân đoạn lồng nhau, vì vậy bạn có thể triển khai các thiết kế phân mảnh lồng nhau trên Android 1.6 trở lên.

Lưu ý: Bạn không thể thổi phồng bố cục thành một đoạn khi bố cục đó bao gồm một <fragment>. Các đoạn lồng nhau chỉ được hỗ trợ khi được thêm động vào một đoạn.

Bởi vì tôi đặt xác định các phân đoạn lồng nhau trong XML, điều này dường như gây ra lỗi như:

Caused by: java.lang.IllegalArgumentException: Binary XML file line #15: Duplicate id 0x7f090009, tag frgCustomerList, or parent id 0x7f090008 with another fragment for de.xyz.is.android.fragment.CustomerListFragment_

Hiện tại, tôi tự kết luận: ngay cả trên 4.1, khi tôi thậm chí không muốn nhắm mục tiêu nền tảng 2.x, các đoạn lồng nhau như thể hiện trong ảnh chụp màn hình là không thể thực hiện được nếu không có thư viện hỗ trợ.

(Đây thực sự có thể là một mục wiki hơn là một câu hỏi, nhưng có thể ai đó đã quản lý nó trước đây).

Cập nhật:

Một câu trả lời hữu ích có tại: Fragment Inside Fragment


22
Bạn có ba tùy chọn: 1. Chỉ nhắm mục tiêu 4.2 với các đoạn lồng nhau gốc. 2. Nhắm mục tiêu 4.x với các đoạn lồng nhau từ thư viện hỗ trợ 3. Không sử dụng các đoạn lồng nhau cho bất kỳ tình huống mục tiêu nền tảng nào khác. Điều này sẽ trả lời câu hỏi của bạn.Ngoài ra, bạn không thể sử dụng phân đoạn lồng nhau được nhúng trong bố cục xml, tất cả chúng phải được thêm vào mã. hầu như không có bất kỳ ví dụ tốt nào hiển thị các phương pháp hay nhất cho việc sử dụng các đoạn mà không có thư viện hỗ trợ - khung hỗ trợ phân đoạn sao chép khuôn khổ gốc nên bất kỳ ví dụ nào cũng nên hoạt động theo cả hai cách.
Luksprog

@Luksprog Cảm ơn ý kiến ​​của bạn. Tôi thích giải pháp 2 của bạn hơn và các khung hoạt động tốt trong thư viện hỗ trợ, nhưng các Tab trong ActionBar thì không. t cần thiết cho 4.x). Và ActionBar.TabListener chỉ hỗ trợ Fragment từ android.app.Fragment, không phải từ thư viện hỗ trợ.
Mathias Conradt

2
Tôi không quen thuộc với ứng dụng Danh bạ trên tab Galaxy nhưng hãy nhớ rằng bạn luôn có thể phải đối mặt với việc triển khai tùy chỉnh ActionBar(được Samsung tích hợp sẵn). Hãy xem xét kỹ hơn về ActionBarSherlock, nó có các tab trong ActionBar nếu còn chỗ.
Luksprog

4
@Luksprog Tôi tin rằng bạn đã cung cấp câu trả lời duy nhất để đưa ra, bạn có vui lòng đưa nó vào như một câu trả lời thích hợp không.
Warpzit

1
@Pork Lý do chính của tôi cho câu hỏi là: có cách giải quyết nào cho các đoạn lồng nhau mà không cần phải sử dụng thư viện hỗ trợ và tất cả đó là các phần tử chế độ xem khác. Có nghĩa là, nếu tôi chuyển sang thư viện hỗ trợ, tôi sẽ sử dụng FragmentActivity thay vì Fragment. Nhưng tôi muốn sử dụng Fragment, tất cả những gì tôi muốn là một sự thay thế cho các Fragment lồng nhau , nhưng không phải tất cả các thành phần v4. Tức là thông qua các thư viện mã nguồn mở khác, v.v. ngoài đó. Ví dụ ở trên ảnh chụp màn hình chạy trên 4.0 và tôi đang tự hỏi liệu họ có đang sử dụng ABS, SupportLib hay bất kỳ thứ gì khác không.
Mathias Conradt

Câu trả lời:


60

Hạn chế

Vì vậy, việc lồng các đoạn bên trong một đoạn khác là không thể với xml bất kể FragmentManagerbạn sử dụng phiên bản nào.

Vì vậy, bạn phải thêm các phân đoạn thông qua mã, điều này có vẻ như là một vấn đề, nhưng về lâu dài làm cho bố cục của bạn siêu linh hoạt.

Vì vậy, làm tổ mà không sử dụng getChildFragmentManger? Bản chất đằng sau childFragmentManagerlà nó định hướng tải cho đến khi giao dịch phân đoạn trước đó kết thúc. Và tất nhiên nó chỉ được hỗ trợ một cách tự nhiên trong 4.2 hoặc thư viện hỗ trợ.

Lồng mà không có ChildManager - Giải pháp

Giải pháp, Chắc chắn! Tôi đã làm điều này trong một thời gian dài, (kể từ khi ViewPagerđược công bố).

Xem bên dưới; Đây là một Fragmentđịnh nghĩa tải, vì vậy Fragments có thể được tải bên trong nó.

Nó khá đơn giản, đây Handlerlà một lớp thực sự thực sự tiện dụng, hiệu quả là trình xử lý đợi một khoảng trống để thực thi trên luồng chính sau khi giao dịch phân mảnh hiện tại đã hoàn thành cam kết (vì các phân đoạn can thiệp vào giao diện người dùng mà chúng chạy trên luồng chính).

// Remember this is an example, you will need to modify to work with your code
private final Handler handler = new Handler();
private Runnable runPager;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
    return inflater.inflate(R.layout.frag_layout, container, false);
}

@Override
public void onActivityCreated(Bundle savedInstanceState)
{
    super.onActivityCreated(savedInstanceState);
    runPager = new Runnable() {

        @Override
        public void run()
        {
          getFragmentManager().beginTransaction().addFragment(R.id.frag_container, MyFragment.newInstance()).commit();
        }
    };
    handler.post(runPager);
}

/**
 * @see android.support.v4.app.Fragment#onPause()
 */
@Override
public void onPause()
{
    super.onPause();
    handler.removeCallbacks(runPager);
}

Tôi sẽ không coi đó là 'phương pháp hay nhất', nhưng tôi có các ứng dụng trực tiếp sử dụng bản hack này và tôi vẫn chưa gặp bất kỳ vấn đề nào với nó.

Tôi cũng sử dụng phương pháp này để nhúng máy nhắn tin xem - https://gist.github.com/chrisjenx/3405429


Làm thế nào để bạn xử lý bố cục các đoạn lồng nhau?
pablisco

Cách duy nhất tôi có thể thấy điều này hoạt động là bằng cách sử dụng CustomLayoutInflater, khi bạn bắt gặp fragmentphần tử, bạn sẽ ghi đè quá trình triển khai siêu và cố gắng tự phân tích / thổi phồng nó. Nhưng đó sẽ là RẤT NHIỀU nỗ lực, Ngoài phạm vi của một câu hỏi StackOverflow.
Chris.Jenkins,

Xin chào, bất cứ ai có thể giúp tôi trong vấn đề này? Tôi thực sự bị mắc kẹt .. stackoverflow.com/questions/32240138/…
Nicks

2

Cách tốt nhất để thực hiện việc này trong pre-API 17 là không làm điều đó. Cố gắng thực hiện hành vi này sẽ gây ra vấn đề. Tuy nhiên, điều đó không có nghĩa là nó không thể bị làm giả một cách thuyết phục bằng cách sử dụng API 14. Những gì tôi đã làm như sau:

1 - xem xét giao tiếp giữa các phân đoạn http://developer.android.com/training/basics/fra mảnh/communicating.html

2 - di chuyển bố cục xml FrameLayout của bạn từ Fragment hiện có của bạn sang bố cục Activity và ẩn nó bằng cách đặt chiều cao là 0:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
          xmlns:tools="http://schemas.android.com/tools"
          android:layout_width="fill_parent"
          android:layout_height="fill_parent">
<FrameLayout android:id="@+id/content"
          android:layout_width="300dp"
          android:layout_height="match_parent" />


<FrameLayout android:id="@+id/lstResults"
             android:layout_width="300dp"
             android:layout_height="0dp"
             android:layout_below="@+id/content"
             tools:layout="@layout/treeview_list_content"/>


<FrameLayout android:id="@+id/anomalies_fragment"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
        android:layout_toRightOf="@+id/content" />

3 - Triển khai giao diện trong Fragment chính

    OnListener mCallback;

// Container Activity must implement this interface
public interface OnListener 
{
    public void onDoSomethingToInitChildFrame(/*parameters*/);
    public void showResults();
    public void hideResults();
}

@Override
public void onAttach(Activity activity) {
    super.onAttach(activity);

    // This makes sure that the container activity has implemented
    // the callback interface. If not, it throws an exception
    try {
        mCallback = (OnFilterAppliedListener) activity;
    } catch (ClassCastException e) {
        throw new ClassCastException(activity.toString()
                + " must implement OnListener");
    }
}

@Override
public void onActivityCreated(Bundle savedInstanceState) 
{
    super.onActivityCreated(savedInstanceState);

    mCallback.showResults();
}

@Override
public void onPause()
{
    super.onPause();

    mCallback.hideResults();
}

public void onClickButton(View view)
{
    // do click action here

    mCallback.onDoSomethingToInitChildFrame(/*parameters*/);
}

4 - Triển khai giao diện trong Hoạt động cha

public class YourActivity mở rộng Hoạt động triển khai yourParentFragment.OnListener {

public void onDoSomethingToInitChildFrame(/*parameters*/)
{
    FragmentTransaction ft = getFragmentManager().beginTransaction();
    Fragment childFragment = getFragmentManager().findFragmentByTag("Results");
    if(childFragment == null)
    {
        childFragment = new yourChildFragment(/*parameters*/);
        ft.add(R.id.lstResults, childFragment, "Results");
    }
    else
    {
        ft.detach(childFragment);

        ((yourChildFragment)childFragment).ResetContent(/*parameters*/);

        ft.attach(childFragment);
    }
    ft.commit();

    showResultsPane();
}

public void showResults()
{
    FragmentTransaction ft = getFragmentManager().beginTransaction();
    Fragment childFragment = getFragmentManager().findFragmentByTag("Results");
    if(childFragment != null)
        ft.attach(childFragment);
    ft.commit();

    showResultsPane();
}

public void showResultsPane()
{
    //resize the elements to show the results pane
    findViewById(R.id.content).getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT;
    findViewById(R.id.lstResults).getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT;
}

public void hideResults()
{
    //resize the elements to hide the results pane
    findViewById(R.id.content).getLayoutParams().height = ViewGroup.LayoutParams.MATCH_PARENT;
    findViewById(R.id.lstResults).getLayoutParams().height = 0;

    FragmentTransaction ft = getFragmentManager().beginTransaction();
    Fragment childFragment = getFragmentManager().findFragmentByTag("Results");
    if(childFragment != null)
        ft.detach(childFragment);
    ft.commit();
}

}

5 - Hãy tận hưởng, với phương pháp này, bạn sẽ có được chức năng linh hoạt giống như với hàm getChildFragmentManager () trong một envoronment trước API 17. Như bạn có thể đã nhận thấy, phân đoạn con không còn thực sự là con của phân đoạn mẹ nữa mà bây giờ là con của hoạt động, điều này thực sự không thể tránh khỏi.


1

Tôi đã phải giải quyết vấn đề chính xác này do sự kết hợp của NavigationDrawer, TabHost và ViewPager đã gây ra sự cố với việc sử dụng thư viện hỗ trợ vì TabHost. Và sau đó tôi cũng phải hỗ trợ API tối thiểu của JellyBean 4.1, vì vậy việc sử dụng các đoạn lồng nhau với getChildFragmentManager không phải là một tùy chọn.

Vì vậy, vấn đề của tôi có thể được chắt lọc thành ...

TabHost (dành cho cấp cao nhất)
+ ViewPager (chỉ dành cho một trong các phân đoạn được tab cấp cao nhất)
= cần các Đoạn lồng nhau (mà JellyBean 4.1 sẽ không hỗ trợ)

Giải pháp của tôi là tạo ảo giác về các mảnh lồng nhau mà không thực sự lồng ghép các mảnh. Tôi đã làm điều này bằng cách để hoạt động chính sử dụng TabHost VÀ ViewPager để quản lý hai Chế độ xem anh em có khả năng hiển thị được quản lý bằng cách chuyển đổi layout_weight giữa 0 và 1.

//Hide the fragment used by TabHost by setting height and weight to 0
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 0);
mTabHostedView.setLayoutParams(lp);
//Show the fragment used by ViewPager by setting height to 0 but weight to 1
lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 1);
mPagedView.setLayoutParams(lp);

Điều này cho phép hiệu quả "Phân đoạn lồng nhau" giả mạo của tôi hoạt động như một chế độ xem độc lập miễn là tôi quản lý thủ công các trọng số bố cục có liên quan.

Đây là activity_main.xml của tôi:

<android.support.v4.widget.DrawerLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.ringofblades.stackoverflow.app.MainActivity">

    <TabHost
        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:background="@drawable/background_image"
                android:layout_width="match_parent"
                android:layout_weight="0.5"
                android:layout_height="0dp"/>
            <android.support.v4.view.ViewPager
                xmlns:tools="http://schemas.android.com/tools"
                android:id="@+id/pager"
                android:background="@drawable/background_image"
                android:layout_width="match_parent"
                android:layout_weight="0.5"
                android:layout_height="0dp"
                tools:context="com.ringofblades.stackoverflow.app.MainActivity">
                <FrameLayout
                    android:id="@+id/container"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent" />
            </android.support.v4.view.ViewPager>
            <TabWidget android:id="@android:id/tabs"
                android:layout_width="match_parent"
                android:layout_height="wrap_content" />
        </LinearLayout>
    </TabHost>

    <fragment android:id="@+id/navigation_drawer"
        android:layout_width="@dimen/navigation_drawer_width"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:name="com.ringofblades.stackoverflow.app.NavigationDrawerFragment"
        tools:layout="@layout/fragment_navigation_drawer" />
</android.support.v4.widget.DrawerLayout>

Lưu ý rằng "@ + id / pager" và "@ + id / container" là anh em ruột với 'android: layout_weight = "0.5"' và 'android: layout_height = "0dp"'. Điều này để tôi có thể xem nó trong trình xem trước cho bất kỳ kích thước màn hình nào. Dù sao thì trọng lượng của chúng cũng sẽ được thao tác trong mã trong thời gian chạy.


Xin chào, tôi tò mò tại sao bạn lại chọn sử dụng TabHost thay vì ActionBar với Tabs? Bản thân tôi đã chuyển từ TabHost chỉ ActionBar, và mã của tôi trở nên sạch hơn và nhỏ gọn hơn ...
IgorGanapolsky

Theo như tôi nhớ, một nhược điểm của việc sử dụng các tab trong ActionBar là nó quyết định tự động hiển thị chúng dưới dạng menu thả xuống hình vòng quay (trong trường hợp màn hình nhỏ) và điều đó không tốt cho tôi. Nhưng tôi không chắc 100%.
WindRider

@ Hay là, tôi đã đọc ở đâu đó ở đây SOrằng việc sử dụng ActionBarcác tab với a Navigation Drawerlà không tốt vì nó sẽ tự động đặt các tab trên chế độ xem ngăn kéo của bạn. Xin lỗi, tôi không có liên kết để sao lưu điều này.
Azurespot

1
@NoniA. blog.xamarin.com/android-tips-hello-toolbar-goodbye-action-bar Bạn có theo dõi sự phát triển của Android không?
IgorGanapolsky

1
Chà, cảm ơn vì liên kết @Igor! Tôi sẽ kiểm tra điều này cho chắc chắn. Tôi vẫn là người mới bắt đầu, vì vậy có hàng triệu thứ khác để học với Android, nhưng điều này có vẻ giống như một viên ngọc quý! Cảm ơn một lần nữa.
Azurespot,

1

Dựa trên câu trả lời của @ Chris.Jenkins, đây là giải pháp đã hoạt động tốt đối với tôi, để loại bỏ (các) phân đoạn trong các sự kiện vòng đời (có xu hướng ném IllegalStateExceptions). Điều này sử dụng kết hợp phương pháp Xử lý và kiểm tra Activity.isFinishing () (nếu không, nó sẽ xuất hiện lỗi "Không thể thực hiện hành động này sau onSaveInstanceState).

import android.app.Activity;
import android.os.Handler;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;

public abstract class BaseFragment extends Fragment {
    private final Handler handler = new Handler();

    /**
     * Removes the {@link Fragment} using {@link #getFragmentManager()}, wrapped in a {@link Handler} to
     * compensate for illegal states.
     *
     * @param fragment The {@link Fragment} to schedule for removal.
     */
    protected void removeFragment(@Nullable final Fragment fragment) {
        if (fragment == null) return;

        final Activity activity = getActivity();
        handler.post(new Runnable() {
            @Override
            public void run() {
                if (activity != null && !activity.isFinishing()) {
                    getFragmentManager().beginTransaction()
                            .remove(fragment)
                            .commitAllowingStateLoss();
                }
            }
        });
    }

    /**
     * Removes each {@link Fragment} using {@link #getFragmentManager()}, wrapped in a {@link Handler} to
     * compensate for illegal states.
     *
     * @param fragments The {@link Fragment}s to schedule for removal.
     */
    protected void removeFragments(final Fragment... fragments) {
        final FragmentManager fragmentManager = getFragmentManager();
        final FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();

        for (Fragment fragment : fragments) {
            if (fragment != null) {
                fragmentTransaction.remove(fragment);
            }
        }

        final Activity activity = getActivity();
        handler.post(new Runnable() {
            @Override
            public void run() {
                if (activity != null && !activity.isFinishing()) {
                    fragmentTransaction.commitAllowingStateLoss();
                }
            }
        });
    }
}

Sử dụng:

class MyFragment extends Fragment {
    @Override
    public void onDestroyView() {
        removeFragments(mFragment1, mFragment2, mFragment3);
        super.onDestroyView();
    }
}

1

Mặc dù OP có thể có những trường hợp đặc biệt khiến anh ta không thể sử dụng Thư viện hỗ trợ, nhưng hầu hết mọi người nên sử dụng nó. Tài liệu Android đề xuất nó và nó sẽ cung cấp ứng dụng của bạn cho nhiều đối tượng nhất có thể.

Trong câu trả lời đầy đủ hơn của tôi ở đây, tôi đã làm một ví dụ minh họa cách sử dụng các đoạn lồng nhau với thư viện hỗ trợ.

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


Tôi là OP. Lý do không sử dụng hỗ trợ lib là vì đây là ứng dụng nội bộ của công ty, trong đó phần cứng được sử dụng được xác định rõ ràng là> = 4.0 và <= 4.1. Không cần tiếp cận nhiều đối tượng, đó là nhân viên nội bộ và không có ý định sử dụng ứng dụng bên ngoài công ty. Lý do duy nhất của hỗ trợ lib là tương thích ngược - nhưng người ta sẽ mong đợi rằng mọi thứ bạn có thể làm với thư viện hỗ trợ, bạn sẽ có thể đạt được "nguyên bản" mà không cần nó. Tại sao một phiên bản cao hơn "gốc" lại có ít tính năng hơn thư viện hỗ trợ mà mục đích chỉ là để tương thích xuống.
Mathias Conradt

1
Tuy nhiên, tất nhiên bạn có thể sử dụng thư viện hỗ trợ và tôi cũng vậy. Chỉ không hiểu tại sao Google cung cấp các tính năng CHỈ trong thư viện hỗ trợ mà không phải bên ngoài hoặc tại sao họ thậm chí gọi nó là thư viện hỗ trợ và không biến nó thành tiêu chuẩn tổng thể sau đó, nếu đó là phương pháp hay nhất. Đây là một bài viết hay về thư viện hỗ trợ: martiancraft.com/blog/2015/06/android-support-library
Mathias Conradt

@Mathias, bài viết hay.
Suragch
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.