サポートライブラリを使用しないAndroid 4.0、4.1(<4.2)でのネストされたフラグメントのベストプラクティス


115

私は4.0および4.1タブレット用のアプリを書いています。そのため、サポートライブラリ(必要でない場合)を使用したくないので、4.x APIのみを使用します。

だから私のターゲットプラットフォームは次のように非常によく定義されています:> = 4.0および<= 4.1

アプリにはマルチペインレイアウト(2つのフラグメント、左側に小さなフラグメント、右側に1つのコンテンツフラグメント)とタブ付きのアクションバーがあります。

これに似ています:

ここに画像の説明を入力してください

アクションバーのタブをクリックすると、「外部」フラグメントが変更され、内部フラグメントは2つのネストされたフラグメントを持つフラグメントになります(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アプリを書くことは単に不可能ですか?(Fragmentなどの代わりにFragmentActivityを使用する必要がありますか?) または、ベストプラクティスは何ですか?


私が現在開発で抱えている問題は、まさにこの声明です:

Android Support Libraryはネストされたフラグメントもサポートするようになり、ネストされたフラグメントデザインを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


22
3つのオプションがあります。1.ネイティブのネストされたフラグメントを持つ4.2のみをターゲットにします。2.サポートライブラリのネストされたフラグメントで4.xをターゲットにします。3.他のプラットフォームターゲットシナリオでネストされたフラグメントを使用しないでください。これはあなたの質問に答えるはずです。また、XMLレイアウトに埋め込まれたネストされたフラグメントを使用することはできません。それらすべてをコードに追加する必要があります。サポートライブラリなしでフラグメントを使用するためのベストプラクティスを示す良い例はほとんどありません。サポートフラグメントフレームワークはネイティブのものを複製するため、どの例でもどちらの方法でも機能します。
Luksprog 2013年

@Luksprogコメントありがとうございます。私はあなたの解決策2を好み、フレームはサポートライブラリでうまく機能しますが、ActionBarのタブはそうではありません-私はActionBarSherlockを使用する必要がありますが、タブはActionBarに統合されず、その下だけです(これは4.xには必要です)。また、ActionBar.TabListenerはandroid.app.Fragmentからのフラグメントのみをサポートし、サポートライブラリからはサポートしません。
Mathias Conradt 2013年

2
私はGalaxyタブの連絡先アプリについてはよく知りませんが、ActionBar(Samsungが自社で構築した)カスタム実装に常に直面する可能性があることを覚えておいてください。ActionBarSherlockをよく見てください。スペースがある場合は、ActionBarにタブがあります。
Luksprog 2013年

4
@Luksprog私はあなたが与えるべき唯一の答えをすでに提供していると思います、あなたはそれを適切な答えとして入れてもらえますか?
Warpzit 2013年

1
@Pork質問の主な理由は、サポートライブラリとその他すべてのビュー要素を使用せずにネストされたフラグメントの回避策があるかどうかです。つまり、サポートライブラリに切り替えると、FragmentではなくFragmentActivityを使用することになります。しかし、私はフラグメントを使用したいのですが、必要なのはNested Fragmentsの代わりですが、すべてのv4コンポーネントではありません。つまり、他のオープンソースライブラリなどを介して。たとえば、上記のスクリーンショットは4.0で実行されており、ABS、SupportLib、またはその他のものを使用しているのではないかと思います。
Mathias Conradt 2013年

回答:


60

制限事項

そのFragmentManagerため、使用するバージョンに関係なく、別のフラグメント内にフラグメントをネストすることは、xmlでは不可能です。

したがって、コードを使用してフラグメントを追加する必要があります。これは問題のように思えるかもしれませんが、長い目で見ればレイアウトが非常に柔軟になります。

使用せずに入れ子にするのgetChildFragmentMangerですか?背後にchildFragmentManagerある本質は、前のフラグメントトランザクションが完了するまでロードを延期することです。そしてもちろん、それは4.2またはサポートライブラリでのみ自然にサポートされていました。

ChildManagerを使用しないネスト-ソリューション

ソリューション、確かに!私はこれを長い間行ってきました(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);
}

