340

3 つのタブを持つアプリケーションがあります。

各タブには独自のレイアウト .xml ファイルがあります。main.xml には独自のマップ フラグメントがあります。アプリケーションを最初に起動したときに表示されるものです。

タブを切り替えるときを除いて、すべて正常に動作します。マップ フラグメント タブに戻ろうとすると、このエラーが発生します。他のタブへの切り替えは問題なく機能します。

ここで何が問題なのですか?

これは、私のメイン クラスと main.xml、および私が使用する関連クラスです (下部にエラー ログもあります)。

メインクラス

package com.nfc.demo;

import android.app.ActionBar;
import android.app.ActionBar.Tab;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentTransaction;
import android.os.Bundle;
import android.widget.Toast;

public class NFCDemoActivity extends Activity {

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        ActionBar bar = getActionBar();
        bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
        bar.setDisplayOptions(0, ActionBar.DISPLAY_SHOW_TITLE);

        bar.addTab(bar
                .newTab()
                .setText("Map")
                .setTabListener(
                        new TabListener<MapFragment>(this, "map",
                                MapFragment.class)));
        bar.addTab(bar
                .newTab()
                .setText("Settings")
                .setTabListener(
                        new TabListener<SettingsFragment>(this, "settings",
                                SettingsFragment.class)));
        bar.addTab(bar
                .newTab()
                .setText("About")
                .setTabListener(
                        new TabListener<AboutFragment>(this, "about",
                                AboutFragment.class)));

        if (savedInstanceState != null) {
            bar.setSelectedNavigationItem(savedInstanceState.getInt("tab", 0));
        }
        // setContentView(R.layout.main);

    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putInt("tab", getActionBar().getSelectedNavigationIndex());
    }

    public static class TabListener<T extends Fragment> implements
            ActionBar.TabListener {
        private final Activity mActivity;
        private final String mTag;
        private final Class<T> mClass;
        private final Bundle mArgs;
        private Fragment mFragment;

        public TabListener(Activity activity, String tag, Class<T> clz) {
            this(activity, tag, clz, null);
        }

        public TabListener(Activity activity, String tag, Class<T> clz,
                Bundle args) {
            mActivity = activity;
            mTag = tag;
            mClass = clz;
            mArgs = args;

            // Check to see if we already have a fragment for this tab,
            // probably from a previously saved state. If so, deactivate
            // it, because our initial state is that a tab isn't shown.
            mFragment = mActivity.getFragmentManager().findFragmentByTag(mTag);
            if (mFragment != null && !mFragment.isDetached()) {
                FragmentTransaction ft = mActivity.getFragmentManager()
                        .beginTransaction();
                ft.detach(mFragment);
                ft.commit();
            }
        }

        public void onTabSelected(Tab tab, FragmentTransaction ft) {
            if (mFragment == null) {
                mFragment = Fragment.instantiate(mActivity, mClass.getName(),
                        mArgs);
                ft.add(android.R.id.content, mFragment, mTag);
            } else {
                ft.attach(mFragment);
            }
        }

        public void onTabUnselected(Tab tab, FragmentTransaction ft) {
            if (mFragment != null) {
                ft.detach(mFragment);
            }
        }

        public void onTabReselected(Tab tab, FragmentTransaction ft) {
            Toast.makeText(mActivity, "Reselected!", Toast.LENGTH_SHORT)
                         .show();
        }
    }

}

main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <fragment
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/mapFragment"
        android:name="com.google.android.gms.maps.MapFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>

関連するクラス ( MapFragment.java )

package com.nfc.demo;

import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

public class MapFragment extends Fragment {

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        super.onCreateView(inflater, container, savedInstanceState);
        return inflater.inflate(R.layout.main, container, false);
    }

    public void onDestroy() {
        super.onDestroy();
    }
}

エラー

