本篇文章纲要
- 零、先看效果
- 一、准备
- 开始动手
- 1.添加依赖
- 2.新建布局文件
- 3、添加tabLayout和ViewPager布局
- 4.添加一个activity页面
- 5.按钮点击事件的处理
零、先看效果
一、准备
tab的使用我们经常能看到,在各大应用中也是颇受青睐,而对于Android开发而言,最常用的方式是tabLayout+ViewPager来实现这种需求。为了满足用户个性化的需求,越来越多的应用选择动态改变tab的数量。
那么我们今天就模仿这个,利用viewpager+tablayout的方式来自己写一个。本文不会有太多原理性的东西,直接上代码。
开始动手
1.添加依赖
使用TabLayout,首先要在build.gradle中添加如下依赖
implementation 'com.google.android.material:material:1.2.1'
2.新建布局文件
添加如下四个按钮(Button)和两个(EditText)。
具体布局大家可以自己写,这里给出我的布局示例
(我的布局使用了约束布局ConstraintLayout+表格布局tableLayout,不过这都不是重点,接着往下看)
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
tools:context=".row.sixth.viewpager.ViewPagerActivity">
<LinearLayout
android:id="@+id/button_area"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TableLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TableRow>
<Button
android:id="@+id/add_last_page"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/button_add_last_page" />
<Button
android:id="@+id/decrease_last_page"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/button_decrease_last_page" />
</TableRow>
<TableRow>
<Button
android:id="@+id/add_first_page"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/button_add_first_page" />
<Button
android:id="@+id/decrease_first_page"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/button_decrease_first_page" />
</TableRow>
<TableRow>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/text_pos" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/text_count" />
</TableRow>
<TableRow>
<EditText
android:id="@+id/pos"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:inputType="phone"
android:maxLength="3" />
<EditText
android:id="@+id/count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:inputType="phone"
android:maxLength="3" />
</TableRow>
<TableRow>
<Button
android:id="@+id/auto_add"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/text_auto_add" />
<Button
android:id="@+id/auto_dec"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/text_auto_des" />
</TableRow>
</TableLayout>
</LinearLayout>
<!--这里待添加关键布局-->
</androidx.constraintlayout.widget.ConstraintLayout>
<string name="button_add_last_page">在最后增加一页</string>
<string name="button_decrease_last_page">在最后减少一页</string>
<string name="button_add_first_page">增加第一个</string>
<string name="button_decrease_first_page">减少第一个</string>
<string name="text_pos">位置</string>
<string name="text_count">个数</string>
<string name="text_auto_add">自由添加</string>
<string name="text_auto_des">自由减少</string>
3、添加tabLayout和ViewPager布局
在上面的布局文件中
添加一个tabLayout
<com.google.android.material.tabs.TabLayout
android:id="@+id/tab_layout"
android:layout_width="match_parent"
android:layout_height="@dimen/dp_50"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/button_area"
app:tabMode="scrollable" />
再在tablayout下面添加一个ViewPager
<androidx.viewpager.widget.ViewPager
android:id="@+id/view_pager"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tab_layout" />
4.添加一个activity页面
我们的主要逻辑都在这个页面里,tabLayout和ViewPager的基础使用方式为:
1.初始化tabLayout和viewPager;
private TabLayout mTabLayout;
private ViewPager mViewPager;
private List<String> mTabNameList = new ArrayList<>();
private List<String> mTabIdList = new ArrayList<>();
private List<TabFragment> mFragmentList = new ArrayList<>();
private int mCurrentSelect = 0;
……
mTabLayout = findViewById(R.id.tab_layout);
mViewPager = findViewById(R.id.view_pager);
……
initTab();
private void initTab() {
//这里初始化一些数据
for (int i = 0; i < 3; i++) {
mTabNameList.add("tab" + i);
mTabIdList.add(String.valueOf(i));
}
for (int i = 0; i < mTabNameList.size(); i++) {
TextView textView = (TextView) LayoutInflater.from(this).inflate(R.layout.tab_layout, null);
textView.setText(mTabNameList.get(i));
TabLayout.Tab tab = mTabLayout.newTab().setCustomView(textView);
mTabLayout.addTab(tab, (i == mCurrentSelect));
TabFragment fragment = new TabFragment("页面" + i + ",\n" + "时间: " + System.currentTimeMillis());
mFragmentList.add(fragment);
}
}
2.新建一个Adapter,我把它命名为TabViewPagerAdapter,这个Adapter继承FragmentPagerAdapter,adapter用于管理viewpager的每一个page;
private TabViewPagerAdapter mAdapter;
……
mAdapter = new TabViewPagerAdapter(getSupportFragmentManager(), FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT, mFragmentList);
……
private class TabViewPagerAdapter extends FragmentPagerAdapter {
private FragmentManager mFragmentManager;
private List<TabFragment> mFragmentList = new ArrayList<>();
public TabViewPagerAdapter(@NonNull FragmentManager fm, int behavior, List<TabFragment> fragmentList) {
super(fm, behavior);
mFragmentManager = fm;
updateFragmentList(fragmentList);
}
public void updateFragmentList(List<TabFragment> fragmentList) {
if (!mFragmentList.isEmpty()) {
mFragmentList.clear();
}
mFragmentList.addAll(fragmentList);
notifyDataSetChanged();
mViewPager.setOffscreenPageLimit(mFragmentList.size());
}
@NonNull
@Override
//……覆写的一系列函数
}
3.将tabLayout和viewPager相互绑定,并设置监听
mViewPager.setAdapter(mAdapter);
mViewPager.setOffscreenPageLimit(mFragmentList.size());
mViewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(mTabLayout));
mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
mCurrentSelect = position;
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
mTabLayout.setupWithViewPager(mViewPager);
mTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
mViewPager.setCurrentItem(tab.getPosition(), false);
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {
}
@Override
public void onTabReselected(TabLayout.Tab tab) {
}
});
4.重点:在TabViewPagerAdapter中重写fragmentViewPagerAdapter的以下几个方法:
/**
** 作用:根据位置(position),返回当前的fragment
**/
@Override
public Fragment getItem(int position) {}
/**
** 作用:返回当前viewpager的page个数
**/
@Override
public int getCount() {}
/**
**作用:根据位置(position),返回当前fragment的id
**/
@Override
public long getItemId(int position) {}
/**
**作用:根据位置(position),返回当前fragment的标题
**/
@Override
public CharSequence getPageTitle(int position) {}
/**
**作用:当fragment被创建的时候执行
**/
@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {}
/**
**作用:当fragment被销毁的时候执行
**/
@Override
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {}
/**
**作用:当主视图视图确定当前item的位置是否有更改
**/
@Override
public int getItemPosition(@NonNull Object object) {}
具体的覆写如下:
三个常用的覆写函数就不做过多描述:
@NonNull
@Override
public Fragment getItem(int position) {
return mFragmentList.get(position);
}
@Override
public int getCount() {
return mFragmentList.size();
}
@Nullable
@Override
public CharSequence getPageTitle(int position) {
return mTabNameList.get(position);
}
实现tab个viewpager的重点在于如下覆写的五个函数,
其中instantiateItem()和destroyItem()函数覆写参考了这篇博客,见注释[^1]。
//将每一个item的id指定为fragment的hasCode,确保不会变更
@Override
public long getItemId(int position) {
return mFragmentList.get(position).hashCode();
}
@NonNull
@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
Fragment instantiateItemFragment = (Fragment) super.instantiateItem(container, position);
Fragment itemFragment = mFragmentList.get(position);
//如果集合中对应下标的fragment和fragmentManager中的对应下标的fragment对象一致,则直接返回该fragment
if (instantiateItemFragment == itemFragment) {
return instantiateItemFragment;
} else {
//如果集合中对应下标的fragment和fragmentManager中的对应下标的fragment对象不一致,那么就是新添加的,所以自己add进入;
mFragmentManager.beginTransaction().add(container.getId(), itemFragment).commit();
return itemFragment;
}
}
@Override
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
Fragment fragment = (Fragment) object;
//如果getItemPosition中的值为PagerAdapter.POSITION_NONE,就执行该方法。
if (mFragmentList.contains(fragment)) {
super.destroyItem(container, position, object);
return;
}
//自己执行移除。因为mFragments在删除的时候就把某个fragment对象移除了,所以一般都得自己移除在fragmentManager中的该对象。
if (!getSupportFragmentManager().isStateSaved()) {
mFragmentManager.beginTransaction().remove(fragment).commit();
}
}
@Override
public int getItemPosition(@NonNull Object object) {
//如果fragment还没有添加过,或者没有包含在里面,则返回没有找到
if (!((Fragment) object).isAdded() || !mFragmentList.contains(object)) {
return POSITION_NONE;
}
//否则就返回列表中的位置
return mFragmentList.indexOf(object);
}
5.按钮点击事件的处理
增加tab和viewpager的逻辑
/**
*
* @param pos :需要在哪个位置开始增加
* @param count :增加的个数
*/
private void addFragment(int pos, int count) {
//增加的位置超过现有的tab个数,无效
if (pos > mFragmentList.size()) {
pos = mFragmentList.size();
}
//位置小于0也无效
if (pos < 0) {
pos = 0;
}
//增加的数量小于0也无意义
if (count <= 0) {
count = 1;
}
for (int i = pos; i < pos + count; i++) {
long time = System.currentTimeMillis();
mTabIdList.add(i, "增加tabId" + time);
mTabNameList.add(i, "tab名称" + time);
TextView textView = (TextView) LayoutInflater.from(this).inflate(R.layout.tab_layout, null);
textView.setText(mTabNameList.get(i));
TabLayout.Tab tab = mTabLayout.newTab().setCustomView(textView);
mTabLayout.addTab(tab, i);
TabFragment tabFragment = new TabFragment("增加Fragment:" + "\n" + "时间戳:" + time);
mFragmentList.add(pos, tabFragment);
}
if (mViewPager.getAdapter() instanceof TabViewPagerAdapter) {
//源数据有变更后,要及时更新
((TabViewPagerAdapter) mViewPager.getAdapter()).updateFragmentList(mFragmentList);
}
//再选中tab变更之前的tab
mViewPager.post(new Runnable() {
@Override
public void run() {
mTabLayout.selectTab(mTabLayout.getTabAt(mCurrentSelect));
}
});
}
减少tab和viewpager的逻辑
/**
*
* @param pos :需要在哪个位置开始减少
* @param count :减少的个数
*/
private void decFragment(int pos, int count) {
if (mFragmentList.size() < count) {
if (count > 1) {
ToastUtil.toastLong(getString(R.string.text_input_count_error));
} else {
ToastUtil.toastLong(getString(R.string.text_dec_tip));
}
return;
} else if (mFragmentList.size() == count) {
ToastUtil.toastLong(getString(R.string.text_dec_all_tip));
return;
}
if (pos > mFragmentList.size() - 1) {
pos = mFragmentList.size() - 1;
}
if (pos < 0) {
pos = 0;
}
if (count <= 0) {
count = 1;
}
for (int i = pos; i < pos + count; i++) {
mTabIdList.remove(i);
mTabNameList.remove(i);
mTabLayout.removeTabAt(i);
TabFragment tabFragment = (TabFragment) mFragmentList.get(i);
mFragmentList.remove(i);
}
if (mViewPager.getAdapter() instanceof TabViewPagerAdapter) {
((TabViewPagerAdapter) mViewPager.getAdapter()).updateFragmentList(mFragmentList);
}
//再选中tab变更之前的tab
mViewPager.post(new Runnable() {
@Override
public void run() {
mTabLayout.selectTab(mTabLayout.getTabAt(mCurrentSelect), false);
}
});
}
剩余的按钮点击事件逻辑:
@Override
public void onClick(View view) {
int id = view.getId();
switch (id) {
case R.id.add_last_page:
addFragment(mFragmentList.size(), 1);
break;
case R.id.decrease_last_page:
decFragment(mFragmentList.size() - 1, 1);
break;
case R.id.add_first_page:
addFragment(0, 1);
break;
case R.id.decrease_first_page:
decFragment(0, 1);
break;
case R.id.auto_add:
if (TextUtils.isEmpty(mEditTextPos.getText().toString())) {
ToastUtil.toastLong(getString(R.string.text_pos_tip));
return;
}
if (TextUtils.isEmpty(mEditTextCount.getText().toString())) {
ToastUtil.toastLong(getString(R.string.text_count_tip));
return;
}
String countString1 = mEditTextCount.getText().toString();
String posString1 = mEditTextPos.getText().toString();
try {
int count = Integer.parseInt(countString1);
int pos = Integer.parseInt(posString1);
addFragment(pos, count);
} catch (Exception e) {
ToastUtil.toastLong(getString(R.string.text_input_tip));
}
break;
case R.id.auto_dec:
if (TextUtils.isEmpty(mEditTextPos.getText().toString())) {
ToastUtil.toastLong(getString(R.string.text_pos_tip));
return;
}
if (TextUtils.isEmpty(mEditTextCount.getText().toString())) {
ToastUtil.toastLong(getString(R.string.text_count_tip));
return;
}
String countString2 = mEditTextCount.getText().toString();
String posString2 = mEditTextPos.getText().toString();
try {
int count = Integer.parseInt(countString2);
int pos = Integer.parseInt(posString2);
decFragment(pos, count);
} catch (Exception e) {
ToastUtil.toastLong(getString(R.string.text_input_tip));
}
break;
default:
break;
}
}