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


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开发人员指南,也几乎没有很好的示例显示没有支持库的片段用法的最佳实践。特别是关于嵌套片段的内容。

所以我想知道:如果不使用支持库及其附带的所有内容,是否无法简单地编写带有嵌套片段的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平台时,如无屏幕截图所示的嵌套片段,都没有支持库是不可能的。

(这实际上可能更多的是维基条目而不是问题,但也许以前有人管理过它)。

更新:

一个有用的答案是:片段片段内部


22
您有三个选择:1.仅使用本地嵌套片段定位4.2。2.使用支持库中的嵌套片段作为目标4.x 3.不要将嵌套片段用于任何其他平台目标方案。这应该可以回答您的问题。此外,您不能使用xml布局中嵌入的嵌套片段,所有片段都必须添加到代码中。在没有支持库的情况下,几乎没有任何好的示例可以显示片段用法的最佳实践 -支持片段框架复制了本机片段,因此任何示例都应该以任何一种方式工作。
Luksprog

@Luksprog感谢您的评论。我更喜欢您的解决方案2,并且frament在支持库中可以很好地工作,但是ActionBar中的Tabs不能-afaik,我需要使用ActionBarSherlock,但是这些Tabs不会集成到ActionBar中,而只能集成在下面(这是' t对于4.x是必需的)。并且ActionBar.TabListener仅支持android.app.Fragment中的Fragments,而不支持该支持库中的Fragments。
Mathias Conradt

2
我不熟悉“银河”选项卡上的“联系人”应用程序,但请记住,您可能总是会面对的自定义实现ActionBar(由三星内部构建)。仔细看看ActionBarSherlock,如果有空间,它在ActionBar中具有选项卡。
Luksprog

4
@Luksprog我相信您已经提供了唯一的答案,您是否愿意将它作为一个正确的答案。
2013年

1
@Pork我提出这个问题的主要原因是:无需使用支持库及其所有其他视图元素,是否有任何嵌套片段的解决方法。意思是,如果我切换到支持库,则将使用FragmentActivity而不是Fragment。但是我确实想使用Fragment,我只想替代Nested Fragments,而不是所有v4组件。即通过其他开放源代码库等。例如,上面的屏幕截图在4.0上运行,我想知道他们是否正在使用ABS,SupportLib或其他任何工具。
Mathias Conradt

Answers:


60

局限性

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

因此,您必须通过代码添加片段,这似乎是一个问题,但是从长远来看,这会使您的布局具有超强的灵活性。

那么不使用嵌套getChildFragmentManger呢?背后的本质childFragmentManager是它推迟加载,直到上一个片段事务完成为止。当然,只有4.2或支持库自然支持它。

没有ChildManager的嵌套-解决方案

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

见下文; 这是一个Fragment延迟加载的时间,因此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


您如何处理布局嵌套片段?
pablisco 2014年

我看到此工作的唯一方法是使用CustomLayoutInflater,当您遇到该fragment元素时,您将覆盖超级实现并尝试自己解析/添加。但这将花费很多精力,这超出了StackOverflow问题的范围。
克里斯·詹金斯(Chris.Jenkins),2014年

嗨,有人可以帮我解决这个问题吗?我真的被卡住了..stackoverflow.com
questions/32240138/

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

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


1

由于NavigationDrawer,TabHost和ViewPager的组合,我不得不处理这个确切的问题,由于TabHost,使用支持库会很复杂。然后,我还必须支持JellyBean 4.1的最小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“”的同级。这样一来,我可以在预览器中查看任何屏幕尺寸的内容。无论如何,它们的权重将在运行时在代码中进行控制。


嗨,我很好奇您为什么选择使用TabHost代替带有Tabs的ActionBar?我本人从TabHost切换到了ActionBar,我的代码变得更简洁,紧凑了……
IgorGanapolsky 2014年

据我所记得,在ActionBar中使用选项卡的一个缺点是它会自动决定将它们显示为微调框下拉菜单(在小屏幕的情况下),这对我不利。但是我不确定100%。
WindRider 2014年

@Igor,我在这里某处读到SO,使用ActionBar带有a的标签Navigation Drawer并不好,因为它将标签自动放置在抽屉视图上。抱歉,我没有支持此操作的链接。
Azurespot,2015年

1
@NoniA。blog.xamarin.com/android-tips-hello-toolbar-goodbye-action-bar 您是否完全关注Android的发展?
IgorGanapolsky

1
哇,谢谢@Igor的链接!我一定会检查出来的。我仍然是一个初学者,因此要学习Android还需要学习一百万种其他东西,但是这看起来像是一颗宝石!再次感谢。
Azurespot'2

1

建立在@ Chris.Jenkins答案的基础上,这对我来说一直很有效,可以在生命周期事件(有引发IllegalStateExceptions的趋势)中删除碎片的解决方案。这结合使用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();
    }
}

1

尽管OP可能有特殊的情况使他无法使用支持库,但大多数人都应该使用它。Android文档建议这样做,它将使您的应用程序可为最广泛的受众使用。

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

在此处输入图片说明


我是OP。不使用支持库的原因是因为它是公司的内部应用程序,使用的硬件明确定义为> = 4.0和<= 4.1。无需覆盖广泛的受众,这是内部人员,无意在公司外部使用该应用程序。支持库的唯一原因是要向后兼容-但人们希望使用支持库可以做的所有事情,没有它,您应该能够“自然地”实现。为什么“本机”更高版本的功能要少于支持库,其功能仅是向下兼容。
Mathias Conradt

1
当然,您当然可以使用支持库,我也可以。只是不明白为什么Google仅在支持库中提供功能,而不在外部提供功能,或者为什么他们甚至称其为支持库而不将其作为整体标准(如果无论如何都是最佳实践)。这是一篇关于支持库的好文章:martiancraft.com/blog/2015/06/android-support-library
Mathias

@Mathias,好文章。
Suragch
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.