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

当前做的项目中,需要实现点击 “通知编辑界面” 中的 📎 跳转到 “附件选择界面”。附件选择界面实现的思路是 Activity+TabLayout+ViewPager,ViewPager 中嵌套Fragment,fragment 中只用一个lv展示数据。

通知编辑界面.png
选择附件.png

在上图中,“全部” 标签中展示本地所有的文件,“文档”中只展示本地的文档,“图片” 标签中展示本地所有的图片文档。 如果我在 “全部” 中选中一个文档,那么我需要记录它的选中状态,当我切换到 “文档” 标签时就直接选中它,这就是这里说的数据同步。

实现这个数据同步的话,整体思路是在 “选择附件界面” 的Activity 中定义各个标签对应的集合,然后 在Frgament 中直接获取并使用。

开始的时候,我是在 “附件选择界面” 的Activity中开启子线程直接获取数据填充到各个集合,这样可以直接实现数据同步,但这样有一个问题,就是从 “通知编辑界面“ 跳转到 ” 附件选择界面“ 的时候需要等待老大一会儿,用户体验不佳。

虽然可以在 ”通知编辑界面” 加dialog进度提示,但是总感觉这不符合正常逻辑;正常应该是进入 “上传文件界面” 后,去加载本地数据,如果数据多加载慢就给出加载提示。

所以,为了将加载进度挪到 “上传文件界面” 中,我将获取本地文件的操作挪到了 Fragment 中。在 Fragment 中,先获取定义在Activtiy 的各个Tab 对应的集合,然后在 onCreateView 方法中开启子线程获取本地文件数据并添加到Tab对应的集合中。为了防止数据重复,在添加到Tab 对应的集合之前,先根据集合的size是否为0 判断集合中是否有数据,如果有数据,就不再添加,否则就添加。然后用Handler 发送消息 去更新LV 的适配器。

如果ViewPager没有预加载的话,上面在Fragment 中获取数据并添加的方式是没错的。but ,ViewPager 最少会预加载一页,这就导致了一个问题,当我在 “全部” 的Fragment中开启线程加载本地文件数据的时候,由于VP 预加载,“文档” 的Frgament 中也在同一时刻开启了子线程去获取本地文件数据。这样就导致了在判断 集合的size 时 拿到的都是0,然后两边就分别获取到了数据并添加到集合中,导致 文档 集合中数据的重复。

为了解决数据重复的问题,可以想法子关闭ViewPager 的预加载,一个关闭VP预加载的简单粗暴的方式就是直接复制ViewPager 类中的代码,然后更改类名作为自定义VP,并将 private static final int DEFAULT_OFFSCREEN_PAGES = 1 的值改成0,这样就有了一个不会预加载的VP,但这样就不能与TabLayout 关联并实现同步滑动了。所以,**在不更改VP 的情况下,我们可以考虑加 同步锁,将 Tab 对应的集合锁定,当一个线程正在操作/使用该集合的时候,不让其他线程操作和使用。 **相关代码如下:

  • GetLocalFilesActivity.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();
    }

}

上面代码中ThreeWightTitleLayout 是自定义的顶部标题栏控件, LogUtils 是自定义的吐司工具类

  • LocalFilesFragment.java 展示数据的Fragment
/**
 * 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);
            }
        }
    };
}

A
上面代码中,核心部分是fillData() 方法。
B
EmptyListViewPromptView 是自定义的数据为空时的提示控件
C
为了方便外部在创建Fragment的时候传递数据给Fragment , 封装了一个 newInstance() 方法,该方法中将要传递的数据封装到bundle,然后setArguments()给Frgament, 最终返回一个fragment对象。
D
同步锁 锁定的是 对象,所以,想锁定哪个对象,就将对象作为同步锁对象
E
从Fragment中获取它所依附的Activity 中的数据时,这里直接使用的是getActivity(), 然后强转为所在的Activity类,然后再获取数据。

  • LocalFilesVPAdapter.java ViewPager 的适配器
/**
 * 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);
    }
}

补充:
在向Frgament中传递数据的时候,官方推荐使用setArguments() 。但是,为什么推荐使用setArguments() 而不是直接通过 有参构造传递数据呢 ?因为当Fragment所依赖的Actvitiy重新创建的时候(比如横竖屏切换的时候),会先销毁已有的Fragment对象,然后调用Fragment的空参去重新创建Frament 对象这就导致了数据的丢失。而使用setArguments()传递的数据则不受影响。
详细原因可以参考:https://yq.aliyun.com/articles/12368

关于TabLayout的基本使用,可以参考我的另一篇文章:http://www.jianshu.com/p/c1aa1056176b

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,761评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,953评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,998评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,248评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,130评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,145评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,550评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,236评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,510评论 1 291
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,601评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,376评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,247评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,613评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,911评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,191评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,532评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,739评论 2 335

推荐阅读更多精彩内容