ViewPager--實現多個Fragment中的數據同步
當前做的項目中,需要實現點擊 “通知編輯界面” 中的 :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