【问题标题】:Save/Restore fragments state android保存/恢复片段状态android
【发布时间】:2013-04-04 15:00:47
【问题描述】:

我正在尝试创建一个使用 Jack Wharton 的 ViewPager 库 (here) 的应用程序,每个页面仅使用图像(类似于 Prixing (here) 主屏幕)。 一切正常,除了片段中的 saveInstance。

在 Jack Wharton 的示例中,他将文本存储在名为 mContent 的 String 变量中,并在 onCreate 中将其恢复,但在我的情况下,我该怎么办?保存/恢复位图?!任何客观的答案将不胜感激!

我对这个使用应用程序的 Fragment 很陌生,因为我看到的每个示例都只提供基础知识,而更复杂的示例则变得越来越困难。

PS。如果知道有用的话,我正在使用 CirclePageIndicator。

这里的当前片段代码:

    public final class SpecialOfferFragment extends Fragment {

    private int imageResourceId;

    public static SpecialOfferFragment newInstance(int i) {

        //probably I'll use a bitmap(downloaded) as parameter instead of using static images
        SpecialOfferFragment fragment = new SpecialOfferFragment();

        fragment.imageResourceId = i;

        return fragment;
    }


    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // if ((savedInstanceState != null) { }

    }

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

        ImageView image = new ImageView(getActivity());
        image.setImageResource(imageResourceId);
        image.setScaleType(ScaleType.FIT_XY);

        LinearLayout layout = new LinearLayout(getActivity());
        layout.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT,
                LayoutParams.FILL_PARENT));

        layout.setGravity(Gravity.CENTER);
        layout.addView(image);

        return layout;
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        //smth to save here..
    }
}

在应用程序的当前状态下,我得到以下异常:

04-12 07:28:17.760: E/AndroidRuntime(31903): FATAL EXCEPTION: main 
04-12 07:28:17.760: E/AndroidRuntime(31903): java.lang.NullPointerException
04-12 07:28:17.760: E/AndroidRuntime(31903): at android.support.v4.app.FragmentManagerImpl.saveFragmentBasicState(FragmentManager.java:1576)
04-12 07:28:17.760: E/AndroidRuntime(31903):    at android.support.v4.app.FragmentManagerImpl.saveAllState(FragmentManager.java:1617)
04-12 07:28:17.760: E/AndroidRuntime(31903):    at android.support.v4.app.FragmentActivity.onSaveInstanceState(FragmentActivity.java:481)
04-12 07:28:17.760: E/AndroidRuntime(31903):    at android.app.Activity.performSaveInstanceState(Activity.java:1113)
04-12 07:28:17.760: E/AndroidRuntime(31903):    at android.app.Instrumentation.callActivityOnSaveInstanceState(Instrumentation.java:1188)
04-12 07:28:17.760: E/AndroidRuntime(31903):    at android.app.ActivityThread.performStopActivityInner(ActivityThread.java:2804)
04-12 07:28:17.760: E/AndroidRuntime(31903):    at android.app.ActivityThread.handleStopActivity(ActivityThread.java:2862)
04-12 07:28:17.760: E/AndroidRuntime(31903):    at android.app.ActivityThread.access$900(ActivityThread.java:127)
04-12 07:28:17.760: E/AndroidRuntime(31903):    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1175)
04-12 07:28:17.760: E/AndroidRuntime(31903):    at android.os.Handler.dispatchMessage(Handler.java:99)
04-12 07:28:17.760: E/AndroidRuntime(31903):    at android.os.Looper.loop(Looper.java:137)
04-12 07:28:17.760: E/AndroidRuntime(31903):    at android.app.ActivityThread.main(ActivityThread.java:4511)
04-12 07:28:17.760: E/AndroidRuntime(31903):    at java.lang.reflect.Method.invokeNative(Native Method)
04-12 07:28:17.760: E/AndroidRuntime(31903):    at java.lang.reflect.Method.invoke(Method.java:511)
04-12 07:28:17.760: E/AndroidRuntime(31903):    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:980)
04-12 07:28:17.760: E/AndroidRuntime(31903):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:747)
04-12 07:28:17.760: E/AndroidRuntime(31903):    at dalvik.system.NativeStart.main(Native Method)

每当我暂停/停止包含此片段的活动时都会发生这种情况,例如按下主页按钮。

**