私はそれを「ベストプラクティス」とは考えていませんが、このハックを使用したライブアプリがあり、まだ問題はありません。

私はこの方法を使用してビューページャーを埋め込みます-https ://gist.github.com/chrisjenx/3405429


ネストされたフラグメントのレイアウトをどのように処理しますか?
pablisco 14

この動作を確認できる唯一の方法は、CustomLayoutInflaterを使用することです。fragment要素に遭遇したら、スーパー実装をオーバーライドして、自分で構文解析/ インフレートしようとします。しかし、それはかなりの努力であり、StackOverflowの質問の範囲外です。
Chris.Jenkins、2014

こんにちは、この問題で誰かが私を助けてくれますか?私は本当に行き詰まっています。stackoverflow.com/questions/32240138
Nicks

2

これを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-親フラグメントにインターフェースを実装する

    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 extends 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(最上位のタブ付きフラグメントの1つのみ)
=ネストされたフラグメントが必要(JellyBean 4.1ではサポートされない)

私の解決策は、実際にフラグメントをネストすることなく、フラグメントのネストのような錯覚を作成することでした。これを行うには、メインアクティビティでTabHostおよびViewPagerを使用して、layout_weightを0と1の間で切り替えることによって可視性を管理する2つの兄弟ビューを管理します。

//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」」の兄弟であることに注意してください。これは、どの画面サイズでもプレビューアで確認できるようにするためです。とにかく、それらの重みは実行時にコードで操作されます。


こんにちは、タブ付きのActionBarではなくTabHostを選択した理由を知りたいのですが。私自身もTabHostからActionBarだけに切り替え、コードがより簡潔でコンパクトになりました...
IgorGanapolsky

私が覚えている限りでは、ActionBarでタブを使用することの1つの欠点は、それがスピナードロップダウンメニューとして表示するように自動的に決定することです(小さな画面の場合)。しかし、私は100%確実ではありません。
WindRider 2014年

@イゴール、私がここでどこかで読んだのは、タブをa と一緒にSO使用ActionBarすると、Navigation Drawer引き出しのビューの上にタブが自動的に配置されるため、良くありません。これをバックアップするためのリンクがありません。
Azurespot 2015

1
@NoniA。blog.xamarin.com/android-tips-hello-toolbar-goodbye-action-bar Androidの開発をまったくフォローしていますか?
IgorGanapolsky 2015

1
うわー、リンク@Igorをありがとう!私は確かにこれをチェックします。私はまだ初心者なので、Androidで学ぶべきことが他にもたくさんありますが、これは宝石のようです!再度、感謝します。
Azurespot 2015

1

@ Chris.Jenkinsの回答に基づいて構築されたこれは、ライフサイクルイベント(IllegalStateExceptionsをスローする傾向がある)中にフラグメントを削除するために私にとってうまく機能しているソリューションです。これは、ハンドラーアプローチと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でした。サポートlibを使用しない理由は、それが会社の内部アプリであり、使用されたハードウェアが> = 4.0および<= 4.1として明確に定義されていたためです。幅広いオーディエンスにリーチする必要はなく、社内スタッフであり、社外でアプリを使用する意図はありませんでした。サポートライブラリの唯一の理由は下位互換性です-しかし、サポートライブラリで実行できるすべてのことは、それなしで「自然に」達成できるはずです。「ネイティブ」の上位バージョンが、下位互換性のみを目的とするサポートライブラリよりも機能が少ないのはなぜですか。
Mathias Conradt 2016

1
それにもかかわらず、もちろん、サポートライブラリを使用でき、私も使用できました。とにかく、Googleがサポートライブラリでのみ機能を提供し、外部では機能を提供しない理由、またはそれがサポートライブラリと呼ばれ、全体的な標準にならない理由を理解していません。サポートライブラリに関する優れた記事を以下に示します。martiancraft.com
Mathias Conradt

@マティアス、素敵な記事。
Suragch 2016
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.