4

望ましい結果

ランドスケープモードでは画面の左側/右側にカスタムアイテムを含む垂直リストを、ポートレートモードでは画面の上部/下部に水平リストを表示したいと思います。水平/垂直リストはFragment、後でスマートフォンバージョンで再利用できるようにする必要があります。SDKの最小バージョンは13(Android 3.2)です。

私の試み

私の習慣Activityには単一の習慣LayersFragmentと別の習慣がありViewます。ポートレートモードでは、フラグメントは親の左側に配置されます。横向きモードでは、親の下に揃えられます。

LayersFragmentポートレートモードとランドスケープモードのレイアウトも異なります。ポートレートモードではGallery、ランドスケープモードではListViewです。

GalleryListViewはのサブクラスであるため、AdapterView<Adapter>この親クラスを使用しBaseAdapterてアイテムを設定し、リッスンしOnItemClicksます。

PhotoEditorActivityポートレートモード PhotoEditorActivityランドスケープモード

リソースの詳細

frag_layers.xml-横向きのXMLレイアウトLayersFragment

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <ListView
        android:id="@android:id/list"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

frag_layers.xml-LayersFragmentポートレートモードのXMLレイアウト。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <Gallery
        android:id="@android:id/list"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

activity_photo_editor.xml-ActivityポートレートモードでのカスタムのXMLレイアウト。android:layout_alignParentBottomの代わりに横向きモードのレイアウトandroid:layout_alignParentLeft

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <fragment
        android:id="@+id/photo_editor_layouts"
        class="rs.ailic.android.heritage.ui.LayersFragment"
        android:layout_width="match_parent"
        android:layout_height="@dimen/photo_editor_layouts_size"
        android:layout_alignParentBottom="true" />

    <!-- Not relevant. -->

</RelativeLayout>

コードの詳細

クラスLayersFragment

public class LayersFragment extends Fragment implements OnItemClickListener {

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

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        mLayersAdapter = new LayersAdapter();
        mLayersView = (AdapterView<Adapter>) getView().findViewById(android.R.id.list);
        mLayersView.setOnItemClickListener(this);
        mLayersView.setAdapter(mLayersAdapter);
    }

    @Override
    public void onItemClick(AdapterView<?> adapter, View view, int position, long id) {
        //Not implemented
    }

    private class LayersAdapter extends BaseAdapter {
        //Not implemented. Returning 0 in getCount().
    }
}

私のカスタムアクティビティ

public class PhotoEditorActivity extends Activity {

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

    //Not relevant
}

問題

横向きから縦向きに回転すると、このClassCastExceptionが発生します(ListView-> Gallery)

Caused by: java.lang.ClassCastException: android.widget.AbsListView$SavedState cannot be cast to android.widget.AbsSpinner$SavedState
at android.widget.AbsSpinner.onRestoreInstanceState(AbsSpinner.java:421)    
at android.view.View.dispatchRestoreInstanceState(View.java:8341)
at android.view.ViewGroup.dispatchThawSelfOnly(ViewGroup.java:2038)
at android.widget.AdapterView.dispatchRestoreInstanceState(AdapterView.java:766)
at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:2024)
at android.view.View.restoreHierarchyState(View.java:8320)
at android.app.Fragment.restoreViewState(Fragment.java:583)
at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:801)
at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:977)
at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:960)
at android.app.FragmentManagerImpl.dispatchStart(FragmentManager.java:1679)
at android.app.Activity.performStart(Activity.java:4413)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1791)
... 12 more

ポートレートからランドスケープに回転するときのこれ(ギャラリー->リストビュー)

Caused by: java.lang.ClassCastException: android.widget.AbsSpinner$SavedState cannot be cast to android.widget.AbsListView$SavedState
at android.widget.AbsListView.onRestoreInstanceState(AbsListView.java:1650)
at android.view.View.dispatchRestoreInstanceState(View.java:8341)
at android.view.ViewGroup.dispatchThawSelfOnly(ViewGroup.java:2038)
at android.widget.AdapterView.dispatchRestoreInstanceState(AdapterView.java:766)

この問題をどのように解決できますか、または別の解決策を探す必要がありますか?

私の意見

画面の向きが変わると問題が発生します。問題はとの「デフォルトの実装」にあるListViewと思いますGallery。向きを変更SavedStateした後、復元しようとしますが、が変更され、ClassCastExceptionがスローされます。onRestoreInstanceStateView

ありがとうございました、

アレクサンダルイリッチ

4

5 に答える 5

1

フラグメントがバックスタックに配置されず、フラグメントインスタンスが保持されないと仮定すると(主に、どちらの効果もわからないため)、方向が変わるたびにonCreateViewが実行されます。したがって、現在の向きに基づいて、使用するレイアウトを指定できます。ListViewとGalleryに異なるIDを設定することも重要です。