android.view.InflateException: Binary XML file line #7: 
     Error inflating class fragment
   at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:704)
   at android.view.LayoutInflater.rInflate(LayoutInflater.java:746)
   at android.view.LayoutInflater.inflate(LayoutInflater.java:489)
   at android.view.LayoutInflater.inflate(LayoutInflater.java:396)
   at com.nfc.demo.MapFragment.onCreateView(MapFragment.java:15)
   at android.app.Fragment.performCreateView(Fragment.java:1695)
   at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:885)
   at android.app.FragmentManagerImpl.attachFragment(FragmentManager.java:1255)
   at android.app.BackStackRecord.run(BackStackRecord.java:672)
   at android.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1435)
   at android.app.FragmentManagerImpl$1.run(FragmentManager.java:441)
   at android.os.Handler.handleCallback(Handler.java:725)
   at android.os.Handler.dispatchMessage(Handler.java:92)
   at android.os.Looper.loop(Looper.java:137)
   at android.app.ActivityThread.main(ActivityThread.java:5039)
   at java.lang.reflect.Method.invokeNative(Native Method)
   at java.lang.reflect.Method.invoke(Method.java:511)
   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:793)
   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:560)
   at dalvik.system.NativeStart.main(Native Method)

Caused by: java.lang.IllegalArgumentException: 
     Binary XML file line #7: Duplicate id 0x7f040005, tag null, or 
     parent id 0xffffffff with another fragment for 
     com.google.android.gms.maps.MapFragment
   at android.app.Activity.onCreateView(Activity.java:4722)
   at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:680)
   ... 19 more
4

23 に答える 23

400

Mattが提案する答えは機能しますが、マップが再作成および再描画されるため、常に望ましいとは限りません。たくさんの試行錯誤の末、私は自分に合った解決策を見つけました。

private static View view;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    if (view != null) {
        ViewGroup parent = (ViewGroup) view.getParent();
        if (parent != null)
            parent.removeView(view);
    }
    try {
        view = inflater.inflate(R.layout.map, container, false);
    } catch (InflateException e) {
        /* map is already there, just return view as it is */
    }
    return view;
}

適切な方法として、R.id.mapFragment(android:id = "@ + id / mapFragment")を使用した「map.xml」(R.layout.map)を次に示します。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/mapLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <fragment xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/mapFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        class="com.google.android.gms.maps.SupportMapFragment" />
</LinearLayout>

これがお役に立てば幸いですが、悪影響がないことを保証することはできません。

編集:アプリケーションを終了して再開する場合など、いくつかの悪影響がありました。アプリケーションは必ずしも完全にシャットダウンされるとは限らないため(ただし、バックグラウンドでスリープ状態になるだけです)、アプリケーションを再起動すると、前に送信したコードは失敗します。マップの出入りとアプリケーションの終了と再起動の両方で、コードを自分に合ったものに更新しました。try-catchビットにはあまり満足していませんが、十分に機能しているようです。スタックトレースを見ると、マップフラグメントがFragmentManagerにあるかどうかを確認でき、try-catchブロックは不要で、コードが更新されていることがわかりました。

その他の編集:結局のところ、そのtry-catchが必要であることがわかりました。マップフラグメントをチェックするだけでは、結局あまりうまく機能しないことがわかりました。Blergh。

于 2013-02-04T20:50:11.770 に答える
284

問題は、あなたがやろうとしていることが実行されるべきではないということです。他のフラグメント内でフラグメントを膨らませるべきではありません。Androidのドキュメントから:

注: レイアウトに <fragment> が含まれている場合、レイアウトをフラグメントにインフレートすることはできません。ネストされたフラグメントは、フラグメントに動的に追加された場合にのみサポートされます。

ここで紹介するハックでタスクを達成できるかもしれませんが、実行しないことを強くお勧めします。これらのハックが、別のフラグメントを含むフラグメントのレイアウトを拡張しようとするときに、新しい Android OS が行うことを確実に処理することは不可能です。

フラグメントを別のフラグメントに追加する Android でサポートされている唯一の方法は、子フラグメント マネージャーからのトランザクションを使用することです。

XML レイアウトを空のコンテナーに変更するだけです (必要に応じて ID を追加します)。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/mapFragmentContainer"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
</LinearLayout>

次に FragmentonViewCreated(View view, @Nullable Bundle savedInstanceState)メソッドで:

@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    FragmentManager fm = getChildFragmentManager();
    SupportMapFragment mapFragment = (SupportMapFragment) fm.findFragmentByTag("mapFragment");
    if (mapFragment == null) {
        mapFragment = new SupportMapFragment();
        FragmentTransaction ft = fm.beginTransaction();
        ft.add(R.id.mapFragmentContainer, mapFragment, "mapFragment");
        ft.commit();
        fm.executePendingTransactions();
    }
    mapFragment.getMapAsync(callback);
}
于 2013-11-06T14:53:58.873 に答える
184

同じ問題があり、クラスMapFragmentonDestroy()メソッドで手動で削除することで解決できました。XML で ID を使用しFragmentて参照するコードを次に示します。MapFragment

@Override
public void onDestroyView() {
    super.onDestroyView();
    MapFragment f = (MapFragment) getFragmentManager()
                                         .findFragmentById(R.id.map);
    if (f != null) 
        getFragmentManager().beginTransaction().remove(f).commit();
}

手動で削除しないMapFragmentと、地図ビューを再作成/表示するために多くのリソースが費やされないように、ハングアップします。基盤を維持することはMapView、タブ間を行ったり来たりするのに最適なようですが、フラグメントで使用すると、この動作により、同じ ID を持つMapView新しいものごとに重複が作成されます。解決策は、フラグメントが膨張するたびMapFragmentに を手動で削除して、基礎となるマップを再作成することです。MapFragment

これは別の回答でも指摘しました [ 1 ]。

于 2013-01-23T16:35:09.340 に答える
10
  1. @Justin Breitfeller が述べたように、@Vidar Wahlberg ソリューションはハックであり、Android の将来のバージョンでは機能しない可能性があります。
  2. @Vidar Wahlberg は、他の解決策では「マップが再作成および再描画される可能性があり、これは常に望ましいとは限らない」ため、ハックを優先します。毎回新しいインスタンスを作成するのではなく、古いマップ フラグメントを維持することで、マップの再描画を防ぐことができます。
  3. @Matt ソリューションが機能しない (IllegalStateException)
  4. @Justin Breitfeller が引用したように、「レイアウトに が含まれている場合、レイアウトをフラグメントにインフレートすることはできません。ネストされたフラグメントは、フラグメントに動的に追加された場合にのみサポートされます。」

私の解決策:

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,                              Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.fragment_map_list, container, false);

    // init
    //mapFragment = (SupportMapFragment)getChildFragmentManager().findFragmentById(R.id.map);
    // don't recreate fragment everytime ensure last map location/state are maintain
    if (mapFragment == null) {
        mapFragment = SupportMapFragment.newInstance();
        mapFragment.getMapAsync(this);
    }
    FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
    // R.id.map is a layout
    transaction.replace(R.id.map, mapFragment).commit();

    return view;
}
于 2015-06-16T12:49:02.803 に答える
3

別の解決策:

if (view == null) {
    view = inflater.inflate(R.layout.nearbyplaces, container, false);
}

それだけです。 null でない場合は、再初期化する必要はありません。親から削除する必要はありません。

于 2015-07-10T21:30:43.030 に答える
3

レイアウトを 2 回返したり、インフレートしたりしています。一度だけインフレートしているかどうかを確認してください。

于 2019-10-31T11:16:44.510 に答える
2

幸いなことに、この問題は MapFragment の実装が原因ではありません。幸いなことに、ネストされたフラグメントは rev 11 のサポート ライブラリを介してのみサポートされているため、これは機能しません。

私の実装には、2 つのタブ (ビューページャーなし) を持つアクションバー (タブ モード) を持つアクティビティがあり、1 つにはマップがあり、もう 1 つにはエントリのリストがあります。もちろん、タブフラグメント内で MapFragment を使用するのは非常に単純でした。マップタブに切り替えるたびにアプリがクラッシュしました。

(タブフラグメントが他のフラグメントを含むレイアウトを膨らませる場合にも同じ問題が発生します)。

