ViewPager--實現多個Fragment中的數據同步

qkwu 7年前發布 | 19K 次閱讀 ViewPager 數據同步 Android開發 移動開發

當前做的項目中,需要實現點擊 “通知編輯界面” 中的 :paperclip: 跳轉到 “附件選擇界面”。附件選擇界面實現的思路是 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()傳遞的數據則不受影響。

 

 

來自:http://www.jianshu.com/p/ec9ca8631ac0

 

 本文由用戶 qkwu 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!