示例效果: 
主要思路分為兩個方面: 1. ViewPager 實現左右拖動切換 Fragment,FragmentTabHost 點擊底部按鈕切換 Fragment; 2. 將 ViewPager 的翻頁動作與 FragmentTabHost 的頁面切換進行關聯,反過來又將 FragmentTabHost 的點擊切換與 ViewPager 的翻頁進行關聯,這樣就能實現點擊和拖拽翻頁的同步了;
后面會有詳細代碼,demo鏈接:https://github.com/hry712/Android_ViewPager_FragmentTabHost_Demo.git
一、主界面layout
布局如下:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context="com.geekschoole.waimai.controllers.MainActivity"> <android.support.v4.view.ViewPager android:id="@+id/pager_fragments" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1"> </android.support.v4.view.ViewPager> <FrameLayout android:id="@+id/frame_tabContent" android:visibility="gone" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1"></FrameLayout> <android.support.v4.app.FragmentTabHost android:id="@+id/tabhost_pages" android:layout_width="match_parent" android:layout_height="wrap_content"> </android.support.v4.app.FragmentTabHost> </LinearLayout>需要為 ViewPager 自定義 adapter ,用以裝填要切換的 Fragment ,并根據拖動事件的觸發返回相應的 Fragment,自定義 adapter 繼承自FragmentPagerAdapter
(谷歌官方推薦使用提供的標準FragmentPagerAdapter
或FragmentStatePagerAdapter
,后者適合于標簽頁較多的情況),代碼如下:
public class MyFragmentAdapter extends FragmentPagerAdapter { // 在 MainActivity 中會初始化各個 Fragment 構成列表一并傳入到 adapter 中處理 PRivate List<Fragment> fragments; public MyFragmentAdapter(FragmentManager fm, List<Fragment> fragments) { super(fm); this.fragments = fragments; } // 官方文檔中介紹只需重載 getItem 和 getCount 即可使用 // 該方法返回一個與特定位置相關的 Fragment @Override public Fragment getItem(int position) { return fragments.get(position); } // 返回可用視圖的總數 @Override public int getCount() { return fragments.size(); } }MainActivity.class
中創建 ViewPager 的代碼如下:
pager = (ViewPager) findViewById(R.id.pager_fragments); // fragmentList 是包括了已初始化并要進行切換的 Fragment 列表 pager.setAdapter(new MyFragmentAdapter(getSupportFragmentManager(), fragmentList));為了響應拖動切換事件,MainActivity
需實現 ViewPager.OnPageChangeListener
接口,其下3個接口方法實現如下,注意到在onPageSelected()
中, ViewPager 的切換引起 FragmentTabHost 同步切換也是在此實現:
// 當滾動狀態發生改變時調用,特別適合在用戶開始拖動時觸發 @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { } // 當前頁滾動時會調用此方法 @Override public void onPageScrollStateChanged(int state) { } // 當新頁面變為選中狀態時會調用此方法 @Override public void onPageSelected(int position) { TabWidget widget = fragmentTabHost.getTabWidget(); // 在查找取得焦點的view時,descendant focusability定義了view group與其后代的聯系 int oldFocusability = widget.getDescendantFocusability(); widget.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); // 這里關聯到 fragmentTabHost 一起切換 fragmentTabHost.setCurrentTab(position); widget.setDescendantFocusability(oldFocusability); }“綁定”也許并不準確,實際上是在一個接口方法onTabChanged()
(接口為 FragmentTabHost.OnTabChangeListener
)中令 ViewPager 的當前頁與 FragmentTabHost 切換時同步改變,實現“綁定”作用。
在 MainActivity 中初始化 FragmentTabHost:
// 下面都是在準備 FragmentTabHost 的創建 fragmentTabHost = (FragmentTabHost); findViewById(R.id.tabhost_pages); // 要求 MainActivity 實現 FragmentTabHost.OnTabChangeListener 接口的 onTabChanged 方法 fragmentTabHost.setOnTabChangedListener(this); // 官方文檔中要求在從視圖層完成inflate后,必須調用setup方法繼續完成FragmentTabHost初始化 fragmentTabHost.setup(this, getSupportFragmentManager(), R.id.frame_tabContent); // 至此,FragmentTabHost 已經創建完成,下面要向其裝填底部欄的幾個按鈕 // fragmentArr[] 中保存了自定義的幾個 Fragment 類用作 Tab 頁 int count = fragmentsArr.length; for (int i = 0; i < count; i++) { // 使用了自定義的 getTabItemViewById() 方法 // 這里的 TabSpec 設置了 label 和 icon,icon的生成封裝在了 getTabItemViewById() 中 TabHost.TabSpec tabSpec = fragmentTabHost.newTabSpec(TabNameArr[i]).setIndicator(getTabItemViewById(i)); // 將底部按鈕與 fragment 關聯起來 fragmentTabHost.addTab(tabSpec, fragmentsArr[i], null); fragmentTabHost.getTabWidget().getChildAt(i).setBackgroundResource(R.drawable.bottom_switcher); }實現 FragmentTabHost.OnTabChangeListener
接口的 onTabChanged()
方法如下:
@Override public void onTabChanged(String s) { // 通過這個方法令 fragmentTabHost 觸發 ViewPager 同步變化 pager.setCurrentItem(fragmentTabHost.getCurrentTab()); }一個Tab頁包含一個 Tab 指示器,content,用于跟蹤它的 tag,TabSpec 就是用來選擇這些內容。
Tab指示器有兩種形式: 1. 設置一個 label 2. 設置一個 label 和 icon
Tab 內容有3種: 1. View的id 2. 創建視圖內容的 TabHost.TabContentFactory 3. 啟動 Activity 的 Intent
自定義的 getTabItemViewById()
方法如下:
// 解析單個Tab頁按鈕的XML布局,將icon和label的具體內容依次裝填進去生成一個新的view供 TabSpec 使用 private View getTabItemViewById(int index) { // bottom_tab_switcher.xml 是每個標簽頁下對應的圖標和文字組合的小布局 View view = layoutInflater.inflate(R.layout.bottom_tab_switcher, null); ImageView imageViewTabIcon = (ImageView) view.findViewById(R.id.imgvw_bottom_tabIcon); // index 變量布局用于索引預制在數組變量中的tab命名字符串和圖片點擊動作響應xml文件 imageViewTabIcon.setImageResource(ImageViewArr[index]); TextView textViewTabName = (TextView) view.findViewById(R.id.tv_bottom_tabText); textViewTabName.setText(TabNameArr[index]); return view; }MainActivity.class
實現 onTabChanged()
接口方法如下:
// 當標簽頁切換時會調用此方法 @Override public void onTabChanged(String s) { // viewpager 的 setCurrentItem 方法用于設置當前選中頁面 pager.setCurrentItem(fragmentTabHost.getCurrentTab()); }四、主要代碼如下
MainActivity.class
完整代碼如下:
package com.geekschoole.waimai.controllers;import android.support.v4.app.Fragment;import android.support.v4.app.FragmentTabHost;import android.support.v4.view.ViewPager;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import android.widget.ImageView;import android.widget.TabHost;import android.widget.TabWidget;import android.widget.TextView;import com.geekschoole.waimai.views.IndexFragment;import com.geekschoole.waimai.views.OrderFragment;import com.geekschoole.waimai.R;import com.geekschoole.waimai.views.UserFragment;import java.util.ArrayList;import java.util.List;public class MainActivity extends AppCompatActivity implements ViewPager.OnPageChangeListener, FragmentTabHost.OnTabChangeListener{ private FragmentTabHost fragmentTabHost; private LayoutInflater layoutInflater; private Class fragmentsArr[] = {IndexFragment.class, OrderFragment.class, UserFragment.class}; private int ImageViewArr[] = {R.drawable.bottom_index_tab_selector, R.drawable.bottom_order_tab_selector, R.drawable.bottom_user_tab_selector}; private String TabNameArr[] = {"Index", "Order", "User"}; private List<Fragment> fragmentList = new ArrayList<>(); private ViewPager pager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 控件初始化,并將 ViewPager 與 FragmentTabHost 進行綁定 initView(); // 創建3個Fragment,通過Adapter添加到 ViewPager 中作為Tab頁 initTabs(); } private void initView() { layoutInflater = LayoutInflater.from(this); pager = (ViewPager) findViewById(R.id.pager_fragments); pager.addOnPageChangeListener(this); fragmentTabHost = (FragmentTabHost) findViewById(R.id.tabhost_pages); fragmentTabHost.setOnTabChangedListener(this); fragmentTabHost.setup(this, getSupportFragmentManager(), R.id.frame_tabContent); int count = fragmentsArr.length; for (int i = 0; i < count; i++) { TabHost.TabSpec tabSpec = fragmentTabHost.newTabSpec(TabNameArr[i]).setIndicator(getTabItemViewById(i)); fragmentTabHost.addTab(tabSpec, fragmentsArr[i], null); fragmentTabHost.getTabWidget().getChildAt(i).setBackgroundResource(R.drawable.bottom_switcher); } } private View getTabItemViewById(int index) { View view = layoutInflater.inflate(R.layout.bottom_tab_switcher, null); ImageView imageViewTabIcon = (ImageView) view.findViewById(R.id.imgvw_bottom_tabIcon); imageViewTabIcon.setImageResource(ImageViewArr[index]); TextView textViewTabName = (TextView) view.findViewById(R.id.tv_bottom_tabText); textViewTabName.setText(TabNameArr[index]); return view; } private void initTabs() { // 這里的添加順序對 tab 頁的先后順序有影響 fragmentList.add(new IndexFragment()); fragmentList.add(new OrderFragment()); fragmentList.add(new UserFragment()); pager.setAdapter(new MyFragmentAdapter(getSupportFragmentManager(), fragmentList)); fragmentTabHost.getTabWidget().setDividerDrawable(null); } @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { } @Override public void onPageScrollStateChanged(int state) { } @Override public void onPageSelected(int position) { TabWidget widget = fragmentTabHost.getTabWidget(); int oldFocusability = widget.getDescendantFocusability(); widget.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); fragmentTabHost.setCurrentTab(position); widget.setDescendantFocusability(oldFocusability); } @Override public void onTabChanged(String s) { pager.setCurrentItem(fragmentTabHost.getCurrentTab()); }}底部單個Tab圖標和label的組合布局 bottom_tab_switcher.xml
如下(位于 res/layout/ 中),就是一個icon和一個label簡單的縱向排列:
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:gravity="center"> <ImageView android:id="@+id/imgvw_bottom_tabIcon" android:layout_width="30dp" android:layout_height="30dp" android:focusable="false" android:padding="3dp"/> <TextView android:id="@+id/tv_bottom_tabText" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Index" android:textSize="10sp" /></LinearLayout>底部每個 icon 點擊時的圖片切換配置bottom_index_tab_selector.xml
示例如下,需事先為每個圖標準備一套在選中和未選中時的 icon 圖片資源放置于 res/drawable/drawable-XXXdpi 下,在 getTabItemViewById()
方法的 imageViewTabIcon.setImageResource(ImageViewArr[index]);
中會為每個 icon 綁定此配置(配置文件中已經引用了圖片資源):
<?xml version="1.0" encoding="utf-8"?><selector xmlns:android="http://schemas.android.com/apk/res/android"> <!--Non focused states--> <item android:state_focused="false" android:state_selected="false" android:state_pressed="false" android:drawable="@drawable/index_unselected" /> <!--Focused states--> <item android:state_focused="true" android:state_selected="false" android:state_pressed="false" android:drawable="@drawable/index_selected" /> <!--Pressed--> <item android:state_selected="true" android:state_pressed="true" android:drawable="@drawable/index_selected" /> <item android:drawable="@drawable/index_selected" /></selector>至于切換的幾個 Tab ,里面使用的 Fragment 可自行創建空白或關聯有xml 的 fragment 再根據需要進行各種界面繪制,示例中只包含了一個 <TextView>
用來顯示文字。