Android之ActionBar、Tabs、Fragment、ViewPager實現標簽頁切換並緩存頁麵
感覺 Android 到處都是坑,每個地方都要把人折騰半天。
今天來簡單說說 Android之ActionBar、Tabs、Fragment、ViewPager 實現標簽頁切換並緩存頁麵
關於他們的介紹就不多說了,網上到處都是,隻說關鍵的部分:
我在開發的時候遇到幾個疑難問題,花費大量時間處理,總結如下:
1. 關於 Fragment 內部邏輯處理該寫在哪個事件回調部分?
2. ViewPager 頁麵切換動畫卡頓,讓我頭疼了很久。
3. ViewPager 中如何保存 Fragment 當前視圖的狀態,讓 Tabs 頁麵切換後不會重新加載,這地方很坑爹
4. ActionBar 中的 tab 很多時如何滾動顯示
解答:
一、Fragment 的事件回調:
package com.ai9475.meitian.ui.fragment;
import android.app.Activity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ListView;
import com.ai9475.meitian.R;
import com.ai9475.meitian.view.DiaryList;
import com.ai9475.util.ZLog;
/**
*
* Created by ZHOUZ on 14-1-21.
*/
public class DiaryListFragment extends BaseFragment
{
private static final String TAG = "DiaryListFragment";
@Override
public void onAttach(Activity activity)
{
ZLog.i(TAG, "onAttach");
super.onAttach(activity);
}
@Override
public void onCreate(Bundle savedInstanceState)
{
ZLog.i(TAG, "onCreate");
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
ZLog.i(TAG, "onCreateView");
return inflater.inflate(R.layout.fragment_diary_list, container, false);
}
@Override
public void onActivityCreated(Bundle savedInstanceState)
{
ZLog.i(TAG, "onActivityCreated");
super.onActivityCreated(savedInstanceState);
ZLog.i(TAG, "DiaryList0");
DiaryList diaryList = new DiaryList(
getActivity().getApplicationContext(),
(ListView) getView().findViewById(R.id.diaryListCt)
);
ZLog.i(TAG, "DiaryList load0");
diaryList.load("https://m.ai9475.com/?con=meitian_app");
this.setRetainInstance(true);
}
@Override
public void onStart()
{
ZLog.i(TAG, "onStart");
super.onStart();
}
@Override
public void onResume()
{
ZLog.i(TAG, "onResume");
super.onResume();
}
@Override
public void onPause()
{
ZLog.i(TAG, "onPause");
super.onPause();
}
@Override
public void onStop()
{
ZLog.i(TAG, "onStop");
super.onStop();
}
@Override
public void onDestroyView()
{
ZLog.i(TAG, "onDestroyView");
super.onDestroyView();
}
@Override
public void onDestroy()
{
ZLog.i(TAG, "onDestroy");
super.onDestroy();
}
@Override
public void onDetach()
{
ZLog.i(TAG, "onDetach");
super.onDetach();
}
}
上麵的類中的 on 事件就是Fragment主要處理的時間回調,注意複寫父類方法時要回調執行父類同名方法,否則會出錯
主要複寫 onCreateView 方法,返回該 Fragment 所對應的視圖對象,這裏可以在返回視圖對象前進行一些簡單的配置,但千萬不要寫太耗時的處理阻塞UI主線程。
另外 onActivityCreated 方法,是當 activity 的 onCreate 事件結束時的回調,此時當前的Fragment對應的view已經並入到整個布局中,此時可以使用 getView() 方法獲取視圖對象。
其他幾個事件沒什麼太多可說,有些我也還不是太清楚,還有些動畫調用的事件。
二、切換頁麵卡頓問題
這個問題的產生主要可能是兩方麵,
1. 沒有使用 ViewPager 的緩存,每次切換都重新加載。
2. 加載 Fragment 內部有耗時耗資源的邏輯處理。
這裏主要說下第二種情況,我一開始沒處理掉 緩存問題時有一個解決辦法,
<android.support.v4.view.ViewPager
android:
android:layout_width="match_parent"
android:layout_height="match_parent"
>
</android.support.v4.view.ViewPager>
mViewPager = (ViewPager) findViewById(R.id.tabsViewPager);
mViewPager.setOnPageChangeListener(
new ViewPager.SimpleOnPageChangeListener() {
private static final String TAG = "ViewPager.SimpleOnPageChangeListener";
private ArrayList hasLoadedPages = new ArrayList<Integer>();
@Override
public void onPageSelected(int position) {
ZLog.i(TAG, "onPageSelected position:"+ position);
getSupportActionBar().setSelectedNavigationItem(position);
}
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels)
{
ZLog.i(TAG, "onPageScrolled position: "+ position +", positionOffset:"+ positionOffset +", positionOffsetPixels:"+ positionOffsetPixels);
}
@Override
public void onPageScrollStateChanged(int state) {
ZLog.i(TAG, "onPageScrollStateChanged");
int position = mViewPager.getCurrentItem();
switch (state) {
// 正在拖動
case ViewPager.SCROLL_STATE_DRAGGING :
ZLog.i(TAG, "ViewPager.SCROLL_STATE_DRAGGING position:"+ position);
break;
// 拖動釋放後正在沉降的過程
case ViewPager.SCROLL_STATE_SETTLING :
ZLog.i(TAG, "ViewPager.SCROLL_STATE_SETTLING position:"+ position);
break;
// 切換動畫全部完成結束
case ViewPager.SCROLL_STATE_IDLE :
ZLog.i(TAG, "ViewPager.SCROLL_STATE_IDLE position:"+ position);
// 已加載過則不再加載
if (hasLoadedPages.contains(position)) break;
Fragment fragment = mPager.getFragments().get(position);
runCallback(position, fragment);
hasLoadedPages.add(position);
break;
}
}
public void runCallback(int position, Fragment fragment) {
ZLog.i(TAG, "runCallback");
DiaryList diaryList;
switch (position) {
case 0 :
ZLog.i(TAG, "DiaryList0");
diaryList = new DiaryList(
getApplicationContext(),
(ListView) fragment.getView().findViewById(R.id.diaryListCt)
);
ZLog.i(TAG, "DiaryList load0");
diaryList.load("https://m.ai9475.com/?con=meitian_app");
break;
case 1:
ZLog.i(TAG, "DiaryList1");
diaryList = new DiaryList(
getApplicationContext(),
(ListView) fragment.getView().findViewById(R.id.diaryListCt)
);
ZLog.i(TAG, "DiaryList load1");
diaryList.load("https://m.ai9475.com/?con=meitian_app&act=hot");
break;
case 2:
ZLog.i(TAG, "DiaryList2");
diaryList = new DiaryList(
getApplicationContext(),
(ListView) fragment.getView().findViewById(R.id.diaryListCt)
);
ZLog.i(TAG, "DiaryList load2");
diaryList.load("https://m.ai9475.com/?con=meitian_app");
break;
}
}
}
這裏主要用到 public void onPageScrollStateChanged(int state) 頁麵滾動切換狀態變化的事件監聽
當滾動動畫完全結束 case ViewPager.SCROLL_STATE_IDLE 時再執行 Fragment 的邏輯處理,這樣動畫就會流暢了。
但經過後來的測試發現有個很簡單的解決方案,就是下麵要說到的 ViewPager 的緩存功能,其實很簡單。
三、緩存 Tabs 頁麵切換不重新加載數據
我在這地方折騰的最久,而且很多時候無從下手的感覺,網上搜索了很多文章都有說道保存 Fragment 數據和狀態,但是沒有整整提到如何來保存他的當前view狀態,不知道如何保存,當然實際上我還是沒搞懂如何僅僅緩存 Fragment 的狀態,但對於 ViewPager 緩存 Tab 對應的 Fragment 還是找到了辦法,之前花了很大功夫來自己實現,後來偶然發現他居然有個自帶的方法
// 設置緩存多少個 Tab對應的 fragment
mViewPager.setOffscreenPageLimit(6);
我測試時用了 6個 listView 加載圖片列表數據,切換動畫也沒有任何卡頓現象,非常流暢,就這麼簡單一句就搞定了。
配置該項後,ViewPager在切換時將不會清理不可見的 Fragment,不會觸發 Fragment 的任何事件,因此也就不會導致其重新加載。
四、ActionBar 中的 Tabs
這個其實不用操作,在tabs數量超過一屏後,例如我現在設置6個寬度超過了屏幕則會變成可以橫向滾動的狀態,而不需要自己實現,之前不知道在這方麵查了很多資料都無果,隻知道可以用 tabhost 可以實現,但在ActionBar 裏麵卻又用不了,自己試了下方多個tab才發現原來會自動實現,無語。