用代码编辑:

**

 public class MainMenu extends FragmentActivity {

    //private List<CategoriesHolder> categoriesList = new ArrayList<CategoriesHolder>();
    //private CategoriesAdapter categoriesAdapter = null;
    //private GridView gv_mainmenu_categories;

    SpecialOfferFragmentAdapter mAdapter;
    ViewPager mPager;
    PageIndicator mIndicator;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_mainmenu);

        linkUI();
        setAction();

        // just for testing
        String[] imagesUrls = null;

        mAdapter = new SpecialOfferFragmentAdapter(getSupportFragmentManager(),
                this, imagesUrls);

        //categoriesAdapter = new CategoriesAdapter(this, categoriesList);

        // used for Categories GridView
        //gv_mainmenu_categories.setAdapter(categoriesAdapter);

        // used for ViewPager
        mPager.setAdapter(mAdapter);
        mIndicator.setViewPager(mPager);

    }

    private void linkUI() {
        mPager = (ViewPager) findViewById(R.id.vp_mainmenu_special_offers);
        mIndicator = (CirclePageIndicator) findViewById(R.id.indicator);

        //gv_mainmenu_categories = (GridView) findViewById(R.id.gv_mainmenu_categories);

    }

    private void setAction() {

    }
} 

    public class SpecialOfferFragmentAdapter extends FragmentPagerAdapter implements
        IconPagerAdapter {

    private int[] mCarouselImages = new int[] { R.drawable.pic1,
            R.drawable.pic2, R.drawable.pic3, R.drawable.pic4, R.drawable.pic5

    };

    private String[] imagesUrls;
    private Context context;

    public static final int[] ICONS = new int[] {
            R.drawable.perm_group_calendar, R.drawable.perm_group_camera,
            R.drawable.perm_group_device_alarms, R.drawable.perm_group_location };

    private int mCount = mCarouselImages.length;

    public SpecialOfferFragmentAdapter(FragmentManager fm, Context context,
            String[] imagesUrls) {
        super(fm);
        this.context = context;
        this.imagesUrls = imagesUrls;
    }

    @Override
    public Fragment getItem(int position) {

        return SpecialOfferFragment.newInstance(mCarouselImages[position]);
    }

    @Override
    public int getCount() {
        return mCount;
    }

    @Override
    public int getIconResId(int index) {
        return ICONS[index % ICONS.length];
    }

    public void setCount(int count) {
        if (count > 0 && count <= 10) {
            mCount = count;
            notifyDataSetChanged();
        }
    }
}

【问题讨论】:

  • 你想通过保存实例实现什么?
  • 我编辑了我的问题,所以它也有例外。当我阅读 Android 文档时,我了解到我必须每次都保存片段的状态并在 OnCreate 中恢复它(也许我错过了,如果是这样,我该如何解决该异常?)。
  • 请提供有关代码中抛出异常的信息。
  • 我不能这样做,因为当我双击日志 (Eclipse) 时,异常日志不会向我发送应用程序代码中的任何位置。 - 也许它与 android.support.v4 jar 有关,所以它不能将我重定向到“问题代码”。我用完整的代码重新编辑问题。
  • 所以基本上只是为了清楚,你想要实现的事情是在 ViewPager 中加载图像,如果用户例如转到横向/纵向(或转到另一个活动/片段)模式停留在当前图像,而不是从头开始,对吗?

标签: android android-viewpager fragment savestate


【解决方案1】:

为了实现你想要的东西,你应该使用整数值将当前位置存储在你的ViewPager 中,然后使用这个值将正确的位置设置为你的ViewPager

例如,在您的 FragmentActivity 中执行类似的操作:

@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
  super.onSaveInstanceState(savedInstanceState);
  savedInstanceState.putInt("mMyCurrentPosition", mPager.getPosition());
}

@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
  super.onRestoreInstanceState(savedInstanceState);
  mMyCurrentPosition = savedInstanceState.getInt("mMyCurrentPosition");
  // where mMyCurrentPosition should be a public value in your activity.
}

这将保存您的 ViewPager 的最后位置。然后在您的onResume() 中,例如,您可以检查

if(mMyCurrentPosition != 0){
    mPager.setCurrentItem(mMyCurrentPosition);
}

我认为这应该可行,将图像/位图保存在包中不是一个好习惯(而且我认为你做不到)。

