4

期望的结果

我想在横向模式下在屏幕的左侧/右侧有带有自定义项目的垂直列表,在纵向模式下在屏幕的顶部/底部有水平列表。水平/垂直列表应该是Fragment这样我以后可以在智能手机版本中重复使用它。最低 SDK 版本为 13 (Android 3.2)。

我的尝试

我的自定义Activity有一个自定义LayersFragment和另一个自定义View。在纵向模式下,片段与父级的左侧对齐。在横向模式下与父母的底部对齐。

LayersFragment纵向和横向模式也有不同的布局。在纵向模式下是Gallery,在横向模式下是ListView

因为GalleryListViewAdapterView<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 - 纵向模式的 XML 布局LayersFragment

<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.xmlActivity - 我的自定义纵向模式的 XML 布局。横向模式而不是android:layout_alignParentBottomhas的布局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
}

问题

从横向旋转到纵向(ListView -> Gallery)时,我得到了这个 ClassCastException

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

而这个从纵向旋转到横向(图库-> ListView)

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)

我该如何解决这个问题,或者我应该寻找其他解决方案?

我的意见

屏幕方向更改时会出现问题。我认为问题出在 and 的“默认实现”ListViewGallery。他们尝试在方向更改后恢复其SavedStatein onRestoreInstanceState,但View已更改并抛出 ClassCastException。

谢谢,

亚历山大·伊利奇

4

5 回答 5

1

假设fragment没有放在backstack上,fragment实例也没有保留(主要是我也不知道会有什么效果),每次方向改变都会运行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 期间,我没有将第一个可见位置存储在首选项中,而是直接将其与我的数据一起存储,片段稍后可以在将数据加载到适配器时使用这些数据。如果在当前位置之前或之后添加数据,这也使得计算新位置变得相对简单。任何数据更新都相对简单。Fragment通过监听器获知更新,根据变化修改Adapter,设置AdapterView的正确调整位置。

根据一次对适配器进行多少更改,您可能还希望在创建新适配器时使用 Adapter.setNotifyOnChange(false) 并在更新适配器后使用 Adapter.notifyDataSetChanged()。这可以防止在进行所有更改之前通知 AdapterView 数据已更改。

于 2012-10-23T23:53:00.770 回答
0

与其尝试将交替视图绑定到一个 BaseAdapter,不如使用两个适当的适配器并简单地在不同视图之间传递相关信息。

像这样的东西:

mListView.setPosition(mGallery.getFirstVisiblePosition());

反之亦然。您可能需要将此信息保存在 onPause() 中,因为我不知道您是否可以引用不再可见的视图的第一个可见位置。

于 2012-06-18T17:56:40.543 回答
0

这是一个非常有趣的方法。前片段,我会说您已经为您完成了工作,因为代码类型自然希望变体 XML 布局文件具有具有相同 ID 的相同类型的控件。但是,如您所知,使用 Fragment,您所要做的就是说出它的去向并将其链接到一个类。我看不出为什么你不能有不同的布局(例如,纵向和横向)实例化不同的片段类。

于 2012-06-18T17:56:41.217 回答
0

笔记

下面写的解决方案是“轻解决方案”,只是避免ClassCastException,它还不是最终的。微调当然是必要的。由于使用了 Java 反射并且字段名称是硬编码的,因此在名称更改或以不同方式实现的平台上可能会失败。

写完我的应用程序后,我会立即用更多细节更新这个答案。

解决方案

您必须覆盖onRestoreInstanceState和。在他们两个中,您都必须进行适当的转换。在你转换给定和转换为。ListViewGalleryListViewParceableAbsListView$SavedStateGalleryAbsSpinner$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

如果您使用两个不同的列表视图(一个可扩展),请确保它们在 xml 布局中具有不同的 id。

于 2013-06-19T17:13:51.940 回答