getFirstVisiblePositionとsetSelectionを使用して、現在のアダプターの位置を記憶します。これは、フラグメントが再開状態にないときにアダプター内のデータ位置が変更されない場合にのみ確実に機能します。データが変更された場合は、AdapterViewに設定する適切な位置を再計算する必要があります。

frag_layers.xml-横向きのLayersFragmentのXMLレイアウト。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <ListView
        android:id="@android:id/listview"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

frag_layers.xml-縦向きモードのLayersFragmentのXMLレイアウト。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <Gallery
        android:id="@android:id/gallery"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

クラスLayersFragment

public class LayersFragment extends Fragment implements OnItemClickListener {
    private AdapterView<Adapter> mLayersView;
    private LayersAdapter mLayersAdapter;
    private int mVisiblePosition = 0;

    @Override
    public void onPause() {
        super.onPause();
        SharedPreferences prefs = getActivity().getPreferences(Context.MODE_PRIVATE);
        SharedPreferences.Editor editor = prefs.edit();
        if (mLayersView != null) {
            editor.putInt("visiblePosition", mLayersView.getFirstVisiblePosition());
        } else {
            editor.putInt("visiblePosition", 0);
        }
        editor.commit();
    }

    @Override
    public void onResume() {
        super.onResume();
        SharedPreferences prefs = getActivity().getPreferences(Context.MODE_PRIVATE);
        mVisiblePosition = prefs.getInt("visiblePosition", 0);
        // -- Set the position that was stored in onPause.
        mLayersView.setSelection(mVisiblePosition);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        ViewGroup rootView;

        rootView = (ViewGroup)inflater.inflate(R.layout.frag_layers, container, false);

        switch (getActivity().getResources().getConfiguration().orientation ) {
        case Configuration.ORIENTATION_LANDSCAPE:
            mLayersView = (AdapterView<Adapter>)rootView.findViewById(R.id.gallery);
            break;
        default:
            mLayersView = (AdapterView<Adapter>)rootView.findViewById(R.id.listView);
            break;
        }        

        mLayersAdapter = new LayersAdapter();
        mLayersView.setOnItemClickListener(this);
        mLayersView.setAdapter(mLayersAdapter);

        return rootView;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        // -- Populate mLayersAdapter here or through a data ready listener registered with the activity?
    }

    @Override
    public void onItemClick(AdapterView<?> adapter, View view, int position, long id) {
        //Not implemented
    }

    private class LayersAdapter extends BaseAdapter {
        //Not implemented. Returning 0 in getCount().
    }

私はこのコードをテストしていませんが、私が使用しているのと同様の実装です。私の実装では、スーパーコールを除いて、onResumeとonPauseのコードを削除します。onPause中に、設定で最初に表示される位置を保存する代わりに、データと一緒に直接保存します。フラグメントは、後でデータをアダプターにロードするときに使用できます。これにより、現在の位置の前または後にデータが追加された場合に、新しい位置を比較的簡単に計算できます。その場合、データの更新は比較的簡単です。フラグメントは、リスナーを介して更新を学習し、変更に基づいてアダプターを変更し、AdapterViewの正しい調整位置を設定します。

一度にアダプターに加えられる変更の数によっては、新しいアダプターを作成するときにAdapter.setNotifyOnChange(false)を使用し、アダプターの更新後にAdapter.notifyDataSetChanged()を使用することもできます。これにより、すべての変更が行われるまで、データが変更されたことをAdapterViewに通知できなくなります。

于 2012-10-23T23:53:00.770 に答える
0

ノート

以下に書かれている解決策は「軽い解決策」であり、回避するだけClassCastExceptionであり、まだ最終的なものではありません。微調整は確かに必要です。Java リフレクションが使用され、フィールド名がハードコーディングされているため、名前が変更されているか、実装が異なるプラットフォームでは失敗する可能性があります。

アプリケーションの作成が完了したらすぐに、この回答を詳細に更新します。

解決

と でオーバーライドする必要がonRestoreInstanceStateありListViewますGallery。どちらの場合も、適切な変換を行う必要があります。ListViewあなたは与えられたに変換Parceableし、AbsListView$SavedStateに変換GalleryAbsSpinner$SavedSateます。

VerticalList - 変更済みListView

public class VerticalList extends ListView {

    public VerticalList(Context context) {
        super(context);
    }

