現在很多APP,如微信、QQ、微博等等,它們的主頁面都無一例外的選擇使用底部Tab導航, 通過這種方式,可以很好的把頁面層級分化,很好的提高用戶體驗。相信,很多Android開發者,都使用到過這種經典的設計,可是您你能保證您的設計真的沒問題麼?
爲啥我會有這個疑問呢? 因爲我日前就遇到了這麼一個情況,發現我做的APP導航頁有問題。 具體可以參考這篇博客:【Android】保存Fragment切換狀態 , 首先說明的是,我的項目是從之前就沿用下來的框架,頁面底部tab的實現,就是採用前面博客提到的方式, 可是在測試的時候,竟然發現,使用這種方式來實現,經常會發生tab重疊情況: 比如,此刻選中的事“首頁”tab,可是內容確實“活動”tab,尤其是在你的app在二級頁面發生崩潰返回到一級頁面時,這種情況經常發生! 當然,這篇博客的評論裏面,也提到了這個問題,所以,最後大家建議大家採用的是;"推薦直接使用ViewPager,通過自定義ViewPager禁用掉左右滑動和自動銷燬即可"
在我接觸的APP中,我覺得新浪微博的設計當然是最經典的,爲啥呢?就因爲它多了一個功能,“底部tab的雙擊,來實現列表滾動到最上方並刷新博客列表”,要知道,這樣的設計,可以極大提高用戶體驗的(避免了用戶手動滾動到最上方,然後下拉刷新...),接下來,將帶着大家學習如何去實現吧。
先看效果圖(尤其是日誌):
1. 直接定義tab頁面,一個ViewPager,四個RadioButton:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.lnyp.vf.ContainerViewPager
android:id="@+id/viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="60dp" />
<RadioGroup
android:id="@+id/radiogroup"
android:layout_width="fill_parent"
android:layout_height="60dp"
android:background="#ececec"
android:layout_alignParentBottom="true"
android:orientation="horizontal">
<RadioButton
android:id="@+id/radio_main"
style="@style/navigation_style"
android:checked="true"
android:drawableTop="@drawable/selector_main_bottom_tab_first"
android:paddingLeft="0dp"
android:text="首頁" />
<RadioButton
android:id="@+id/radio_projects"
style="@style/navigation_style"
android:checked="false"
android:drawableTop="@drawable/selector_main_bottom_tab_second"
android:paddingLeft="0dp"
android:text="活動" />
<RadioButton
android:id="@+id/radio_studys"
style="@style/navigation_style"
android:checked="false"
android:drawableTop="@drawable/selector_main_bottom_tab_third"
android:paddingLeft="0dp"
android:text="社區" />
<RadioButton
android:id="@+id/radio_user_center"
style="@style/navigation_style"
android:checked="false"
android:drawableTop="@drawable/selector_main_bottom_tab_forth"
android:paddingLeft="0dp"
android:text="我的" />
</RadioGroup>
</RelativeLayout>
2. 自定義PagerAdapter,爲ViewPager添加布局(Fragment),要求可以實現Fragment切換,狀態的保存:
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.view.PagerAdapter;
import android.view.View;
import android.view.ViewGroup;
import java.util.List;
/**
* 爲ViewPager添加布局(Fragment),綁定和處理fragments和viewpager之間的邏輯關係
* 可保持Fragment切換狀態
*/
public class FragmentViewPagerAdapter extends PagerAdapter implements MyViewPager.OnPageChangeListener {
private List<Fragment> fragments; // 每個Fragment對應一個Page
private FragmentManager fragmentManager;
private ContainerViewPager viewPager; // viewPager對象
private int currentPageIndex = 0; // 當前page索引(切換之前)
private OnExtraPageChangeListener onExtraPageChangeListener; // ViewPager切換頁面時的額外功能添加接口
public FragmentViewPagerAdapter(FragmentManager fragmentManager, ContainerViewPager viewPager, List<Fragment> fragments) {
this.fragments = fragments;
this.fragmentManager = fragmentManager;
this.viewPager = viewPager;
this.viewPager.setAdapter(this);
this.viewPager.setOnPageChangeListener(this);
}
@Override
public int getCount() {
return fragments.size();
}
@Override
public boolean isViewFromObject(View view, Object o) {
return view == o;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView(fragments.get(position).getView()); // 移出viewpager兩邊之外的page佈局
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
Fragment fragment = fragments.get(position);
if (!fragment.isAdded()) { // 如果fragment還沒有added
FragmentTransaction ft = fragmentManager.beginTransaction();
ft.add(fragment, fragment.getClass().getSimpleName());
ft.commit();
/**
* 在用FragmentTransaction.commit()方法提交FragmentTransaction對象後
* 會在進程的主線程中,用異步的方式來執行。
* 如果想要立即執行這個等待中的操作,就要調用這個方法(只能在主線程中調用)。
* 要注意的是,所有的回調和相關的行爲都會在這個調用中被執行完成,因此要仔細確認這個方法的調用位置。
*/
fragmentManager.executePendingTransactions();
}
if (fragment.getView().getParent() == null) {
container.addView(fragment.getView()); // 爲viewpager增加布局
}
return fragment.getView();
}
/**
* 當前page索引(切換之前)
*
* @return
*/
public int getCurrentPageIndex() {
return currentPageIndex;
}
public OnExtraPageChangeListener getOnExtraPageChangeListener() {
return onExtraPageChangeListener;
}
/**
* 設置頁面切換額外功能監聽器
*
* @param onExtraPageChangeListener
*/
public void setOnExtraPageChangeListener(OnExtraPageChangeListener onExtraPageChangeListener) {
this.onExtraPageChangeListener = onExtraPageChangeListener;
}
@Override
public void onPageScrolled(int i, float v, int i2) {
if (null != onExtraPageChangeListener) { // 如果設置了額外功能接口
onExtraPageChangeListener.onExtraPageScrolled(i, v, i2);
}
}
@Override
public void onPageSelected(int i) {
fragments.get(currentPageIndex).onPause(); // 調用切換前Fargment的onPause()
if (fragments.get(i).isAdded()) {
fragments.get(i).onResume(); // 調用切換後Fargment的onResume()
}
currentPageIndex = i;
if (null != onExtraPageChangeListener) { // 如果設置了額外功能接口
onExtraPageChangeListener.onExtraPageSelected(i);
}
}
@Override
public void onPageScrollStateChanged(int i) {
if (null != onExtraPageChangeListener) { // 如果設置了額外功能接口
onExtraPageChangeListener.onExtraPageScrollStateChanged(i);
}
}
/**
* page切換額外功能接口
*/
public static class OnExtraPageChangeListener {
public void onExtraPageScrolled(int i, float v, int i2) {
}
public void onExtraPageSelected(int i) {
}
public void onExtraPageScrollStateChanged(int i) {
}
}
}
注: 上述代碼中,有個ContainerViewPager,該ContainerViewPager是繼承ViewPager,主要是爲了去除ViewPager左右滑動功能,大家可以再源碼裏直接看到。
4. 實現MainActivity.java,爲ViewPager設置PageAdapter, 並且,在MainActivity中實現雙擊tab功能:
import android.os.Bundle;
import android.os.SystemClock;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.view.View;
import android.widget.RadioButton;
import java.util.ArrayList;
import java.util.List;
import butterknife.Bind;
import butterknife.ButterKnife;
import butterknife.OnClick;
public class MainActivity extends FragmentActivity {
public static final int TAB_HOME = 0;
public static final int TAB_PROJECTS = 1;
public static final int TAB_STUDYS = 2;
public static final int TAB_USER_CENTER = 3;
@Bind(R.id.viewpager)
public ContainerViewPager viewPager;
@Bind(R.id.radio_main)
public RadioButton radioMain;
@Bind(R.id.radio_projects)
public RadioButton radioProjects;
@Bind(R.id.radio_studys)
public RadioButton radioStudys;
@Bind(R.id.radio_user_center)
public RadioButton radioUserCenter;
FragmentMain fragmentMain;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
initView();
addPageChangeListener();
}
private void initView() {
List<Fragment> fragments = new ArrayList<Fragment>();
fragmentMain = new FragmentMain();
FragmentHuodong fragmentHuodong = new FragmentHuodong();
FragmentShequ fragmentShequ = new FragmentShequ();
FragmentMy fragmentMy = new FragmentMy();
fragments.add(fragmentMain);
fragments.add(fragmentHuodong);
fragments.add(fragmentShequ);
fragments.add(fragmentMy);
this.viewPager.setOffscreenPageLimit(0);
FragmentViewPagerAdapter adapter = new FragmentViewPagerAdapter(this.getSupportFragmentManager(), viewPager, fragments);
}
private void addPageChangeListener() {
viewPager.setOnPageChangeListener(new MyViewPager.OnPageChangeListener() {
@Override
public void onPageSelected(int id) {
switch (id) {
case TAB_HOME:
radioMain.setChecked(true);
break;
case TAB_PROJECTS:
radioProjects.setChecked(true);
break;
case TAB_STUDYS:
radioStudys.setChecked(true);
break;
case TAB_USER_CENTER:
radioUserCenter.setChecked(true);
break;
}
}
@Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
}
@Override
public void onPageScrollStateChanged(int arg0) {
}
});
}
@OnClick({R.id.radio_main, R.id.radio_projects, R.id.radio_studys, R.id.radio_user_center})
public void onClick(View v) {
switch (v.getId()) {
case R.id.radio_main:
viewPager.setCurrentItem(TAB_HOME, false);
doubleClick(v);
break;
case R.id.radio_projects:
viewPager.setCurrentItem(TAB_PROJECTS, false);
break;
case R.id.radio_studys:
viewPager.setCurrentItem(TAB_STUDYS, false);
break;
case R.id.radio_user_center:
viewPager.setCurrentItem(TAB_USER_CENTER, false);
break;
}
}
long firstClickTime = 0;
long secondClickTime = 0;
public void doubleClick(View view) {
if (firstClickTime > 0) {
secondClickTime = SystemClock.uptimeMillis();
if (secondClickTime - firstClickTime < 500) {
fragmentMain.ScrollToTop();
}
firstClickTime = 0;
return;
}
firstClickTime = SystemClock.uptimeMillis();
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(500);
firstClickTime = 0;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
注:在處首頁Tab的點擊事件時,除了要設置viewPager.setCurrentItem(TAB_HOME, false);外,還需要設置doubleClick(v);它主要是處理了雙擊事件。
通過以上四個步驟,已經可以實現tab導航,雙擊tab調用Fragment中方法了。接下來,讓我們看下日誌:仔細看下ViewPager+Fragment的生命週期:(我設置ViewPager取消了預加載功能)
1. 第一次進入到主頁面:加載FragmentMain,執行生命週期方法
2. 分別點擊其他的Tab:
3. 之後再點擊FragmentMain,我們發現,並未在執行任何生命週期的方法;
4. 點擊FragmentMain頁面中的Button,進入新的Activity:
我們發現,剛剛啓動的幾個Fragment(首頁、活動、社區),都執行了onPause和onStop方法;
5. 返回到上一級頁面,也就是首頁:
剛剛停止的幾個Fragment(首頁、活動、社區),執行了onStart和onResume方法。
6. 接着,測試下首頁tab的雙擊事件:
會調用我們在FragmentMain中定義ScrollToTop方法,在該方法中,我們可以處理一些相應的邏輯。
7. 退出APP,看下:
看到這裏,不知道大家是否明白瞭如何定義使用Tab了,如果有疑問,可以再多看看源碼,也歡迎一起討論。
github源碼地址:https://github.com/zuiwuyuan/ViewpagerFragmentTab
如此這般,就OK啦!歡迎指正!
如有疑問,歡迎進QQ羣:487786925( Android研發村 )