Android 4.0,4.1(<4.2)中嵌套片段的最佳实践,而不使用支持库

我正在写一个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中可用

  • http://developer.android.com/about/versions/android-4.2.html#NestedFragments
  • http://developer.android.com/reference/android/app/Fragment.html#getChildFragmentManager()

不幸的是,即使在整个Android开发人员指南中,几乎没有任何好的示例显示了没有支持库的片段使用的最佳实践; 特别是关于嵌套的片段。

所以我想知道:是不是可以使用嵌套的片段来编写4.1应用程序,而不使用支持库及其附带的所有东西? (需要使用FragmentActivity而不是Fragment等?)或者最好的做法是什么?


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

  • http://developer.android.com/about/versions/android-4.2.html#NestedFragments

Android支持库现在也支持嵌套片段,因此您可以在Android 1.6或更高版本上实现嵌套片段devise。

注意:当布局包含<fragment>时,不能将布局充气到<fragment> 。 只有在dynamic添加到片段时才支持嵌套片段。

因为我把定义在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平台作为目标时,如果没有支持库,屏幕截图中所示的嵌套片段也是不可能的。

(这实际上可能是更多的维基条目,而不是一个问题,但也许别人已经pipe理它之前)。

更新:

一个有用的答案是: 碎片里面的碎片

限制

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

所以你必须通过代码添加碎片,这可能看起来像是一个问题,但从长远来看,使你的布局非常灵活。

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

嵌套没有ChildManager – 解决scheme

解决scheme,当然! (自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); } 

我不认为这是'最佳实践',但我有实时应用程序使用这个黑客,我还没有任何问题。

我也使用这种方法来embedded视图寻呼机 – https://gist.github.com/chrisjenx/3405429

在API 17之前做到这一点的最好方法就是不要这样做。 试图实现这种行为将导致问题。 但是,这并不是说它不能用目前的API 14来令人信服地说明。我所做的是:

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

2 – 将您的布局xml FrameLayout从您现有的片段移动到活动布局,并通过给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 – 实现父Fragment中的接口

  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 – 在父活动中实现接口

公共类YourActivity扩展活动实现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()函数相同的stream体function。 正如你可能已经注意到,孩子片段不再是父母片段的孩子,而是孩子的活动,这实在是无法避免的。

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

所以我的问题可以被提炼成…

  TabHost(顶层)
 + ViewPager(仅用于顶级标签片段之一)
 =需要嵌套片段(JellyBean 4.1不支持) 

我的解决办法是创build嵌套片段的幻想,而不实际嵌套片段。 我通过让主要活动使用TabHost和ViewPager来pipe理两个同级视图,其可见性是通过在0和1之间切换layout_weight来pipe理的。

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

只要我手动pipe理相关的布局权重,这实际上允许我的假“嵌套片段”作为一个独立的视图。

这是我的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“”的兄弟。 这样,我可以在预览器中看到它的任何屏幕大小。 无论如何,它们的权重将在运行时在代码中进行处理。

build立在@ Chris.Jenkins的回答,这是一直在我的工作很好,在生命周期事件(有倾向抛出IllegalStateExceptions)删除片段的解决scheme。 这使用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(); } } 

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

在我的更全面的答案中,我做了一个演示如何在支持库中使用嵌套片段的例子。

在这里输入图像描述