在项目页面,主要包括2个模块,分别为导航页面和项目页面,因此我考虑使用TabLayout和ViewPager来实现,这也是我在项目中第一次引进TabLayout + ViewPager的结合UI框架。
1、ViewPager 2简介
在我们自定义Banner
时,使用到的控件是ViewPager
,在去年的谷歌大会上,推出的控件,来代替之前的ViewPager
。它跟ViewPager
不同的是,它支持两种滑动方式:垂直滑动和水平滑动。
public static final int ORIENTATION_HORIZONTAL = RecyclerView.HORIZONTAL;
public static final int ORIENTATION_VERTICAL = RecyclerView.VERTICAL;
我自己的理解就是水平的RecyclerView
和竖直的RecyclerView
,在水平滑动时,其实每个页面就相当于一个RecyclerView
的Item
,这样理解在后面适配器设置中就会理解了。
除了方向上不同之外,在设置适配器时,传统的ViewPager需要的是PageAdapter
,我们需要自己去填充页面,然后放进Container;ViewPager2继承是RecyclerView
的适配器,创建ViewHolder来绑定每个Item页面。
说一下ViewPager2
跟ViewPager
有什么不同之处;之前在ViewPager
和Fragment
之间,通过FragmentStatePagerAdapter
来联系起来,现在是用FragmentStateAdapter
;
vp2_main.setAdapter(new FragmentStateAdapter(this) {
@NonNull
@Override
public Fragment createFragment(int position) {
return fragments.get(position);
}
@Override
public int getItemCount() {
return fragments.size();
}
});
之前监听ViewPager
的滑动监听,使用addPageChangeListener
来做页面的滑动监听,现在修改为registerOnPageChangeCallback
;
vp2_main.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
@Override
public void onPageSelected(int position) {
bnb_main.selectTab(position);
}
});
2、TabLayout简介
<com.google.android.material.tabs.TabLayout
android:id="@+id/tab_tour"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabIndicatorColor="@color/colorOrange"
app:tabMode="scrollable"
app:tabIndicatorGravity="bottom"
app:tabIndicatorHeight="2dp"
></com.google.android.material.tabs.TabLayout>
我直接对照布局说一下属性就好了,这个使用过的应该都了解过:
—tabIndicatorColor
:指示器的颜色
—background:TabLayout
的背景
—tabIndicatorHeight
:设置指示器的厚度
—tabMode
:设置为可滑动的
—tabIndicatorGravity
:设置指示器的位置,这里设置为下方
3、TabLayout + ViewPager2的使用
(1)添加依赖
implementation 'androidx.viewpager2:viewpager2:1.0.0'
implementation 'com.google.android.material:material:1.2.0-alpha04'
(2)布局
<com.google.android.material.tabs.TabLayout
android:id="@+id/tab_tour"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabIndicatorColor="@color/colorOrange"
app:tabMode="scrollable"
app:tabIndicatorGravity="bottom"
app:tabIndicatorHeight="2dp"
></com.google.android.material.tabs.TabLayout>
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/vp2_tour"
android:layout_width="match_parent"
android:layout_height="match_parent"
></androidx.viewpager2.widget.ViewPager2>
(3)在将ViewPager 2和TabLayout关联之前,先要给ViewPager2设置适配器,不然,会报错:TabLayoutMediator attached before ViewPager2 has an adapter
@Override
public void showTour(List<TourBean.DataBean> data) {
List<String> titleList = new ArrayList<>();
for (int i = 0; i < titles.length; i++) {
titleList.add(titles[i]);
//之前介绍过,两个模块:导航和项目
}
pjAdapter = new ProjectViewPagerAdapter(getContext(), titleList, data);
vp2_project.setAdapter(pjAdapter);
TabLayoutMediator mediator = new TabLayoutMediator(tab_title, vp2_project, new TabLayoutMediator.TabConfigurationStrategy() {
@Override
public void onConfigureTab(@NonNull TabLayout.Tab tab, int position) {
//设置tab标题
tab.setText(titleList.get(position));
Log.e("TAG","tab position===="+position);
}
});
mediator.attach();
}
在设置适配器之后,调用TabLayoutMediator
来将TabLayout和ViewPager 2绑定:
public TabLayoutMediator(
@NonNull TabLayout tabLayout,
@NonNull ViewPager2 viewPager,
@NonNull TabConfigurationStrategy tabConfigurationStrategy) {
this(tabLayout, viewPager, true, tabConfigurationStrategy);
}
在onConfigureTab
方法中,来设置Tab
的名称,position
是0 - 1;
接下来看看适配器源码:
public class ProjectViewPagerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private final Context context;
private final List<String> titleList;
//导航
private static final int TOUR = 0;
//项目
private static final int PROJECT = 1;
//当前的item
private int currentType = TOUR;
//导航栏数据
private final List<TourBean.DataBean> tourList;
//Tour的适配器
private TourItemAdapter tourAdapter;
public ProjectViewPagerAdapter(Context context, List<String> titleList, List<TourBean.DataBean> tourList){
this.context = context;
this.titleList = titleList;
this.tourList = tourList;
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if (viewType == TOUR) {
View view = LayoutInflater.from(context).inflate(R.layout.item_tour, parent, false);
return new TourViewHolder(view);
}else if(viewType == PROJECT){
View view = LayoutInflater.from(context).inflate(R.layout.item_project, parent, false);
return new ProjectViewHolder(view);
}
return null;
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
if(holder instanceof ProjectViewHolder){
Log.e("TAG","position====="+position);
((ProjectViewHolder) holder).tv_project_title.setText(titleList.get(position));
}else if(holder instanceof TourViewHolder){
//绑定导航界面的数据。
}
}
@Override
public int getItemCount() {
return 2;
}
class ProjectViewHolder extends RecyclerView.ViewHolder{
private TextView tv_project_title;
public ProjectViewHolder(@NonNull View itemView) {
super(itemView);
tv_project_title = itemView.findViewById(R.id.tv_project_title);
}
}
class TourViewHolder extends RecyclerView.ViewHolder{
private TabLayout tab_tour;
private ViewPager2 vp2_tour;
public TourViewHolder(@NonNull View itemView) {
super(itemView);
tab_tour = itemView.findViewById(R.id.tab_tour);
vp2_tour = itemView.findViewById(R.id.vp2_tour);
}
}
@Override
public int getItemViewType(int position) {
switch (position){
case TOUR:
currentType = TOUR;
break;
case PROJECT:
currentType = PROJECT;
break;
}
return currentType;
}
}
之前在说过,ViewPager2
的适配器是继承自RecyclerView
的Adapter
,每个Item都是一个模块,是两个不同的类型,因此通过getItemViewType
来根据position判断当前是哪个Item,如果position
= 0,也就是TOUR
,那么我就去加载导航页面的布局,返回导航页面的holder
,下面就会根据holder去绑定相应的数据。
比如说,我在导航界面,还是想做TabLayout
+ ViewPager2
的UI结构,那么就在导航界面的布局中加入这个结构,同样的,也是给ViewPager2
设置适配器,然后传入数据。
else if(holder instanceof TourViewHolder){
tourAdapter = new TourItemAdapter(context,tourList);
((TourViewHolder) holder).vp2_tour.setAdapter(tourAdapter);
TabLayoutMediator mediator = new TabLayoutMediator(((TourViewHolder) holder).tab_tour,
((TourViewHolder) holder).vp2_tour, new TabLayoutMediator.TabConfigurationStrategy() {
@Override
public void onConfigureTab(@NonNull TabLayout.Tab tab, int position) {
tab.setText(tourList.get(position).getName());
}
});
mediator.attach();
ViewPager的适配器源码先放在这吧。
public class TourItemAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private final Context context;
private final List<TourBean.DataBean> tourList;
public TourItemAdapter(Context context, List<TourBean.DataBean> tourList){
this.context = context;
this.tourList = tourList;
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view= LayoutInflater.from(context).inflate(R.layout.item_tour_adapter,parent,false);
return new TourItemViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
if(holder instanceof TourItemViewHolder){
List<TourBean.DataBean.ArticlesBean> articles = tourList.get(position).getArticles();
for (int i = 0; i < articles.size(); i++) {
TextView tv = new TextView(context);
String title = articles.get(i).getTitle();
tv.setText(title);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
tv.setLayoutParams(params);
((TourItemViewHolder) holder).fl_tour.addView(tv);
}
}
}
@Override
public int getItemCount() {
return tourList.size();
}
class TourItemViewHolder extends RecyclerView.ViewHolder{
private FlexboxLayout fl_tour;
public TourItemViewHolder(@NonNull View itemView) {
super(itemView);
fl_tour = itemView.findViewById(R.id.fl_tour);
}
}
}
就是将每个Tab下的数据,放进FlexBoxLayout容器中(发现我独爱FlexBoxLayout)。
看下效果:
但是滑了一会,有出现了之前RecyclerView的数据错乱的现象,现在应该知道了,还是缓存的问题:
我说一下ViewPager的缓存机制,在ViewPager中,默认的缓存数是1,就是当前页面的左右两边的页面缓存,一共是3个,当我们滑动页面之后,移出缓存区的页面再次进入需要重新绑定数据,因此又出现这个错乱问题,所以自己设置一下缓存数即可。
((TourViewHolder) holder).vp2_tour.setOffscreenPageLimit(tourList.size()/2);
看一下源码就知道了
public void setOffscreenPageLimit(@OffscreenPageLimit int limit) {
if (limit < 1 && limit != OFFSCREEN_PAGE_LIMIT_DEFAULT) {
throw new IllegalArgumentException(
"Offscreen page limit must be OFFSCREEN_PAGE_LIMIT_DEFAULT or a number > 0");
}
mOffscreenPageLimit = limit;
// Trigger layout so prefetch happens through getExtraLayoutSize()
mRecyclerView.requestLayout();
}
这样的话问题就解决了。
我贴出来的图片一般只是看运行成功与否,后期的UI设计美化大家可以自己做。
4、TabLayout
的属性使用
(1)tabMode
在上面的布局设计中,tabMode采用的是“fixed
”,那么因为只有两个Tab,所以就一人一边平分;tabMode还有其他的属性,例如“auto
”
app:tabMode="auto"
这样设置,Tab的位置就改变了。
(2)设置Tab中的文字字体
这个在属性API中没有设置Tab的字体的值,因此需要自己去动态设置。
tab_title.addOnTabSelectedListener(new TabTextSizeListener());
private class TabTextSizeListener implements TabLayout.OnTabSelectedListener {
@Override
public void onTabSelected(TabLayout.Tab tab) {
TextView textView = new TextView(getContext());
textView.setTextSize(20);
textView.setText(tab.getText());
tab.setCustomView(textView);
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {
//这个必须得设置,不然会一直叠加
tab.setCustomView(null);
}
@Override
public void onTabReselected(TabLayout.Tab tab) {
}
}
好了,TabLayout就先介绍到这里,后续会继续更新!