115

我正在为 4.0 和 4.1 平板电脑编写一个应用程序,我不想为此使用支持库(如果不需要),但只使用 4.x api。

所以我的目标平台非常明确地定义为:>= 4.0 和 <= 4.1

该应用程序具有多窗格布局(两个片段,左侧一个小片段,右侧一个内容片段)和一个带有选项卡的操作栏。

与此类似:

在此处输入图像描述

单击操作栏上的选项卡会更改“外部”片段,然后内部片段是具有两个嵌套片段的片段(1. 左侧小列表片段,2. 宽内容片段)。

我现在想知道替换片段尤其是嵌套片段的最佳做法是什么。ViewPager 是支持库的一部分,此类没有本机 4.x 替代方案。在我的意义上似乎被“弃用”。- http://developer.android.com/reference/android/support/v4/view/ViewPager.html

然后我阅读了 Android 4.2 的发行说明,关于ChildFragmentManager,这将是一个很好的选择,但我的目标是 4.0 和 4.1,所以这也不能使用。

ChildFragmentManager仅在 4.2 中可用

不幸的是,即使在整个 Android 开发人员指南中,也几乎没有任何好的示例可以展示在没有支持库的情况下使用 Fragment 的最佳实践;尤其是关于嵌套片段的任何内容。

所以我想知道:如果不使用支持库和它附带的所有东西,就不可能编写带有嵌套片段的 4.1 应用程序吗?(需要使用 FragmentActivity 而不是 Fragment 等?) 或者最佳实践是什么?


我目前在开发中遇到的问题正是这种说法:

Android 支持库现在还支持嵌套片段,因此您可以在 Android 1.6 及更高版本上实现嵌套片段设计。

注意:当布局包含<fragment>. 仅当动态添加到片段时才支持嵌套片段。

因为我在 XML 中定义了嵌套片段,这显然会导致如下错误:

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_

目前,我自己总结:即使在 4.1 上,当我什至不想针对 2.x 平台时,如果没有支持库,截图中所示的嵌套片段是不可能的。

(这实际上可能更像是一个 wiki 条目而不是一个问题,但也许其他人之前已经管理过它)。

更新:

一个有用的答案是:Fragment Inside Fragment

4

5 回答 5

61

限制

FragmentManager因此,无论您使用哪个版本,xml 都无法将片段嵌套在另一个片段中。

所以你必须通过代码添加片段,这似乎是个问题,但从长远来看,你的布局会变得超级灵活。

所以嵌套而不使用getChildFragmentManger?背后的本质childFragmentManager是它延迟加载,直到前一个片段事务完成。当然,它只在 4.2 或支持库中自然得到支持。

没有 ChildManager 的嵌套 - 解决方案

解决方案,当然!我已经这样做了很长时间了(自ViewPager宣布以来)。

见下文; 这是Fragment延迟加载的 a,因此Fragment可以在其中加载 s。

它非常简单,Handler是一个非常方便的类,在当前片段事务完成提交后,处理程序实际上会在主线程上等待一个空间来执行(因为片段会干扰它们在主线程上运行的 UI)。

// 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);
}

我不会认为它是“最佳实践”,但我有使用这个 hack 的实时应用程序,我还没有遇到任何问题。

我也使用这种方法嵌入视图寻呼机 - https://gist.github.com/chrisjenx/3405429

于 2013-06-16T10:21:07.103 回答
2

在 API 17 之前的版本中执行此操作的最佳方法是根本不执行此操作。试图实现这种行为会导致问题。然而,这并不是说它不能令人信服地使用当前的 API 14 伪造。我所做的如下:

1 - 查看片段之间的通信http://developer.android.com/training/basics/fragments/communicating.html

2 - 将您的布局 xml FrameLayout 从现有的 Fragment 移动到 Activity 布局,并通过将高度设置为 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 - 在父片段中实现接口

    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 - 在父Activity中实现接口

公共类 YourActivity 扩展 Activity 实现 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 - 享受吧,通过这种方法,您可以获得与 API 17 之前的环境中的 getChildFragmentManager() 函数相同的流畅功能。您可能已经注意到子片段不再是父片段的子片段,而是活动的子片段,这确实是无法避免的。

于 2014-09-04T15:13:17.453 回答
1

由于 NavigationDrawer、TabHost 和 ViewPager 的组合,我不得不处理这个确切的问题,由于 TabHost 的原因,支持库的使用很复杂。然后我还必须支持 JellyBean 4.1 的 min API,因此不能选择使用带有 getChildFragmentManager 的嵌套片段。

所以我的问题可以提炼为...

TabHost(用于顶级)
+ ViewPager(仅用于顶级选项卡式片段之一)
= 需要嵌套片段(JellyBean 4.1 不支持)

我的解决方案是在没有实际嵌套片段的情况下创建嵌套片段的错觉。我通过让主要活动使用 TabHost 和 ViewPager 来管理两个同级视图来做到这一点,它们的可见性是通过在 0 和 1 之间切换 layout_weight 来管理的。

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

只要我手动管理相关的布局权重,这有效地允许我的假“嵌套片段”作为独立视图运行。

这是我的activity_main.xml:

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

请注意,“@+id/pager”和“@+id/container”是具有“android:layout_weight="0.5"' 和 'android:layout_height="0dp"' 的兄弟姐妹。这样我就可以在任何屏幕尺寸的预览器中看到它。无论如何,它们的权重将在运行时在代码中进行操作。

于 2014-04-03T09:51:57.417 回答
1

尽管 OP 可能有特殊情况阻止他使用支持库,但大多数人应该使用它。Android 文档推荐它,它将使您的应用程序提供给尽可能广泛的受众。

我更完整的答案中,我做了一个示例,演示如何将嵌套片段与支持库一起使用。

在此处输入图像描述

于 2016-09-13T10:36:05.747 回答
1

Building on @Chris.Jenkins answer, this is the solution that has been working well for me, for removing fragment(s) during life cycle events (which have a tendency to throw IllegalStateExceptions). This uses a combination of the Handler approach, and an Activity.isFinishing() check (otherwise it will throw an error for "Can not perform this action after 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();
                }
            }
        });
    }
}

Usage:

class MyFragment extends Fragment {
    @Override
    public void onDestroyView() {
        removeFragments(mFragment1, mFragment2, mFragment3);
        super.onDestroyView();
    }
}
于 2015-08-07T21:39:26.757 回答