【讨论】:

  • 好的,这可能会解决在 PageViewer 暂停/停止后获取最后状态的问题,但是每当我暂停/停止我的应用程序时,我仍然会遇到这个烦人的错误(登录我的问题)。
  • 暂停/停止我的应用是什么意思?
  • 我的意思是打开另一个活动/应用程序,切换方向,任何可能触发此活动暂停或停止操作的事情。它不断触发该异常。
  • 尝试使用FragmentStatePagerAdapter 而不是FragmentPagerAdapter
  • 哦,天哪,终于成功了。非常感谢您花时间在这个问题上!最后一个请求,你可以忍受我多一点时间xD。我尝试了保存最后一个位置的答案,但 ViewPager 似乎没有 getPosition() 方法。有任何关于获得该职位的提示吗?
【解决方案2】:

好的,首先我想澄清一些可能解决您对已保存实例状态的困惑的东西。 Savedinstancestate 只能处理原语(int、double、float、byte..)和 String 对象。这应该是您所需要的,但在某些情况下,您还想保存其他对象的实例。为此,您需要一些其他模式来完成这项工作,例如一个工厂模式,其中包含所有位图的数组。 例如,创建一个类 ImageFactory 来保存位图的静态 ArrayList。 每当您旋转设备时,都会再次调用 oncreateview 和 onactivitycreated 方法,这是检查您是否要从工厂检索信息的时刻。例如

if(savedInstanceState != null) { //我们有一个 savedinstancestate 所以 可能有保存的图像 ImageFactory.getBMPs(); }

希望您可以利用这些信息做点什么。祝你好运

【讨论】:

  • 感谢您的回答。所以,你的意思是为这些位图创建一个类持有者,在我的 Fragment 中的 onCreate 中保存和恢复它们,会解决这个问题吗?
【解决方案3】:

这在你的主要活动中对我来说很容易和简单

 @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        onCreate(savedInstanceState);
    }

在你的清单中

 <activity
            android:configChanges="orientation|screenSize"/>

