首页 > 学院 > 开发设计 > 正文

安卓自学——ViewPager与FragmentTabHost实现拖动翻页

2019-11-06 09:37:20
字体:
来源:转载
供稿:网友

使用ViewPager 和 FragmentTabHost 实现滑动标签页翻动

示例效果: 实现翻页效果

主要思路分为两个方面: 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 实现拖动切换 Fragment 并与 FragmentTabHost 进行关联

需要为 ViewPager 自定义 adapter ,用以装填要切换的 Fragment ,并根据拖动事件的触发返回相应的 Fragment,自定义 adapter 继承自FragmentPagerAdapter(谷歌官方推荐使用提供的标准FragmentPagerAdapterFragmentStatePagerAdapter,后者适合于标签页较多的情况),代码如下:

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); }

三、FragmentTabHost 点击切换 Fragment 并与 ViewPager 关联

“绑定”也许并不准确,实际上是在一个接口方法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> 用来显示文字。


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表