ViewPager--实现多个Fragment中的数据同步

qkwu 5年前
   <p>当前做的项目中,需要实现点击 “通知编辑界面” 中的 :paperclip: 跳转到 “附件选择界面”。附件选择界面实现的思路是 Activity+TabLayout+ViewPager,ViewPager 中嵌套Fragment,fragment 中只用一个lv展示数据。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/af378ea991e98cc863ab899509392c26.png"></p>    <p>通知编辑界面.png</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/e2498ba9bfe74e2b040b890c3c8c5983.png"></p>    <p>选择附件.png</p>    <p>在上图中,“全部” 标签中展示本地所有的文件,“文档”中只展示本地的文档,“图片” 标签中展示本地所有的图片文档。 如果我在 “全部” 中选中一个文档,那么我需要记录它的选中状态,当我切换到 “文档” 标签时就直接选中它,这就是这里说的数据同步。</p>    <p>实现这个数据同步的话,整体思路是在 “选择附件界面” 的Activity 中定义各个标签对应的集合,然后 在Frgament 中直接获取并使用。</p>    <p>开始的时候,我是在 “附件选择界面” 的Activity中开启子线程直接获取数据填充到各个集合,这样可以直接实现数据同步,但这样有一个问题,就是从 “通知编辑界面“ 跳转到 ” 附件选择界面“ 的时候需要等待老大一会儿,用户体验不佳。</p>    <p>虽然可以在 ”通知编辑界面” 加dialog进度提示,但是总感觉这不符合正常逻辑;正常应该是进入 “上传文件界面” 后,去加载本地数据,如果数据多加载慢就给出加载提示。</p>    <p>所以,为了将加载进度挪到 “上传文件界面” 中,我将获取本地文件的操作挪到了 Fragment 中。在 Fragment 中,先获取定义在Activtiy 的各个Tab 对应的集合,然后在 onCreateView 方法中开启子线程获取本地文件数据并添加到Tab对应的集合中。为了防止数据重复,在添加到Tab 对应的集合之前,先根据集合的size是否为0 判断集合中是否有数据,如果有数据,就不再添加,否则就添加。然后用Handler 发送消息 去更新LV 的适配器。</p>    <p>如果ViewPager没有预加载的话,上面在Fragment 中获取数据并添加的方式是没错的。but ,ViewPager 最少会预加载一页,这就导致了一个问题,当我在 “全部” 的Fragment中开启线程加载本地文件数据的时候,由于VP 预加载,“文档” 的Frgament 中也在同一时刻开启了子线程去获取本地文件数据。这样就导致了在判断 集合的size 时 拿到的都是0,然后两边就分别获取到了数据并添加到集合中,导致 文档 集合中数据的重复。</p>    <p>为了解决数据重复的问题,可以想法子关闭ViewPager 的预加载,一个关闭VP预加载的简单粗暴的方式就是直接复制ViewPager 类中的代码,然后更改类名作为自定义VP,并将 private static final int DEFAULT_OFFSCREEN_PAGES = 1 的值改成0,这样就有了一个不会预加载的VP,但这样就不能与TabLayout 关联并实现同步滑动了。所以, 在不更改VP 的情况下,我们可以考虑加 同步锁,将 Tab 对应的集合锁定,当一个线程正在操作/使用该集合的时候,不让其他线程操作和使用。 相关代码如下:</p>    <ul>     <li><strong>GetLocalFilesActivity.java 选择附件界面</strong></li>    </ul>    <pre>  <code class="language-java">/**   * Created by CnPeng on 2016/12/14.   * <p>   * 获取本地文件,并用VP 分别显示   */    public class GetLocalFilesActivity extends FragmentActivity implements View.OnClickListener {      private List<String>        titles;    //标题集合      public  List<LocalFileBean> selectedList; //被选中的集合,定义在这儿直接让VP内的FM调用并赋值      public List<LocalFileBean> words           = new ArrayList<>();//本地文档集合,定义在这儿直接让VP内的FM调用并赋值      public List<LocalFileBean> pics            = new ArrayList<>();//本地图片集合,定义在这儿直接让VP内的FM调用并赋值      public List<LocalFileBean> videos          = new ArrayList<>();//本地视频集合,定义在这儿直接让VP内的FM调用并赋值      public List<LocalFileBean> audios          = new ArrayList<>();//本地音频集合,定义在这儿直接让VP内的FM调用并赋值      public List<LocalFileBean> newSelectedList = new ArrayList<>();//新被选中的集合,定义在这儿直接让VP内的FM调用并赋值        @Override      protected void onCreate(Bundle savedInstanceState) {          super.onCreate(savedInstanceState);          setContentView(R.layout.activity_get_local_file_vp);            //拿到上个activity的集合          selectedList = (List<LocalFileBean>) getIntent().getSerializableExtra("uploadFilesList");            initData();            initView();      }        private void initData() {          titles = new ArrayList<>();          titles.add("全部");          titles.add("文档");          titles.add("图片");        }        private void initView() {          //初始化标题          ThreeWightTitleLayout twtl_selectFiles = (ThreeWightTitleLayout) findViewById(R.id.twtl_selectFiles);          ThreeWightTitleLayout.initTitle(getString(R.string.uploadFiles));          ThreeWightTitleLayout.initFuncText(getString(R.string.upload));          ThreeWightTitleLayout.initFuncImage(0);     //取消右上角图片            //右上角功能按钮          LinearLayout llbtn_Upload = (LinearLayout) findViewById(R.id.func_list_img);          llbtn_Upload.setOnClickListener(this);            ViewPager vp_localFiles = (ViewPager) findViewById(R.id.vp_LocalFiles);          TabLayout tabLayout = (TabLayout) findViewById(R.id.tb_LocalFiels);            //已经从代码中设置了tabIndicatorColor属性,所以这里就不需要了          //        tabLayout.setSelectedTabIndicatorColor(getResources().getColor(R.color.eba338));            FragmentManager manager = getSupportFragmentManager();          LocalFilesVPAdapter adapter = new LocalFilesVPAdapter(titles, manager);          vp_localFiles.setAdapter(adapter);      }        @Override      public void onClick(View v) {          switch (v.getId()) {              case R.id.func_list_img:        //"上传"的点击事件                  //封装Fragment中被选中的数据,传递给上一个界面, 并关闭当前activity。先判断是否已选择数据                  //                if (selectedList.size() == 0) {     //没选中数据,给出提示                  if (newSelectedList.size() == 0) {     //没选中数据,给出提示                      ToastUtil.toastShort(getString(R.string.upload_noAttachment));                  } else {                      LogUtils.e("上传--传递数据给上一级Activity", "此时被选中的集合数据大小" + selectedList.size());                      LogUtils.e("上传--传递数据给上一级Activity", "此时被选中的new集合数据大小" + newSelectedList.size());                      Intent intent = new Intent();                        intent.putExtra("SelectedFilesList", (Serializable) newSelectedList);  //强转,传递数据                      setResult(RESULT_OK, intent);                      finishActivity();   //关闭当前页面                  }                  break;          }      }        public void finishActivity() {          finish();      }    }</code></pre>    <p>上面代码中ThreeWightTitleLayout 是自定义的顶部标题栏控件, LogUtils 是自定义的吐司工具类</p>    <ul>     <li><strong>LocalFilesFragment.java 展示数据的Fragment</strong></li>    </ul>    <pre>  <code class="language-java">/**   * Created by CnPeng on 2016/12/14.   * <p>   * 展示文件列表的Fragment   */  public class LocalFilesFragment extends Fragment {      private EmptyListViewPromptView elvpv_getLocalFiles;  //加载数据时的提示布局      private View                    view;                 //Fragment的界面布局      private UploadFilesLvAdapter    adapter;      private int                     positon;    //当前是VP 的第几个位置      private ListView                listView;   //展示内容的lv      private List<LocalFileBean>     selectedList;   //被选中的全部      private List<LocalFileBean>     newSelectedList;   //被选中的全部      private             List<LocalFileBean> filesList = new ArrayList<>();  //文件集合      public static final int                 LOCALFILE = 3; //创建适配器时使用,用来区分是否显示上传进度      public static final int                 MSG_ALL   = 0;    //发送消息,区分是那种类型的文件,0 全部      public static final int                 MSG_DOC   = 1;    //发送消息,区分是那种类型的文件,1 文档      public static final int                 MSG_IMAGE = 2;    //2 图片      ContentResolver contentResolver;        /**       * 创建Fragment对象,并传递数据       *       * @param index 当前展示的是VP的第几个页面,用来创建不同的Fragment       * @return fragment       */      public static LocalFilesFragment newInstance(int index) {          LocalFilesFragment fragment = new LocalFilesFragment();          Bundle args = new Bundle();          args.putInt("position", index);          fragment.setArguments(args);          return fragment;      }        @Override   //获取传递的数据      public void onCreate(Bundle savedInstanceState) {          super.onCreate(savedInstanceState);          //获取传递的数据,根据position判断要显示的是图片,还是全部,还是文档                positon = getArguments() != null ? getArguments().getInt("position") : 0;            contentResolver = getActivity().getContentResolver();      }        @Override   //填充布局      public View onCreateView(LayoutInflater inflater, final ViewGroup container, Bundle savedInstanceState) {          if (null == view) {     //加这个判断,是为了避免 onCreateView 重复执行                 //获取activity中定义的被选中的集合,实现Activity与Framgment之间的数据传递              selectedList = ((GetLocalFilesActivity) getActivity()).selectedList;              newSelectedList = ((GetLocalFilesActivity) getActivity()).newSelectedList;                view = inflater.inflate(R.layout.fragment_get_local_file, container, false);              listView = (ListView) view.findViewById(R.id.lv_documents);                elvpv_getLocalFiles = (EmptyListViewPromptView) view.findViewById(R.id.elvpv_getLocalFiles);              elvpv_getLocalFiles.setPromptText(getString(R.string.CommonHit_Loading));              elvpv_getLocalFiles.setVisibility(View.VISIBLE);                //条目点击事件                      listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {                  @Override                  public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {                      LocalFileBean bean = filesList.get(i);                      boolean flag = bean.isChecked();    //获取之前的状态                        int size = bean.getFileSize();   //拿到的单位是B                         if (size > 10 * 1024 * 1024) {   //不大于10                          ToastUtil.toastShort(R.string.hint_uploadFileIsLarge);                          return; //弹完吐司之后,不往集合添加                      }                        //控制数量不能超过5个                      if (!flag && selectedList.size() >= 5) {                          ToastUtil.toastShort("最多只能选择五个文件");                      } else {                          flag = !flag;   //取反                            bean.setChecked(flag);                          adapter.notifyDataSetChanged();                            //将被选中的数据存进单独的集合                          if (flag) {                              selectedList.add(bean);    //选中的存                              newSelectedList.add(bean);                          } else {                              selectedList.remove(bean);  //未选中的移除                              newSelectedList.remove(bean);  //未选中的移除                          }                          //                        LogUtils.e("被选中的数据有多少?", selectedList.size() + "");                          //                        LogUtils.e("New被选中的数据有多少?", newSelectedList.size() + "");                      }                  }              });              fillData();          }          return view;      }        private void fillData() {          // 搜索本地文件,是耗时操作,开启子线程,去后台加载数据                         new Thread(new Runnable() {              @Override              public void run() {                    List<LocalFileBean> words = ((GetLocalFilesActivity) getActivity()).words;                  List<LocalFileBean> pics = ((GetLocalFilesActivity) getActivity()).pics;                  List<LocalFileBean> videos = ((GetLocalFilesActivity) getActivity()).videos;                  List<LocalFileBean> audios = ((GetLocalFilesActivity) getActivity()).audios;                    synchronized (words) {  //同步代码块,以集合为锁对象,保证同一时间只有一个线程可以操作该集合                      if (!(words.size() > 0)) {                          //这里要用addAll(),保证words 的对象地址不变;如果用= 地址就变了,就会导致数据不能同步,每次都是新获取                          words.addAll(GetLocalFileUtils2.getAllWords(contentResolver));                      }                  }                  synchronized (pics) {                      if (!(pics.size() > 0)) {                          pics.addAll(GetLocalFileUtils2.getAllPictures(contentResolver));                      }                  }                    synchronized (videos) {                      if (!(videos.size() > 0)) {                          videos.addAll(GetLocalFileUtils2.getAllVideos(contentResolver));                      }                  }                  synchronized (audios) {                      if (!(audios.size() > 0)) {                          audios.addAll(GetLocalFileUtils2.getAllMusic(contentResolver));                      }                  }                  switch (positon) {                      case 0:                          filesList.addAll(words);                          filesList.addAll(pics);                          filesList.addAll(audios);                          filesList.addAll(videos);                          handler.sendEmptyMessage(MSG_ALL);  //全部中集合文件和文档                          LogUtils.e("123", "全部--查找完毕" + filesList.size());                          break;                      case 1: //文档页面                          //获取数据,并添加到集合,拿到数据后发送空消息通知                          filesList.addAll(words);                          handler.sendEmptyMessage(MSG_DOC);                          LogUtils.e("123", "文档--查找完毕" + filesList.size());                          break;                      case 2:                          filesList.addAll(pics);                          filesList.addAll(audios);                          filesList.addAll(videos);                          handler.sendEmptyMessage(MSG_IMAGE);                          LogUtils.e("123", "图片/视频/音频--查找完毕" + filesList.size());                          break;                  }              }          }).start();      }        //处理本地搜索结果,设置LV适配器,展现数据 ,并关闭对话框      Handler handler = new Handler() {          @Override          public void handleMessage(Message msg) {              if (filesList != null && filesList.size() > 0) {                  //有相关数据时,直接隐藏提示布局                  elvpv_getLocalFiles.setVisibility(View.INVISIBLE);                  adapter = new UploadFilesLvAdapter(filesList, LOCALFILE);                  listView.setAdapter(adapter);              } else {                  // 没有数据时,给出空布局提示                            elvpv_getLocalFiles.setPromptText(getString(R.string.CommonHit_NoData));                  elvpv_getLocalFiles.setVisibility(View.VISIBLE);              }          }      };  }</code></pre>    <p>A</p>    <p>上面代码中,核心部分是fillData() 方法。</p>    <p>B</p>    <p>EmptyListViewPromptView 是自定义的数据为空时的提示控件</p>    <p>C</p>    <p>为了方便外部在创建Fragment的时候传递数据给Fragment , 封装了一个 newInstance() 方法,该方法中将要传递的数据封装到bundle,然后setArguments()给Frgament, 最终返回一个fragment对象。</p>    <p>D</p>    <p>同步锁 锁定的是 对象,所以,想锁定哪个对象,就将对象作为同步锁对象</p>    <p>E</p>    <p>从Fragment中获取它所依附的Activity 中的数据时,这里直接使用的是getActivity(), 然后强转为所在的Activity类,然后再获取数据。</p>    <ul>     <li> <p>LocalFilesVPAdapter.java ViewPager 的适配器</p> <pre>  <code class="language-java">/**  * Created by CnPeng on 2016/12/14.  * <p>  * LocalFiles_VP 的适配器  */  public class LocalFilesVPAdapter extends FragmentPagerAdapter {    private final List<String> titles;      public LocalFilesVPAdapter(List<String> titles, FragmentManager manager) {        super(manager);        this.titles = titles;    }      @Override   //获取具体的view,这里使用的是Fragxment    public Fragment getItem(int position) {        return LocalFilesFragment.newInstance(position);    }      @Override    public int getCount() {        return titles.size();    }      @Override   //返回VP的标题    public CharSequence getPageTitle(int position) {        return titles.get(position);    }  }</code></pre> </li>    </ul>    <p>补充:</p>    <p>在向Frgament中传递数据的时候,官方推荐使用setArguments() 。但是,为什么推荐使用setArguments() 而不是直接通过 有参构造传递数据呢 ?因为当Fragment所依赖的Actvitiy重新创建的时候(比如横竖屏切换的时候),会先销毁已有的Fragment对象,然后调用Fragment的空参去重新创建Frament 对象这就导致了数据的丢失。而使用setArguments()传递的数据则不受影响。</p>    <p> </p>    <p> </p>    <p>来自:http://www.jianshu.com/p/ec9ca8631ac0</p>    <p> </p>