垂直列表中的横向列表在横向列表中的纵向是在同一个Activity中吗?

时间:2012-06-18 17:49:08

标签: android android-layout screen-orientation

期望的结果

我希望在横向模式下在屏幕的左/右侧具有自定义项目的垂直列表,在纵向模式下在屏幕的顶部/底部具有水平列表。水平/垂直列表应为Fragment,以便稍后我可以将其重新用于智能手机版本。最小的SDK版本是13(Android 3.2)。

我的尝试

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

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

由于GalleryListViewAdapterView<Adapter>的子类,我使用此父类和BaseAdapter来填充项目并监听OnItemClicks

PhotoEditorActivity Portrait mode PhotoEditorActivity Landscape mode

资源详情

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/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>

代码详情

Class 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
}

问题

从Landscape到Portrait(ListView - &gt; 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

和从纵向旋转到风景时的这个(图库 - &gt; 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)

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

我的意见

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

谢谢,

AleksandarIlić

5 个答案:

答案 0 :(得分:1)

假设片段没有放在backstack上并且片段实例没有被保留(主要是因为我不知道效果会有什么),每次方向改变时都会运行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>

Class 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数据已更改。

答案 1 :(得分:0)

为什么不使用两个合适的适配器并简单地在不同视图之间传递相关信息,而不是尝试将交替视图绑定到一个BaseAdapter。

这样的事情:

mListView.setPosition(mGallery.getFirstVisiblePosition());

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

答案 2 :(得分:0)

这是一种非常有趣的方法。预片段,我会说你已经为你完成了工作,因为代码自然要求变体XML布局文件具有相同类型的具有相同ID的控件。但是,正如您所知,使用片段,您所要做的就是说出它的位置并将其链接到一个类。我没有理由为什么你不能有不同的布局(例如,肖像和风景)实例化不同的片段类。

答案 3 :(得分:0)

注意

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

我会在完成我的申请后立即更新此答案。

<强>解决方案

您必须覆盖onRestoreInstanceStateListView中的Gallery。在他们两个中你必须做适当的转换。在ListView中,您将Parceable转换为AbsListView$SavedState,将Gallery转换为AbsSpinner$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&lt; - &gt; 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!");
    }
}

答案 4 :(得分:0)

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