Best practice for nested fragments in Android 4.0, 4.1 (<4.2) without using the support library

I'm writing an app for 4.0 and 4.1 tablets, for which I do not want to use the support libraries (if not needed) but the 4.x api only therefore.

So my target platform is very well defined as: >= 4.0 and <= 4.1

The app has a multi-pane layout (two fragments, one small on the left, one content fragment on the right) and an action bar with tabs.

Similar to this:

enter image description here

Clicking a tab on the action bar changes the 'outer' fragment, and the inner fragment then is a fragment with two nested fragments (1. small left list fragment, 2. wide content fragment).

I am now wondering what's the best practice to replace fragments and especially nested fragments. The ViewPager is part of the support library, there's no native 4.x alternative for this class. Appear to be 'deprecated' in my sense. - http://developer.android.com/reference/android/support/v4/view/ViewPager.html

Then I read the release notes for Android 4.2, regarding ChildFragmentManager, which would be a good fit, but I am targeting 4.0 and 4.1, so this can't be used either.

ChildFragmentManager is only available in 4.2

Unfortunately, there are hardly any good examples out there that show best practices for fragments usages without the support library, even in the entire Android developer guides; and especially nothing regarding nested fragments.

So I am wondering: is it simply not possible to write 4.1 apps with nested fragments without using the support library and everything that comes with it? (need to use FragmentActivity instead of Fragment, etc.?) Or what would be the best practice?


The problem that I am currently having in the development is exactly this statement:

The Android Support Library also now supports nested fragments, so you can implement nested fragment designs on Android 1.6 and higher.

Note: You cannot inflate a layout into a fragment when that layout includes a <fragment>. Nested fragments are only supported when added to a fragment dynamically.

Because I put define the nested fragments in XML, which apparently causes an error like:

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_

At the moment, I conclude for myself: even on 4.1, when I don't even want to target the 2.x platform, nested fragments as shown in the screenshot are not possible without the support library.

(This might actually be more of a wiki entry than a question, but maybe somebody else has managed it before).

Update:

A helpful answer is at: Fragment Inside Fragment

75164 次浏览

限制

因此,不管使用哪个版本的 FragmentManager,xml 都不可能在另一个片段中嵌套片段。

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

那么不使用 getChildFragmentManger进行嵌套呢?childFragmentManager背后的本质是,它将加载推迟到前一个片段事务完成之后。当然,它只在4.2或支持库中得到自然支持。

没有儿童经理的嵌套-解决方案

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

看下面; 这是一个延迟加载的 Fragment,因此 Fragment可以被加载到它的内部。

非常简单,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);
}

我不会认为这是“最佳实践”,但我有使用这种黑客技术的现场应用程序,我还没有与它有任何问题。

I also use this method for embedding view pagers - https://gist.github.com/chrisjenx/3405429

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

因此,我的问题可以简化为... < pre > TabHost (用于顶级) + ViewPager (针对顶级选项卡片段之一) = 需要嵌套片段(JellyBean 4.1不支持)

我的解决方案是创建嵌套片段的假象,而实际上没有嵌套片段。为此,我让主活动使用 TabHost 和 ViewPager 来管理两个兄弟视图,它们的可见性是通过在0和1之间切换 layoweight 来管理的。

//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: layweight = “0.5”和“ android: layup _ height = “0dp””的兄弟。这样我就可以在任何屏幕尺寸的预览器中看到它。无论如何,它们的权重将在运行时的代码中被操纵。

在 API 17之前做到这一点的最好方法就是根本不做。试图实现这种行为会导致问题。然而,这并不是说它不能令人信服地伪造使用当前的 API 14。我所做的是:

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

2-将您的布局 xml FrameLayout 从现有的片段移动到 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" />

在父级片段中实现接口

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

在父活动中实现接口

公共类 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 ()函数相同的流体功能。正如您可能已经注意到的,子片段实际上不再是父片段的子片段,而是活动的子片段,这实际上是不可避免的。

建立在@Chris 上。Jenkins 回答说,这是在生命周期事件(有抛出 IllegalStateException 的倾向)中删除片段的解决方案,对我来说效果很好。这将结合使用 Handler 方法和 Activity.isFinishing ()检查(否则将抛出一个错误,表示“在 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();
}
}
});
}
}

用法:

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

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

我的完整答案在这里中,我做了一个示例,演示如何使用支持库中的嵌套片段。

enter image description here