安卓自学——ViewPager与FragmentTabHost实现拖动翻页
来源:互联网 发布:m1协同软件 编辑:程序博客网 时间:2024/06/01 23:46
使用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
(谷歌官方推荐使用提供的标准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); }
三、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>
用来显示文字。
补充
在效果图中可以看到label颜色并没有随着Tab的切换,而切换到与icon一致的颜色,label的颜色切换与icon类似,先在 /res/drawable/ 目录下创建一个相应的 XX_selector.xml ,然后在 <TextView>
控件中设置属性 android:textColor 属性值为 @drawable/XX_selector。label 的 selector.xml 配置示例如下:
<selector xmlns:android="http://schemas.android.com/apk/res/android"> <!-- Non focused states --> <!-- 在 res/values/colors.xml 中label颜色在未选中时为黑色 --> <item android:state_focused="false" android:state_selected="false" android:state_pressed="false" android:color="@color/unselectedText" /> <item android:state_focused="false" android:state_selected="true" android:state_pressed="false" android:color="@color/selectedText" /> <!-- Focused states --> <!-- 在 res/values/colors.xml 中label颜色在选中时为红色 --> <item android:state_focused="true" android:state_selected="false" android:state_pressed="false" android:color="@color/selectedText" /> <item android:state_focused="true" android:state_selected="true" android:state_pressed="false" android:color="@color/selectedText" /> <!-- Pressed --> <item android:state_selected="true" android:state_pressed="true" android:color="@color/selectedText" /> <item android:state_pressed="true" android:color="@color/selectedText" /></selector>
如果是在阿里图标库中找现成的图标,可事先选择图标的16进制颜色值并下载,将这个颜色值保存到 colors.xml 中供这里调用
- 安卓自学——ViewPager与FragmentTabHost实现拖动翻页
- 安卓FragmentTabHost+Fragment+ViewPager+TabLayout仿今日头条
- Fragment与ViewPager实现页面滑动翻页
- [一个星期自学安卓]使用Gallery实现立体翻页效果
- 安卓开发复习笔记——Fragment+FragmentTabHost组件(实现新浪微
- 安卓开发复习笔记——Fragment+FragmentTabHost组件(实现新浪微
- 安卓简单实现RadioButton与ViewPager
- 安卓日记——ViewPager与RadioGroup关联
- Viewpager和Fragement实现翻页
- 仿viewPager-实现滑动翻页
- Android底部导航栏——FragmentTabHost+ViewPager+Fragment
- [安卓自定义控件]基于安卓事件分发机制,结合ViewPager与ImageScaleView的可监听左右滑动翻页并支持保存图片的图片浏览页
- ViewPager+FragmentTabHost
- 使用FragmentTabHost+Fragment+viewpager 实现滑动分页
- FragmentTabHost+Fragment+ViewPager实现内外层嵌套
- 使用FragmentTabHost+Fragment+viewpager 实现滑动分页
- FragmentTabHost+Fragment+ViewPager实现内外层嵌套
- FragmentTabHost+viewPager实现底部导航栏
- PAT 1015
- MyEclipse:详细使用教程
- Maven 的搭建
- lua排序
- 软件工程
- 安卓自学——ViewPager与FragmentTabHost实现拖动翻页
- @weakify, @strongify
- android Contentprovider配置记录
- android的Drawable详解
- PAT (Advanced Level) Practise 1004. Counting Leaves (30)
- super和this关键字
- 用LuaBridge为Lua绑定C/C++对象
- 函数调用约定
- AOSP (一):获取源码