1 つのオプションは、(MapFragment の代わりに) MapView を使用することですが、多少のオーバーヘッドがあります ( layout.xmlのドロップイン置換としてMapView Docsを参照してください。別のオプションは、rev. 11 からサポート ライブラリを使用することですが、その後、プログラムによるアプローチを取ります。ネストされたフラグメントはレイアウトを介してサポートされていないため、または、フラグメントを明示的に破棄することによりプログラムで回避するだけです(Matt / Vidarからの回答のように)、ところで:MapView(オプション1)を使用して同じ効果が得られます。

しかし、実際には、タブで移動するたびにマップを失いたくありませんでした。つまり、マップをメモリに保持し、アクティビティを閉じたときにのみクリーンアップしたかったので、タブでマップを非表示/表示するだけにしました。FragmentTransaction / hideを参照してください。

于 2013-12-08T20:45:24.993 に答える
2

この問題がまだ発生している場合、タブ内のマップでこのエラーが発生しないようにする最善の方法は、タブに使用されるフラグメント内にSupportMapFragmenta をネストするのではなく、フラグメントを拡張することSupportMapFragmentです。

3 番目のタブで SupportMapFragment を使用して、これをViewPager機能させました。FragmentPagerAdapter

一般的な構造は次のとおりです。メソッドをオーバーライドする必要onCreateView()はなく、レイアウト xml をインフレートする必要もないことに注意してください。

public class MapTabFragment extends SupportMapFragment 
                                    implements OnMapReadyCallback {

    private GoogleMap mMap;
    private Marker marker;


    public MapTabFragment() {
    }

    @Override
    public void onResume() {
        super.onResume();

        setUpMapIfNeeded();
    }

    private void setUpMapIfNeeded() {

        if (mMap == null) {

            getMapAsync(this);
        }
    }

    @Override
    public void onMapReady(GoogleMap googleMap) {

        mMap = googleMap;
        setUpMap();
    }

    private void setUpMap() {

        mMap.setMyLocationEnabled(true);
        mMap.setMapType(GoogleMap.MAP_TYPE_HYBRID);
        mMap.getUiSettings().setMapToolbarEnabled(false);


        mMap.setOnMapClickListener(new GoogleMap.OnMapClickListener() {

            @Override
            public void onMapClick(LatLng point) {

                //remove previously placed Marker
                if (marker != null) {
                    marker.remove();
                }

                //place marker where user just clicked
                marker = mMap.addMarker(new MarkerOptions().position(point).title("Marker")
                        .icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_MAGENTA)));

            }
        });

    }


}

結果:

ここに画像の説明を入力

テストに使用した完全なクラス コードを次に示します。これには、最初の 2 つのタブに使用されるプレースホルダー フラグメントと、3 番目のタブに使用されるマップ フラグメントが含まれます。

public class MainActivity extends AppCompatActivity implements ActionBar.TabListener{


    SectionsPagerAdapter mSectionsPagerAdapter;

