android - 如何通过方向更改保存自定义 ListFragment 状态

标签 android android-listfragment savestate

我正在更彻底地希望这个问题实际上会更容易理解。

Activity 目的 :允许用户从图库中选择图像;在 ListFragment 中显示图像的缩略图以及用户提供的图像;当用户完成保存每个图像的 uri 和标题,以及用户给这个图像集合的名称。

问题 :当设备旋转时,FragmentList 会丢失用户已经选择的所有图像和标题,即列表的所有行都丢失了。

尝试解决问题 :

  • 实现了 RetainedFragment 以在设备旋转时保存 List 集合。以前我没有这样做并想“啊,适配器在创建时被提供一个空白列表集合。我将保存列表的状态,然后当调用 Activity 的 onCreate 时,我可以将保留的列表提供给适配器构造函数,它会工作的。”但它没有。
  • 然后我想,“当然不行,你还没有通知适配器有变化!”所以我把 adapter.notifyDataSetChanged()在 onCreate.这没有用。
  • 然后我移动了adapter.notifyDataSetChanged()onStart认为我可能需要在 Activity 生命周期的后期通知适配器。没用。

  • 注意:我在同一个应用程序中有另一个 Activity ,它使用相同的自定义 ListViewFragment,并且 ListFragment 的状态随着设备方向的变化而保留。该 Activity 有两个主要区别: fragment 被硬编码到 .xml 中(我认为这不会产生影响,除了可能 Android 的 .xml fragment 的 native 保存与以编程方式添加的 fragment 不同);并且该 Activity 使用 Loader 和 LoaderManager 并从我构建的 Provider 获取其数据(它从我的 SQLite 数据库收集数据)。看看这两个 Activity 之间的差异是什么让我想到“你没有以某种方式适本地处理数据馈送适配器”并启发我在设备旋转时使用 RetainedFragment 来保存 List 集合。

    ...这促使我考虑弄清楚如何去做,正如 Android 在他们的 Loader 页面上关于 LoaderManager 所说的那样:

    "An abstract class associated with an Activity or Fragment for managing one or more Loader instances. This helps an application manage longer-running operations in conjunction with the Activity or Fragment lifecycle; the most common use of this is with a CursorLoader, however applications are free to write their own loaders for loading other types of data."



    正是“加载其他类型的数据”部分让我思考“我可以使用 LoaderManager 来加载列表数据吗?我对此感到害羞的两个原因:1)我已经拥有的,至少在概念上应该可以工作;2 ) 我现在所做的根本不是“长期运行的操作”,我不认为。

    研究 :
  • StackOverflow Fool proof way to handle Fragment on orientation change
  • 保存 fragment 的状态。
  • 我认为我使用的 RetainedFragment 保存了需要保存的内容。(?)
  • Once for all, how to correctly save instance state of Fragments in back stack?
  • 保存 backstack fragment 。
  • 未在下面粘贴的代码中显示,但我的 Activity 动态创建了三个其他 fragment ,如果 savedInstanceState !=null,我使用以下内容并且这些 fragment 的状态无需在 onSaveInstanceState() 中做任何工作即可保存(这就是为什么感觉我的问题不在于在 onSaveInstanceState 中做某事的部分原因,因为 Android 处理保存我的其他 fragment 状态,所以它不应该也使用 ListFragment 来做吗?似乎应该这样做)。
    if(savedInstanceState.containsKey(AddActivity_Frag1.F1_TAG)){
        frag1 = (AddActivity_Frag1)getFragmentManager().getFragment(savedInstanceState, AddActivity_Frag1.F1_TAG);
    }
    
  • Understanding Fragment's setRetainInstance(boolean)
  • 围绕我的查询的许多 StackOverflow 问题似乎主要是关于如何通过方向更改保存 ListFragment 的滚动位置,但我不需要这样做(尽管我确实阅读了它们以寻找可能有帮助的提示)。
  • Android Fragments
  • Android Loaders
  • Android Caching Bitmaps (RetainFragment stuff)

  • Activity - 删除了许多希望不相关的东西:
    public class AddActivity extends Activity{
    
        // data collection
        List<ImageBean> beanList;
    
        // adapter
        AddCollectionAdapter adapter;
    
        // ListViewFragment tag
        private static final String LVF_TAG = "list fragment tag";
    
        // fragment handles
        ListViewFragment listFrag;
    
        // Handles images; LruCache for bitmapes
        ImageHandler imageHandler;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_add2);
    
            // Create ImageHandler that holds LruCache
            imageHandler = new ImageHandler(this, getFragmentManager());        
    
            // Obtain retained List<ImageBean> or create new List<ImageBean>.
            RetainedFragment retainFragment = RetainedFragment.findOrCreateRetainFragment(getFragmentManager());
    
            beanList = retainFragment.list;
    
            if(beanList == null){
    
                beanList = new ArrayList<ImageBean>();
    
                retainFragment.list = beanList;             
            }           
    
            // create fragments
            if(savedInstanceState == null){
    
                listFrag = new ListViewFragment();  
    
                FragmentTransaction ft = getFragmentManager().beginTransaction();
                ft.add(R.id.add_fragFrame, listFrag, LVF_TAG);
    
                ft.commit();            
    
            }else{
                listFrag = (ListViewFragment)getFragmentManager().findFragmentByTag(LVF_TAG);               
            }
    
            // create adapter
            adapter = new AddCollectionAdapter(this, beanList);
    
            // set list fragment adapter
            listFrag.setListAdapter(adapter);
        }       
    
        @Override
        protected void onStart() {
    
            // TESTING: If device orientation has changed List<ImageBean> was saved
            // with a RetainedFragment. Seed the adapter with the retained
            // List.
            adapter.notifyDataSetChanged();
            super.onStart();
        }
    
        @Override
        protected void onSaveInstanceState(Bundle outState) {
    
            // Android automatically saves visible fragments here. (?)
    
            super.onSaveInstanceState(outState);
        }   
    
        /*
         * ImageBean.
         */
        public static class ImageBean{
            private String collectionName;  // Title of image collection
            private String imageUri;        // Image URI as a string
            private String imageTitle;      // Title given to image
    
            public ImageBean(String name, String uri, String title){
                collectionName = name;
                imageUri = uri;
                imageTitle = title;
            }
    
            public String getCollectionName() {
                return collectionName;
            }
    
            public String getImageUri() {
                return imageUri;
            }
    
            public String getImageTitle() {
                return imageTitle;
            }       
        }
    
        /*
         * Called when user is finished selecting images.
         * 
         * Performs a bulk insert to the Provider.
         */
        private void saveToDatabase() {
            int arraySize = beanList.size();
            final ContentValues[] valuesArray = new ContentValues[arraySize];
    
            ContentValues values;
            String imageuri;
            String title;
            int counter = 0;
    
    
            for(ImageBean image : beanList){
    
                imageuri = image.getImageUri();
                title = image.getImageTitle();
    
                values = new ContentValues();   
    
                values.put(CollectionsTable.COL_NAME, nameOfCollection);
                values.put(CollectionsTable.COL_IMAGEURI, imageuri);
                values.put(CollectionsTable.COL_TITLE, title);
                values.put(CollectionsTable.COL_SEQ, counter +1);
    
                valuesArray[counter] = values;
                counter++;
            }
    
            AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
    
                @Override
                protected Void doInBackground(Void... arg0) {
                    getContentResolver().bulkInsert(CollectionsContentProvider.COLLECTIONS_URI, valuesArray);   
                    return null;
                }
    
                @Override
                protected void onPostExecute(Void result) {
    
                    // End this activity.
                    finish();   
                }           
            };
    
            task.execute();                 
        }   
    
        public ImageHandler getImageHandler(){
            return imageHandler;
        }
    }
    
    class RetainedFragment extends Fragment{
    
        private static final String TAG = "RetainedFragment";
    
        // data to retain
        public List<AddActivity.ImageBean> list;
    
        public static RetainedFragment findOrCreateRetainFragment(FragmentManager fm){
    
            RetainedFragment fragment = (RetainedFragment)fm.findFragmentByTag(TAG);
    
            if(fragment == null){
    
                fragment = new RetainedFragment();
                fm.beginTransaction().add(fragment, TAG);
            }
    
            return fragment;
        }
    
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            setRetainInstance(true);
        }   
    }
    

    列表 fragment :
    public class ListViewFragment extends ListFragment {
    
    ListFragListener listener;
    
    public interface ListFragListener{
        public void listFragListener(Cursor cursor);
    }       
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
    
        // Retain this fragment across configuration change
        setRetainInstance(true);
    
        super.onCreate(savedInstanceState);
    }
    
    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
    
        // Set listener
        if(activity instanceof ListFragListener){
    
            listener = (ListFragListener)activity;      
    
        }else{
    
            //Instantiating activity does not implement ListFragListener.
        }
    }
    
    @Override
    public void onListItemClick(ListView listView, View v, int position, long id) {
    
        // no action necessary
    }   
    }   
    

    适配器:
    public class AddCollectionAdapter extends BaseAdapter {
    
    // data collection
    List<ImageBean> beanList;
    
    // layout inflator
    private LayoutInflater inflater;
    
    // context
    Context context;
    
    public AddCollectionAdapter(Context context, List<ImageBean> beanList){
        this.context = context;
        this.beanList = beanList;
        inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }
    
    @Override
    public int getCount() {
        return beanList.size();
    }
    
    @Override
    public Object getItem(int position) {
        return beanList.get(position);
    }
    
    @Override
    public long getItemId(int arg0) {
        // collection not from database nor is going directly to database; this is useless.
        return 0;
    }
    
    // holder pattern
    private class ViewHolder{
        ImageView imageView;
        TextView titleView;
    }
    
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
    
        ViewHolder holder;
        View xmlTemplate = convertView;
    
        if(xmlTemplate == null){
    
            //inflate xml
            xmlTemplate = inflater.inflate(R.layout.frag_listview_row, null);
    
            // initilaize ViewHolder
            holder = new ViewHolder();
    
            // get views that are inside the xml
            holder.imageView = (ImageView)xmlTemplate.findViewById(R.id.add_lvrow_image);
            holder.titleView = (TextView)xmlTemplate.findViewById(R.id.add_lvrow_title);
    
            // set tag
            xmlTemplate.setTag(holder);
    
        }else{
    
            holder = (ViewHolder)xmlTemplate.getTag();
        }
    
        // Get image details from List<ImageBean>
        ImageBean bean = beanList.get(position);        
        String imageUri = bean.getImageUri();
        String title = bean.getImageTitle();
    
        // Set Holder ImageView bitmap; Use parent activity's ImageHandler to load image into Holder's ImageView.
        ((AddActivity)context).getImageHandler().loadBitmap(imageUri, holder.imageView, Constants.LISTVIEW_XML_WIDTH, Constants.LISTVIEW_XML_HEIGHT);       
    
        // Set Holder's TextView.
        holder.titleView.setText(title);
    
        // return view
        return xmlTemplate;
    }
    }
    

    最佳答案

    解决了。在将日志语句放在重要位置后,我发现 RetainedFragment 的列表始终为空。在 RetainedFragment 中挠头后注意到了这一点:

    fm.beginTransaction().add(fragment, TAG);
    

    我错过了 commit() !

    在我添加状态现在正在保留配置更改之后。

    我在试验和磨难中发现的与保存 ListFragment 状态相关的更多信息:

    如果您通过以下方式添加 fragment :
        if(savedInstanceState == null){
    
            listFrag = new ListViewFragment();  
    
            // programmatically add fragment to ViewGroup
            FragmentTransaction ft = getFragmentManager().beginTransaction();
            ft.add(R.id.add_fragFrame, listFrag, LVF_TAG);
    
        }
    

    然后这些中的任何一个都可以在 else 中使用。 :
    1) This one works because Android takes care of saving the Fragment:
    
       listFrag = (ListViewFragment)getFragmentManager().findFragmentByTag(LVF_TAG);
    
    2) This one works because the fragment was specifically saved into bundle in
       onSaveInstanceState:
    
       listFrag = (ListViewFragment)getFragmentManager().getFragment(savedInstanceState, LVF_TAG);
    

    要使 2 号起作用,这发生在 onSaveInstanceState() 中:
    @Override
    protected void onSaveInstanceState(Bundle outState) {       
        super.onSaveInstanceState(outState);
    
        getFragmentManager().putFragment(outState, LVF_TAG, listFrag);
    }
    

    关于android - 如何通过方向更改保存自定义 ListFragment 状态,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25730163/

    相关文章:

    Android 自定义字体未正确映射

    java - 如何添加 ListFragment 使用到布局

    Android 错误 - FragmentTransaction 类型中的方法 replace(int, Fragment) 不适用于参数 (int, ListFragment)

    Android:后退按钮中的 onSaveInstanceState

    android - 如何在 Kotlin 中保存延迟发帖的状态

    java - 如何在旋转时保持应用程序的相同状态?

    android - 如何在样式中使用 textColorPrimary 作为背景色?

    java - android 的新手-将 xml 文档编译成二进制格式有什么好处

    android - 如何在android中使用列表 fragment 显示自定义 ListView

    java - 如何使用attach()和detach()而不是add()和remove()来保存 fragment View