文章目录
前文我们介绍了Fragment的应用之一,最简单的页面导航方式: Fragment+BottomTab实现页面导航。这种方式虽然能实现点击底部导航按钮切换页面,但不够友好。我们更期望的是左右滑动页面也能实现页面切换,同时底部按钮的选中状态也跟着切换。本文就将介绍如何通过Fragment+ViewPager+BottomTab实现左右页面滑动切换这一效果。
1. 目标效果
2. 案例教学
2.1 主界面布局
首先,依然是主界面的布局。跟前文类似,依然是上下两部分,上面是页面主体内容区域,底部是导航按钮。只不过这次主体内容区域我们使用ViewPager这个控件来盛放多个Fragment。(还不熟悉ViewPager这个控件的小伙伴可以自行学习下它的基本用法,这里不展开讲述了)
布局文件activity_fragment_view_pager_bottom.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.viewpager.widget.ViewPager
android:id="@+id/vp"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<include layout="@layout/bottom_tab_layout" />
</LinearLayout>
我们直接引用了前文代码的bottom_tab_layout,也就是底部导航按钮布局。这里看出将代码分开写的好处了吧,可以多处引入使用,非常方便。
2.2 准备Fragment
这里的Fragment我们也使用前文中的那个ExampleFragment,不再重复不必要的信息。详细介绍请看前文。
public class ExampleFragment extends Fragment {
// 改为public,以便于外界能引用到
public static final String ARG_PARAM1 = "param1";
public static final String ARG_PARAM2 = "param2";
private String mParam1;
private String mParam2;
private TextView mTvContent;
public ExampleFragment() {
}
public static ExampleFragment newInstance(String param1, String param2) {
ExampleFragment fragment = new ExampleFragment();
Bundle args = new Bundle();
args.putString(ARG_PARAM1, param1);
args.putString(ARG_PARAM2, param2);
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mParam1 = getArguments().getString(ARG_PARAM1);
mParam2 = getArguments().getString(ARG_PARAM2);
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_example, container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mTvContent = view.findViewById(R.id.tv_content);
// 如果传入的参数有值,则设置内容
if (!TextUtils.isEmpty(mParam1)) {
mTvContent.setText(mParam1);
}
}
}
2.3 主界面MainActivity
接下来是代码部分。首先还是将控件做一些基础的声明和初始化。
/**
* viewpager+fragment+bottomTab
* 普通的控件拼接成的底部导航菜单
*/
public class FragmentViewPagerBottomActivity extends AppCompatActivity implements View.OnClickListener {
private RelativeLayout mRlHome, mRlSetting, mRlMine;
private TextView mTvHome, mTvFind, mTvMine;
private ImageView mIvHome, mIvFind, mIvMine;
private ViewPager mViewPager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fragment_view_pager_bottom);
initView();
initData();
initEvent();
}
private void initView() {
mViewPager = findViewById(R.id.vp);
mRlHome = findViewById(R.id.rl_home);
mRlSetting = findViewById(R.id.rl_find);
mRlMine = findViewById(R.id.rl_mine);
mTvMine = findViewById(R.id.tv_mine);
mTvFind = findViewById(R.id.tv_find);
mTvHome = findViewById(R.id.tv_home);
mIvMine = findViewById(R.id.iv_mine);
mIvFind = findViewById(R.id.iv_find);
mIvHome = findViewById(R.id.iv_home);
}
private void initData() {
}
private void initEvent() {
// 为 底部导航按钮 设置点击事件监听
mRlHome.setOnClickListener(this);
mRlSetting.setOnClickListener(this);
mRlMine.setOnClickListener(this);
}
@Override
public void onClick(View v) {
// 底部导航按钮点击后响应
}
}
2.4 ViewPager部分
ViewPager是一种方便多个页面滑动切换的控件,页面可以是View,也可以是Fragment,常常用来做轮播图、引导页面等。像ListView、RecyclerView一样,这种集合型的控件有个共同点:数据和View分离,中间用适配器adapter连接。
View我们已经有了,就是ViewPager,接下来准备数据。这里的数据就是多个Fragment页面
// 全局变量
private List<Fragment> mFragmentList;
// 添加用来测试的Frament,这里添加三个
private void initData() {
mFragmentList = new ArrayList<>();
for (int i = 0; i < 3; i++) {
ExampleFragment fragment = ExampleFragment.newInstance("fragment" + i, "");
mFragmentList.add(fragment);
}
}
最后是关键的适配器,将数据和View适配起来。MyViewPagerAdapterForFragment.java:
public class MyViewPagerAdapterForFragment extends FragmentPagerAdapter {
private List<Fragment> mFragmentList;
public MyViewPagerAdapterForFragment(@NonNull FragmentManager fm, List<Fragment> fragmentList) {
super(fm);
mFragmentList = fragmentList;
}
@NonNull
@Override
public Fragment getItem(int position) {
return mFragmentList == null ? null : mFragmentList.get(position);
}
@Override
public int getCount() {
return mFragmentList == null ? 0 : mFragmentList.size();
}
}
这个适配器也比较简单,继承FragmentPagerAdapter,然后实现其中的两个方法,getItem、getCount:获取每一个Fragment,获取Fragment的总个数。数据源是mFragmentList,通过构造方法传进来。
看看在Activity中如何把这三者(ViewPager、mFragmentList、FragmentPagerAdapter)连接起来。
代码如下:
// 全局变量
private MyViewPagerAdapterForFragment mMyViewPagerAdapter;
private void initEvent() {
// 省略上文代码...
// 创建adapter
mMyViewPagerAdapter = new MyViewPagerAdapterForFragment(getSupportFragmentManager(), mFragmentList);
// 为ViewPager设置adapter
mViewPager.setAdapter(mMyViewPagerAdapter);
}
也简单,只需要创建出adapter,再为ViewPager设置进去就行了。创建的时候传进去了数据源mFragmentList,这样就把三者连接起来了。
到这里,运行代码就已经可以看到三个页面,并且可以滑动左右切换了。但是底部导航按钮还没有变化,别急,我们接着往下看。
2.5 ViewPager联动底部导航按钮
上文我们做了ViewPager的滑动切换页面部分,我们期望的是滑动上面的页面,底部的按钮也跟着状态切换。那么就要求我们监听ViewPager页面滑动,然后响应方法里对按钮做状态切换。
在initEvent方法里继续添加代码:
// 为ViewPager添加页面切换监听
mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
Toast.makeText(FragmentViewPagerBottomActivity.this, "当前第" + position + "页", Toast.LENGTH_SHORT).show();
// 页面切换后的处理
onPagerSelected(position);
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
主要是 onPageSelected 这个回调方法,当真正的滑到了另一个页面时会调用它,参数position表示当前第几个页面,从0开始计数。
我们在里面加了Toast,方便观察当前第几个页面。接着我们需要处理底部导航按钮状态切换的逻辑,这部分我们封装成一个方法来处理,即:onPagerSelected方法。
private void onPagerSelected(int position) {
resetBottomTab();
switch (position) {
case 0:
mIvHome.setSelected(true);
mTvHome.setTextColor(getResources().getColor(R.color.green_500));
break;
case 1:
mIvFind.setSelected(true);
mTvFind.setTextColor(getResources().getColor(R.color.green_500));
break;
case 2:
mIvMine.setSelected(true);
mTvMine.setTextColor(getResources().getColor(R.color.green_500));
break;
default:
break;
}
}
private void resetBottomTab() {
mTvHome.setTextColor(getResources().getColor(R.color.black));
mTvFind.setTextColor(getResources().getColor(R.color.black));
mTvMine.setTextColor(getResources().getColor(R.color.black));
mIvHome.setSelected(false);
mIvFind.setSelected(false);
mIvMine.setSelected(false);
}
跟前文类似,先重置所有按钮状态(恢复成都不选中),再根据当前的选中的position,设置对应的按钮为绿色。
这样就完成了ViewPager页面切换与底部按钮的联动。
这就结束了吗?
没有,不要忘了ViewPager和底部按钮的联动需要是双向的。上面只是页面切换 --> 底部按钮状态变换,还需要反过来:点击底部导航导航按钮 --> 页面切换。
2.6 底部导航按钮联动ViewPager
这一步也很简单。前文我们已经为底部按钮设置了点击监听,并且预留了onClick方法。接下来只需要在onClick方法中做页面切换的逻辑就行了,我们依然封装成一个方法来实现。
@Override
public void onClick(View v) {
setBottomTabSelected(v.getId());
}
private void setBottomTabSelected(int tabId) {
switch (tabId) {
case R.id.rl_home:
mViewPager.setCurrentItem(0);
break;
case R.id.rl_find:
mViewPager.setCurrentItem(1);
break;
case R.id.rl_mine:
mViewPager.setCurrentItem(2);
break;
default:
break;
}
}
可见,切换ViewPager里的页面只需要一行代码:mViewPager.setCurrentItem(position);
ViewPager还是很方便的吧。
结束了吗?
还差最后一步,跟前文一样,最后我们还需要设置一个默认进入的页面,也就是让ViewPager默认显示哪一页。
在initEvent方法最后添加:onPagerSelected(0);
即可。这就是封装方法的好处,体会到了吗。
3. 完整代码
终于,我们完成了整个案例。下面附上整个主界面FragmentViewPagerBottomActivity的完整代码。
至于其他的资源文件和前文Fragment+BottomTab实现页面导航一样,不再复述。
/**
* viewpager+fragment+bottomTab
* 普通的控件拼接成的底部导航菜单
*/
public class FragmentViewPagerBottomActivity extends AppCompatActivity implements View.OnClickListener {
private RelativeLayout mRlHome, mRlSetting, mRlMine;
private TextView mTvHome, mTvFind, mTvMine;
private ImageView mIvHome, mIvFind, mIvMine;
private ViewPager mViewPager;
private List<Fragment> mFragments;
private MyViewPagerAdapterForFragment mMyViewPagerAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fragment_view_pager_bottom);
initView();
initData();
initEvent();
}
private void initData() {
mFragments = new ArrayList<>();
for (int i = 0; i < 3; i++) {
ExampleFragment fragment = ExampleFragment.newInstance("fragment" + i, "");
mFragments.add(fragment);
}
}
private void initEvent() {
mRlHome.setOnClickListener(this);
mRlSetting.setOnClickListener(this);
mRlMine.setOnClickListener(this);
mMyViewPagerAdapter = new MyViewPagerAdapterForFragment(getSupportFragmentManager(), mFragments);
mViewPager.setAdapter(mMyViewPagerAdapter);
mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
onPagerSelected(position);
Toast.makeText(FragmentViewPagerBottomActivity.this, "当前第" + position + "页", Toast.LENGTH_SHORT).show();
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
onPagerSelected(0);
}
private void initView() {
mViewPager = findViewById(R.id.vp);
mRlHome = findViewById(R.id.rl_home);
mRlSetting = findViewById(R.id.rl_find);
mRlMine = findViewById(R.id.rl_mine);
mTvMine = findViewById(R.id.tv_mine);
mTvFind = findViewById(R.id.tv_find);
mTvHome = findViewById(R.id.tv_home);
mIvMine = findViewById(R.id.iv_mine);
mIvFind = findViewById(R.id.iv_find);
mIvHome = findViewById(R.id.iv_home);
}
@Override
public void onClick(View v) {
setBottomTabSelected(v.getId());
}
private void resetBottomTab() {
mTvHome.setTextColor(getResources().getColor(R.color.black));
mTvFind.setTextColor(getResources().getColor(R.color.black));
mTvMine.setTextColor(getResources().getColor(R.color.black));
mIvHome.setSelected(false);
mIvFind.setSelected(false);
mIvMine.setSelected(false);
}
private void onPagerSelected(int position) {
resetBottomTab();
switch (position) {
case 0:
mIvHome.setSelected(true);
mTvHome.setTextColor(getResources().getColor(R.color.green_500));
break;
case 1:
mIvFind.setSelected(true);
mTvFind.setTextColor(getResources().getColor(R.color.green_500));
break;
case 2:
mIvMine.setSelected(true);
mTvMine.setTextColor(getResources().getColor(R.color.green_500));
break;
default:
break;
}
}
private void setBottomTabSelected(int tabId) {
switch (tabId) {
case R.id.rl_home:
mViewPager.setCurrentItem(0);
break;
case R.id.rl_find:
mViewPager.setCurrentItem(1);
break;
case R.id.rl_mine:
mViewPager.setCurrentItem(2);
break;
default:
break;
}
}
}