還是貼上 MainActivity.class 完整代碼:
package com.ai9475.meitian.ui;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.view.ViewPager;
import android.support.v7.app.ActionBar;
import android.view.Menu;
import android.widget.ListView;
import com.ai9475.meitian.AppManager;
import com.ai9475.meitian.R;
import com.ai9475.meitian.ui.fragment.DiaryListFragment;
import com.ai9475.meitian.ui.fragment.Test2Fragment;
import com.ai9475.meitian.ui.fragment.Test3Fragment;
import com.ai9475.meitian.view.DiaryList;
import com.ai9475.util.ZLog;
import java.util.ArrayList;
public class MainActivity extends BaseActivity
{
private static final String TAG = "MainActivity";
private MyTabsPagerAdapter mPager;
private ViewPager mViewPager;
@Override
protected void onCreate(Bundle savedInstanceState)
{
ZLog.i(TAG, "start");
// 執行父級初始化方法
super.onCreate(savedInstanceState);
//requestWindowFeature(Window.FEATURE_NO_TITLE);
ZLog.i(TAG, "setContentView");
setContentView(R.layout.activity_main);
// 滑動頁麵視圖配置
ZLog.i(TAG, "MyTabsPagerAdapter start");
mPager = new MyTabsPagerAdapter(getSupportFragmentManager());
mPager.getFragments().add(new DiaryListFragment());
mPager.getFragments().add(new Test2Fragment());
mPager.getFragments().add(new Test3Fragment());
mPager.getFragments().add(new Test3Fragment());
mPager.getFragments().add(new Test3Fragment());
mPager.getFragments().add(new Test3Fragment());
// 滑動分頁容器
mViewPager = (ViewPager) findViewById(R.id.tabsViewPager);
// 設置緩存多少個 fragment
mViewPager.setOffscreenPageLimit(6);
mViewPager.setAdapter(mPager);
// 頁麵滑動事件
mViewPager.setOnPageChangeListener(
new ViewPager.SimpleOnPageChangeListener() {
private static final String TAG = "ViewPager.SimpleOnPageChangeListener";
private ArrayList hasLoadedPages = new ArrayList<Integer>();
@Override
public void onPageSelected(int position) {
ZLog.i(TAG, "onPageSelected position:"+ position);
getSupportActionBar().setSelectedNavigationItem(position);
}
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels)
{
ZLog.i(TAG, "onPageScrolled position: "+ position +", positionOffset:"+ positionOffset +", positionOffsetPixels:"+ positionOffsetPixels);
}
@Override
public void onPageScrollStateChanged(int state) {
ZLog.i(TAG, "onPageScrollStateChanged");
int position = mViewPager.getCurrentItem();
switch (state) {
// 正在拖動
case ViewPager.SCROLL_STATE_DRAGGING :
ZLog.i(TAG, "ViewPager.SCROLL_STATE_DRAGGING position:"+ position);
break;
// 拖動釋放後正在沉降的過程
case ViewPager.SCROLL_STATE_SETTLING :
ZLog.i(TAG, "ViewPager.SCROLL_STATE_SETTLING position:"+ position);
break;
// 切換動畫全部完成結束
case ViewPager.SCROLL_STATE_IDLE :
ZLog.i(TAG, "ViewPager.SCROLL_STATE_IDLE position:"+ position);
/*if (hasLoadedPages.contains(position)) break;
Fragment fragment = mPager.getFragments().get(position);
runCallback(position, fragment);
hasLoadedPages.add(position);*/
break;
}
}
public void runCallback(int position, Fragment fragment) {
ZLog.i(TAG, "runCallback");
DiaryList diaryList;
switch (position) {
case 0 :
ZLog.i(TAG, "DiaryList0");
diaryList = new DiaryList(
getApplicationContext(),
(ListView) fragment.getView().findViewById(R.id.diaryListCt)
);
ZLog.i(TAG, "DiaryList load0");
diaryList.load("https://m.ai9475.com/?con=meitian_app");
break;
case 1:
ZLog.i(TAG, "DiaryList1");
diaryList = new DiaryList(
getApplicationContext(),
(ListView) fragment.getView().findViewById(R.id.diaryListCt)
);
ZLog.i(TAG, "DiaryList load1");
diaryList.load("https://m.ai9475.com/?con=meitian_app&act=hot");
break;
case 2:
ZLog.i(TAG, "DiaryList2");
diaryList = new DiaryList(
getApplicationContext(),
(ListView) fragment.getView().findViewById(R.id.diaryListCt)
);
ZLog.i(TAG, "DiaryList load2");
diaryList.load("https://m.ai9475.com/?con=meitian_app");
break;
}
}
}
);
ActionBar actionBar = getSupportActionBar();
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
// Tab 頁麵切換
MyTabListener listener = new MyTabListener();
// 默認的首頁 tab
ActionBar.Tab indexTab = actionBar.newTab()
.setText(getString(R.string.tab_index))
.setTabListener(listener);
actionBar.addTab(indexTab);
actionBar.addTab(actionBar.newTab()
.setText(getString(R.string.tab_hot))
.setTabListener(listener)
);
actionBar.addTab(actionBar.newTab()
.setText(getString(R.string.tab_tag))
.setTabListener(listener)
);
actionBar.addTab(actionBar.newTab()
.setText(getString(R.string.tab_tag))
.setTabListener(listener)
);
actionBar.addTab(actionBar.newTab()
.setText(getString(R.string.tab_tag))
.setTabListener(listener)
);
actionBar.addTab(actionBar.newTab()
.setText(getString(R.string.tab_tag))
.setTabListener(listener)
);
// 顯示首頁
indexTab.select();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
private class MyTabListener implements ActionBar.TabListener
{
@Override
public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft)
{
int position = tab.getPosition();
ZLog.i(TAG, "tab selected: "+ position);
// 數據通信
/*Bundle bundle = new Bundle();
Fragment fragment = mPager.getItem(tab.getPosition());
Toast.makeText(getApplicationContext(), "position:"+ tab.getPosition(), Toast.LENGTH_SHORT).show();
fragment.setArguments(bundle);
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.add(R.id.fragmentContainer, fragment);
fragmentTransaction.commit();*/
mViewPager.setCurrentItem(position);
}
@Override
public void onTabReselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
ZLog.i(TAG, "tab reselected: "+ tab.getPosition());
}
@Override
public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft)
{
ZLog.i(TAG, "tab unselected: "+ tab.getPosition());
}
};
public class MyTabsPagerAdapter extends FragmentPagerAdapter
{
private ArrayList<Fragment> mFragments = new ArrayList<Fragment>();
public MyTabsPagerAdapter(FragmentManager fm) {
super(fm);
}
public ArrayList<Fragment> getFragments() {
return this.mFragments;
}
@Override
public Fragment getItem(int i) {
return this.mFragments.get(i);
}
@Override
public int getCount() {
return this.mFragments.size();
}
}
}
最後更新:2017-04-03 12:55:12