    public VerticalList(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public VerticalList(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    public void onRestoreInstanceState(Parcelable state) {
        super.onRestoreInstanceState(SavedStateConversion.getAbsListViewSavedState(state));
    }
}

Horizo​​ntalList - 変更済みGallery

public class HorizontalList extends Gallery {

    public HorizontalList(Context context) {
        super(context);
    }

    public HorizontalList(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public HorizontalList(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    public void onRestoreInstanceState(Parcelable state) {
        super.onRestoreInstanceState(SavedStateConversion.getAbsSpinnerSavedState(state));
    }
}

SavedStateConversion - AbsListView$SavedState <-> AbsSpinner$SavedState. 変換はJava Reflectionを使用して行われます。

public class SavedStateConversion {

    private SavedStateConversion() {}


    /**
     * Converts <code>android.widget.AbsSpinner$SavedState</code> to <code>android.widget.AbsListView$SavedState</code>.
     * @param state parcelable representing <code>android.widget.AbsSpinnerSavedState</code> 
     * @return parcelable representing <code>android.widget.AbsListView$SavedState</code>
     */
    public static Parcelable getAbsListViewSavedState(Parcelable state) {
        try {
            Class<?> gss = Class.forName("android.widget.AbsSpinner$SavedState");

            /*
             * List of all fields in AbsSpinner$SavedState:
             * 
             * int position;
             * long selectedId;
             */

            Field selectedIdField = gss.getDeclaredField("selectedId");
            selectedIdField.setAccessible(true);
            Field positionField = gss.getDeclaredField("position");
            positionField.setAccessible(true);

            Parcel parcel = Parcel.obtain();
            parcel.writeLong(selectedIdField.getLong(state));
            parcel.writeLong(0);
            parcel.writeInt(0);
            parcel.writeInt(positionField.getInt(state));

            Class<?> lvss = Class.forName("android.widget.AbsListView$SavedState");
            Constructor<?> constructors[] = lvss.getDeclaredConstructors();
            Constructor<?> lvssConstructor = constructors[0];
            lvssConstructor.setAccessible(true);

            return (Parcelable) lvssConstructor.newInstance(parcel);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }   

        throw new RuntimeException("Conversion from AbsSpinner$SavedState to AbsListView$SavedState failed!");
    }

    /**
     * Converts <code>android.widget.AbsListView$SavedState</code> to <code>android.widget.AbsSpinner$SavedState</code>.
     * @param state parcelable representing <code>android.widget.AbsListView$SavedState</code> 
     * @return parcelable representing <code>android.widget.AbsSpinner$SavedState</code>
     */
    public static Parcelable getAbsSpinnerSavedState(Parcelable state) {
        try {

            Class<?> lvss = Class.forName("android.widget.AbsListView$SavedState");

            /*
             * List of all fields in AbsListView$SavedState:
             * 
             * String filter;
             * long firstId;
             * int height;
             * int position;
             * long selectedId;
             * int viewTop;
             */

            Field selectedIdField = lvss.getDeclaredField("selectedId");
            selectedIdField.setAccessible(true);
            Field positionField = lvss.getDeclaredField("position");
            positionField.setAccessible(true);

            Parcel parcel = Parcel.obtain();
            parcel.writeLong(selectedIdField.getLong(state));
            parcel.writeInt(positionField.getInt(state));

            Class<?> gss = Class.forName("android.widget.AbsSpinner$SavedState");
            Constructor<?> constructors[] = gss.getDeclaredConstructors();
            Constructor<?> gssConstructor = constructors[0];
            gssConstructor.setAccessible(true);

            return (Parcelable) gssConstructor.newInstance(parcel); 
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }

        throw new RuntimeException("Conversion from AbsListView$SavedState to AbsSpinner$SavedState failed!");
    }
}
于 2012-06-19T13:21:26.653 に答える
0

代替ビューを 1 つの BaseAdapter にバインドしようとするのではなく、2 つの適切なアダプターを使用して、異なるビュー間で関連情報を単純に受け渡してみませんか。

このようなもの:

mListView.setPosition(mGallery.getFirstVisiblePosition());

そしてその逆。表示されなくなったビューの最初の表示位置を参照できるかどうかわからないため、おそらくこの情報を onPause() に保存する必要があります。

于 2012-06-18T17:56:40.543 に答える
0

これは非常に興味深いアプローチです。プレフラグメント、私はあなたの仕事があなたのために切り取られたと思います.コードの種類は、バリアント XML レイアウト ファイルが同じ ID を持つ同じ種類のコントロールを持つことを当然期待しているためです。しかし、ご存じのように、Fragment を使用すると、その場所を指定してクラスにリンクするだけで済みます。異なるレイアウト (縦向きと横向きなど) で異なる Fragment クラスをインスタンス化できない理由がわかりません。

于 2012-06-18T17:56:41.217 に答える