web-dev-qa-db-ja.com

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

私は4.0と4.1タブレット用のアプリを書いていますが、そのためにサポートライブラリを使用したくありません(必要でない場合)4。したがって、x APIのみ。

したがって、ターゲットプラットフォームは、> = 4.0および<= 4.1として非常に明確に定義されています。

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

これに似ています:

enter image description here

アクションバーのタブをクリックすると、「外側」フラグメントが変更され、内側のフラグメントは、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サポートライブラリもネストフラグメントをサポートするようになったため、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

115
Mathias Conradt

制限事項

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

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

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

ChildManagerなしのネスト-ソリューション

解決策、確かに!私はこれを長い間やっています(ViewPagerが発表されたので)。

下記参照;これはFragmentであり、ロードを延期するため、Fragmentsをその中にロードできます。

その非常にシンプルな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

60
Chris.Jenkins

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は、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()関数を使用した場合と同じ流動的な機能を取得できます。お気付きかもしれませんが、子フラグメントは実際には親フラグメントの子ではなく、アクティビティの子になりました。これは実際に回避することはできません。

2
Alex

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

1
Kain Shin

OPにはサポートライブラリの使用を妨げる特別な状況があるかもしれませんが、ほとんどの人はOPを使用する必要があります。 Androidのドキュメントで推奨されており、これによりアプリを可能な限り幅広いユーザーが利用できるようになります。

ここのより完全な答え で、サポートライブラリでネストされたフラグメントを使用する方法を示す例を作成しました。

enter image description here

1
Suragch

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