    ViewPager mViewPager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());

        // Set up the ViewPager with the sections adapter.
        mViewPager = (ViewPager) findViewById(R.id.pager);
        mViewPager.setAdapter(mSectionsPagerAdapter);

        final ActionBar actionBar = getSupportActionBar();
        actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);

        mViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
            @Override
            public void onPageSelected(int position) {
                actionBar.setSelectedNavigationItem(position);
            }
        });

        for (int i = 0; i < mSectionsPagerAdapter.getCount(); i++) {
            actionBar.addTab(actionBar.newTab().setText(mSectionsPagerAdapter.getPageTitle(i)).setTabListener(this));
        }

    }


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {

        int id = item.getItemId();

        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

    @Override
    public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft) {
        mViewPager.setCurrentItem(tab.getPosition());
    }

    @Override
    public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) {

    }

    @Override
    public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) {

    }


    public class SectionsPagerAdapter extends FragmentPagerAdapter {

        public SectionsPagerAdapter(FragmentManager fm) {
            super(fm);
        }

        @Override
        public Fragment getItem(int position) {

            switch (position) {
                case 0:
                    return PlaceholderFragment.newInstance(position + 1);
                case 1:
                    return PlaceholderFragment.newInstance(position + 1);
                case 2:
                    return MapTabFragment.newInstance(position + 1);
            }

            return null;
        }

        @Override
        public int getCount() {
            // Show 3 total pages.
            return 3;
        }

        @Override
        public CharSequence getPageTitle(int position) {
            Locale l = Locale.getDefault();

            switch (position) {
                case 0:
                    return getString(R.string.title_section1).toUpperCase(l);
                case 1:
                    return getString(R.string.title_section2).toUpperCase(l);
                case 2:
                    return getString(R.string.title_section3).toUpperCase(l);
            }
            return null;
        }
    }


    public static class PlaceholderFragment extends Fragment {

        private static final String ARG_SECTION_NUMBER = "section_number";

        TextView text;

        public static PlaceholderFragment newInstance(int sectionNumber) {
            PlaceholderFragment fragment = new PlaceholderFragment();
            Bundle args = new Bundle();
            args.putInt(ARG_SECTION_NUMBER, sectionNumber);
            fragment.setArguments(args);
            return fragment;
        }

        public PlaceholderFragment() {
        }

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {
            View rootView = inflater.inflate(R.layout.fragment_main, container, false);

            text = (TextView) rootView.findViewById(R.id.section_label);
            text.setText("placeholder");

            return rootView;
        }
    }

    public static class MapTabFragment extends SupportMapFragment implements
            OnMapReadyCallback {

        private static final String ARG_SECTION_NUMBER = "section_number";

        private GoogleMap mMap;
        private Marker marker;


        public static MapTabFragment newInstance(int sectionNumber) {
            MapTabFragment fragment = new MapTabFragment();
            Bundle args = new Bundle();
            args.putInt(ARG_SECTION_NUMBER, sectionNumber);
            fragment.setArguments(args);
            return fragment;
        }

        public MapTabFragment() {
        }

        @Override
        public void onResume() {
            super.onResume();

            Log.d("MyMap", "onResume");
            setUpMapIfNeeded();
        }

        private void setUpMapIfNeeded() {

            if (mMap == null) {

                Log.d("MyMap", "setUpMapIfNeeded");

                getMapAsync(this);
            }
        }

        @Override
        public void onMapReady(GoogleMap googleMap) {
            Log.d("MyMap", "onMapReady");
            mMap = googleMap;
            setUpMap();
        }

        private void setUpMap() {

            mMap.setMyLocationEnabled(true);
            mMap.setMapType(GoogleMap.MAP_TYPE_HYBRID);
            mMap.getUiSettings().setMapToolbarEnabled(false);


            mMap.setOnMapClickListener(new GoogleMap.OnMapClickListener() {

                @Override
                public void onMapClick(LatLng point) {

                    Log.d("MyMap", "MapClick");

                    //remove previously placed Marker
                    if (marker != null) {
                        marker.remove();
                    }

                    //place marker where user just clicked
                    marker = mMap.addMarker(new MarkerOptions().position(point).title("Marker")
                            .icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_MAGENTA)));

                    Log.d("MyMap", "MapClick After Add Marker");

                }
            });

        }
    }
}
于 2015-07-11T01:15:46.007 に答える
0

ネストされたフラグメントは現在サポートされていません。サポートパッケージ、リビジョン11をお試しください。

于 2013-01-13T20:50:24.570 に答える
0

子フラグメントの以前の App-Compat ライブラリにはいくつかのバグがあったと思います。私は@Vidar Wahlbergと@Mattのansを試しましたが、うまくいきませんでした。appcompat ライブラリを更新した後、余分な作業をしなくてもコードが完全に実行されるようになりました。

于 2015-07-16T11:07:33.173 に答える
0

レイアウト ファイルでカスタムMapFragmentクラスを参照しようとしていませんか?

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <fragment
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/mapFragment"
        android:name="com.nfc.demo.MapFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>
于 2013-10-11T10:16:55.993 に答える
0

私はこれをviewPagerに持っていましたが、クラッシュはフラグメントに独自のタグが必要であり、同じフラグメントのタグまたはIDの重複が許可されていないためです。

于 2014-11-20T09:44:25.510 に答える
0

mapView 親レイアウトの ID (android:id="@+id/maps_dialog") を設定してみてください。私のために働きます。

于 2018-10-24T17:34:26.640 に答える