【讨论】:

    【解决方案4】:

    当使用 SharedPreference 更改选项卡时,我创建了我自己的保存片段数据的方法:

    方法如下:

      public void saveData() {
    
        Log.d("msg", "Save Instance");
        SharedPreferences.Editor outState = getActivity().getSharedPreferences("order", Context.MODE_APPEND).edit();
    
        orderDate = orderDateEditText.getText().toString();
        orderBy = orderByEditText.getText().toString();
        orderMobileNo = orderMobileNoEditText.getText().toString();
        orderTransport = orderTransportEditText.getText().toString();
        orderInvoicePer = orderInvoicePerEditText.getText().toString(); 
        orderCharge = orderChargeEditText.getText().toString();
        orderGoodsDispatch = orderGoodsDispatchEditText.getText().toString();
        orderOthers = orderOthersEditText.getText().toString();
        orderIsDirect = orderIsDirectCheckBox.isChecked() ? 1 : 0;
    
        outState.putBoolean("saved", true);
    
        outState.putString("orderDate", orderDate);
        outState.putString("orderBy", orderBy);
        outState.putString("orderMobileNo", orderMobileNo);
        outState.putString("orderTransport", orderTransport);
        outState.putString("orderInvoicePer", orderInvoicePer);
        outState.putString("orderCharge", orderCharge);
        outState.putString("orderGoodsDispatch", orderGoodsDispatch);
        outState.putString("orderOthers", orderOthers);
        outState.putInt("orderIsDirect", orderIsDirect);
    
        outState.commit();
    }
    

    调用这个方法时

        @Override
        public void onStop() {
        // TODO Auto-generated method stub
            super.onStop();
    
            saveData();
    }
    

    EditText 替换保存的数据:

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        super.onActivityCreated(savedInstanceState);
    
        /***** PUT DATA IN EDITTEXT is ITS AVAILABLE IN BUNDLE *****/
        SharedPreferences orderData = getActivity().getSharedPreferences("order", Context.MODE_APPEND);
        Log.d("msg", ""+orderData.getBoolean("saved", false));
    
        if(orderData.getBoolean("saved", false))
        {
            orderDate = orderData.getString("orderDate", "");
            orderBy = orderData.getString("orderBy", "");
            orderMobileNo = orderData.getString("orderMobileNo", "");
            orderTransport = orderData.getString("orderTransport", "");
            orderInvoicePer = orderData.getString("orderInvoicePer", ""); 
            orderCharge = orderData.getString("orderCharge", "");
            orderGoodsDispatch = orderData.getString("orderGoodsDispatch", "");
            orderOthers = orderData.getString("orderOthers", "");
            orderIsDirect = orderData.getInt("orderIsDirect", 0);
    
            orderDateEditText.setText(orderDate);
            orderByEditText.setText(orderBy);
            orderMobileNoEditText.setText(orderMobileNo);
            orderTransportEditText.setText(orderTransport);
            orderInvoicePerEditText.setText(orderInvoicePer);
            orderChargeEditText.setText(orderCharge);
            orderGoodsDispatchEditText.setText(orderGoodsDispatch);
            orderOthersEditText.setText(orderOthers);
            if(orderIsDirect == 0)
                orderIsDirectCheckBox.setChecked(false);
            else
                orderIsDirectCheckBox.setChecked(true);
        }
    }
    

    希望这段代码对你有帮助...

    谢谢...

    【讨论】:

      【解决方案5】:

      我不太确定这个问题是否仍然困扰着您,因为它已经过去了几个月。但我想分享一下我是如何处理这个问题的。 以下是源代码:

      int FLAG = 0;
      private View rootView;
      private LinearLayout parentView;
      
      /**
       * The fragment argument representing the section number for this fragment.
       */
      private static final String ARG_SECTION_NUMBER = "section_number";
      
      /**
       * Returns a new instance of this fragment for the given section number.
       */
      public static Fragment2 newInstance(Bundle bundle) {
          Fragment2 fragment = new Fragment2();
          Bundle args = bundle;
          fragment.setArguments(args);
          return fragment;
      }
      
      public Fragment2() {
      
      }
      
      @Override
      public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
          super.onCreateView(inflater, container, savedInstanceState);
          Log.e("onCreateView","onCreateView");
          if(FLAG!=12321){
              rootView = inflater.inflate(R.layout.fragment_create_new_album, container, false);
              changeFLAG(12321);
          }       
          parentView=new LinearLayout(getActivity());
          parentView.addView(rootView);
      
          return parentView;
      }
      
      /* (non-Javadoc)
       * @see android.support.v4.app.Fragment#onDestroy()
       */
      @Override
      public void onDestroy() {
          // TODO Auto-generated method stub
          super.onDestroy();
          Log.e("onDestroy","onDestroy");
      }
      
      /* (non-Javadoc)
       * @see android.support.v4.app.Fragment#onStart()
       */
      @Override
      public void onStart() {
          // TODO Auto-generated method stub
          super.onStart();
          Log.e("onstart","onstart");
      }
      
      /* (non-Javadoc)
       * @see android.support.v4.app.Fragment#onStop()
       */
      @Override
      public void onStop() {
          // TODO Auto-generated method stub
          super.onStop();
          if(false){
              Bundle savedInstance=getArguments();
              LinearLayout viewParent;
      
              viewParent= (LinearLayout) rootView.getParent();
              viewParent.removeView(rootView);
      
          }
          parentView.removeView(rootView);
      
          Log.e("onStop","onstop");
      }
      @Override
      public void onPause() {
          super.onPause();
          Log.e("onpause","onpause");
      }
      
      @Override
      public void onResume() {
          super.onResume();
          Log.e("onResume","onResume");
      }
      

      这里是 MainActivity:

      /**
       * Fragment managing the behaviors, interactions and presentation of the
       * navigation drawer.
       */
      private NavigationDrawerFragment mNavigationDrawerFragment;
      
      /**
       * Used to store the last screen title. For use in
       * {@link #restoreActionBar()}.
       */
      
      public static boolean fragment2InstanceExists=false;
      public static Fragment2 fragment2=null;
      
      @Override
      protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
          setContentView(R.layout.activity_main);
      
          mNavigationDrawerFragment = (NavigationDrawerFragment) getSupportFragmentManager()
                  .findFragmentById(R.id.navigation_drawer);
          mTitle = getTitle();
      
          // Set up the drawer.
          mNavigationDrawerFragment.setUp(R.id.navigation_drawer,
                  (DrawerLayout) findViewById(R.id.drawer_layout));
      }
      
      @Override
      public void onNavigationDrawerItemSelected(int position) {
          // update the main content by replacing fragments
          FragmentManager fragmentManager = getSupportFragmentManager();
          FragmentTransaction fragmentTransaction=fragmentManager.beginTransaction();
          switch(position){
          case 0:
              fragmentTransaction.addToBackStack(null);
              fragmentTransaction.replace(R.id.container, Fragment1.newInstance(position+1)).commit();
              break;
          case 1:
      
              Bundle bundle=new Bundle();
              bundle.putInt("source_of_create",CommonMethods.CREATE_FROM_ACTIVITY);
      
              if(!fragment2InstanceExists){
                  fragment2=Fragment2.newInstance(bundle);
                  fragment2InstanceExists=true;
              }
              fragmentTransaction.addToBackStack(null);
              fragmentTransaction.replace(R.id.container, fragment2).commit();
      
              break;
          case 2:
              fragmentTransaction.addToBackStack(null);
              fragmentTransaction.replace(R.id.container, FolderExplorerFragment.newInstance(position+1)).commit();
              break;
          default: 
              break;
          }
      }
      

      parentView 是关键点。 通常,在onCreateView时,我们只使用return rootView。但是现在,我将rootView添加到parentView,然后返回parentView。为防止出现“The specified child has a parent. You must call removeView() on the ...”错误,我们需要调用“parentView.removeView(rootView)”,否则我提供的方法没有用。 我也想分享我是如何找到它的。首先,我设置了一个布尔值来指示实例是否存在。当实例存在时,rootView 不会再次膨胀。但是后来,logcat给了child已经有了parent的东西,所以我决定用另一个parent作为中间的Parent View。这就是它的工作原理。 希望对你有帮助。

      【讨论】:

        【解决方案6】:

        让我写下我处理这个问题的方法。我在 Activity 中保留了一个片段对象变量,以供以后交互。在 onCreate() 方法中,我检查是否存在 savedInstanceState(由屏幕旋转或其他原因引起)。

        public class MainActivity extends AppCompatActivity {
        PlaceAddFragment mFragment;
        
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
        
            if(savedInstanceState == null) {
                mFragment = new PlaceAddFragment();
                mFragment.setProfileImages(incomingPics);
        
                getSupportFragmentManager()
                        .beginTransaction()
                        .replace(R.id.form_fragment_holder, mFragment, PlaceAddFragment.TAG)
                        .commit();
            }else{
                mFragment = (PlaceAddFragment) getSupportFragmentManager().findFragmentByTag(PlaceAddFragment.TAG);
            }
        }
        

        在我的片段类中,我重写了 onSaveInstanceState 方法,并在其中添加了 setRetainInstance(true),如下所示:

        public class PlaceAddFragment extends Fragment {
        public static final String TAG = ".PlaceAddFragment";
        private ProfileImageAdapter mAdapter;
        private List<ProfileImage> mProfileImages;
        private Context mContext;
        
        public PlaceAddFragment() {
            // Required empty public constructor
        }
        
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {
        
            mContext = getContext();
            View view = inflater.inflate(R.layout.fragment_place_add, container, false);
        
            RecyclerView mRecyclerView = (RecyclerView) view.findViewById(R.id.hw_pictures_holder);
            mRecyclerView.setLayoutManager(new LinearLayoutManager(mContext, LinearLayoutManager.HORIZONTAL, false));
            mAdapter = new ProfileImageAdapter(mContext, mProfileImages);
            mRecyclerView.setAdapter(mAdapter);
        
            return view;
        }
        
        @Override
        public void onSaveInstanceState(final Bundle outState) {
            super.onSaveInstanceState(outState);
            setRetainInstance(true);
        }
        
        @Override
        public void onDestroy() {
            super.onDestroy();
            mAdapter = null;
            mProfileImages = null;
        }
        
        public void setProfileImages(List<ProfileImage> incomingPics) {
            this.mProfileImages = incomingPics;
        }
        
        public List<ProfileImage> getProfileImages() {
            return this.mProfileImages;
        }
        }
        

        如果您希望此代码正常工作,您应该实现 ProfileImageAdapter mAdapter、ProfileImage 对象和所有这些的 xml 布局。

        【讨论】:

          猜你喜欢
          • 2014-04-25
          • 2015-10-06
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-01-11
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多