先上个效果图:
废话一下
目前很多界面都是这种TabLayout+ViewPager的组合界面,TabLayout自带的指示器样式,似乎有点不够用。我个人并不喜欢写很多style的xml设置给TabLayout。我想的是指示器要白色三角形,就直接写个ImageView,给一张白色三角形的图片就行了。指示器需要什么样子就在布局文件中写成什么样子,这样会让我比较爽。所以就写了个这玩意。把原来的TabLayout+ViewPger变成了TabLayout+TabIndicatorLayout+ViewPager.
布局代码
在布局中找到你需要放Indicator指示器的位置,用TabIndicatorLayout这个控件,包裹你的指示器就行了,比如我这里的指示器是一个白色三角形,TriangleView就是白色三角形,这个自己写就行了。如果你的指示器是其他的什么,一样放这里就行了,比如ImageView,设置为一张白色三角形的图片。。或者你的指示器是一个复杂的布局(基本不存在),你可以在这里直接写你的布局,比如一个LinearLayout,包裹你要显示的Indicator的内容。
<!--TabLayout,不解释-->
<android.support.design.widget.TabLayout
android:id="@+id/tab_layout"
android:layout_width="match_parent"
android:layout_height="55dp"
android:background="@color/colorPrimary"
app:tabIndicatorHeight="0dp"
app:tabMode="scrollable"
app:tabTextColor="#ffffff">
</android.support.design.widget.TabLayout>
<!--TabIndicatorLayout,指示器的容器,将你需要的指示器布局,写在这个容器中就行了-->
<com.ts_xiaoa.tsxiaoa_lib.widget.TabIndicatorLayout
android:id="@+id/tab_indicator_layout"
android:layout_width="match_parent"
android:layout_height="10dp"
android:background="@color/colorPrimary">
<!--白色三角形,这里可以放任意其他布局,比如ImageView...-->
<com.ts_xiaoa.tsxiaoa_lib.widget.TriangleView
android:layout_width="20dp"
android:layout_height="10dp" />
</com.ts_xiaoa.tsxiaoa_lib.widget.TabIndicatorLayout>
<!--ViewPager,不解释-->
<android.support.v4.view.ViewPager
android:id="@+id/view_pager"
android:layout_width="match_parent"
android:layout_height="match_parent">
</android.support.v4.view.ViewPager>
java代码
在代码中需要为TabIndicator绑定tabLayout和ViewPager。看下面代码中的最后一句,最后一句,最后一句。
//获取控件
TabLayout tabLayout = findViewById(R.id.tab_layout);
ViewPager viewPager = findViewById(R.id.view_pager);
TabIndicatorLayout tabIndicatorLayout = findViewById(R.id.tab_indicator_layout);
//设置viewpager的适配器
viewPager.setAdapter(new FragmentPagerAdapter(getSupportFragmentManager()) {
@Override
public Fragment getItem(int i) {
return new MyFragment();
}
@Override
public int getCount() {
return 0;
}
@Nullable
@Override
public CharSequence getPageTitle(int position) {
return position % 3 == 0 ? "我的标题" : "我的比较长的标题";
}
});
//绑定TabLayout,ViewPager
tabLayout.setupWithViewPager(viewPager);
//绑定TabIndicatorLayout,TabLayout,ViewPager
tabIndicatorLayout.setupWithViewPager(tabLayout, viewPager);
实现步骤
这其实没什么可说的,就是监听viewpager的滑动,根据当前TabItem的位置,移动Indicator的位置。逻辑也不复杂。具体的逻辑都在代码中了,改写的注释都写了。没有自定义属性什么的,所以就这一个类。代码不多,就几行,全部都贴出来了。关键代码是71-75行代码和86行-125行,也就是两个监听滑动的方法。
/**
* create by ts_xiaoa
* create Time 2018/9/14
* description:
*/
public class TabIndicatorLayout extends FrameLayout implements ViewTreeObserver.OnScrollChangedListener, ViewPager.OnPageChangeListener {
//需要显示的指示器
private View indicatorView;
//绑定的viewpager和tabLayout
private TabLayout tabLayout;
private ViewPager viewPager;
//tabItem的父容器
private ViewGroup tabParentLayout;
//记录Indicator的宽度是否为MATCH_PARENT
private boolean isMatchParent;
private boolean isPagerScroll;
public TabIndicatorLayout(@NonNull Context context) {
super(context);
}
public TabIndicatorLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public TabIndicatorLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (getChildCount() > 1) {
throw new IllegalArgumentException("只能有一个indicator布局");
}
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
//得到指示器indicator的视图
indicatorView = getChildAt(0);
//判断是否需要Indicator宽度全充满
switch (indicatorView.getLayoutParams().width) {
case LayoutParams.MATCH_PARENT:
isMatchParent = true;//记录是否需要全充满
break;
default:
isMatchParent = false;//记录是否需要全充满
break;
}
}
public void setupWithViewPager(TabLayout tabLayout, ViewPager viewPager) {
this.tabLayout = tabLayout;
this.viewPager = viewPager;
this.tabLayout.getViewTreeObserver().addOnScrollChangedListener(this);
this.viewPager.addOnPageChangeListener(this);
//得到TabItem的父布局
this.tabParentLayout = (ViewGroup) tabLayout.getChildAt(0);
}
/**
* 监听tabLayout的滑动
*/
@Override
public void onScrollChanged() {
//如果用户只拖动TabLayout,让Indicator的位置跟着TabItem一起动
if (!isPagerScroll) {
int tabRelationX = (int) tabParentLayout.getChildAt(viewPager.getCurrentItem()).getX() - tabLayout.getScrollX();
int tabWidth = tabParentLayout.getChildAt(viewPager.getCurrentItem()).getMeasuredWidth();
indicatorView.setX(tabRelationX + tabWidth / 2 - indicatorView.getMeasuredWidth() / 2);
}
}
/**
* 监听viewPager的滑动
*
* @param position
* @param positionOffset
* @param positionOffsetPixels
*/
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
isPagerScroll = true;
//判断当切换完成到最后一个pager时
if (position == tabParentLayout.getChildCount() - 1) {
//return,下面107行位置tabParentLayout.getChildAt(position + 1).getMeasuredWidth()会报NullPoint
return;
}
/* *******************************indicator移动部分*********************************** */
//得到当前TabItem的X坐标
int currTabItemX = (int) tabParentLayout.getChildAt(position).getX();
//得到当前TabItem的宽度
int currTabItemWidth = tabParentLayout.getChildAt(position).getMeasuredWidth();
//计算出当前Indicator的X 注:没有滑动时indicator的位置X
int indicatorX = currTabItemX + currTabItemWidth / 2 - indicatorView.getMeasuredWidth() / 2;
//得到indicator移动到下一个pager的总距离:(当前TabItem的宽度+下一个TabItem的宽度)/2
int distance = (tabParentLayout.getChildAt(position + 1).getMeasuredWidth() + currTabItemWidth) / 2;
//计算滑动viewpager时的偏移量:偏移比例 * 滑动到下一个pager的总距离
int offset = (int) (positionOffset * (distance));
//得到TabLayout滑动的X
int tabScrollX = tabLayout.getScrollX();
//计算得出滑动时indicator的X:原来的X+滑动的偏移量 - TabLayout滑动的X
indicatorX += offset - tabScrollX;
//重新设置当前indicator的X
indicatorView.setX(indicatorX);
/* *******************************indicator移动部分*********************************** */
/* *******************************indicator宽度变化部分*********************************** */
//如果indicator的宽度设置为MATCH_PARENT,每个TabItem的宽度不一样
if (isMatchParent) {
int leftWidth = tabParentLayout.getChildAt(position).getMeasuredWidth();
int rightWidth = tabParentLayout.getChildAt(position + 1).getMeasuredWidth();
int indicatorWidth = (int) (leftWidth + (rightWidth - leftWidth) * positionOffset);
ViewGroup.LayoutParams layoutParams = indicatorView.getLayoutParams();
layoutParams.width = indicatorWidth;
indicatorView.setLayoutParams(layoutParams);
}
/* *******************************indicator宽度变化部分*********************************** */
}
@Override
public void onPageSelected(int i) {
isPagerScroll = false;
}
@Override
public void onPageScrollStateChanged(int i) {
/**
* ViewPager.SCROLL_STATE_IDLE;值为0 表示空闲
* ViewPager.SCROLL_STATE_DRAGGING;值为1 表示拖动viewPager
* ViewPager.SCROLL_STATE_SETTLING;值为2 表示停止
*/
//记录viewpager是否正在滑动
if (i != ViewPager.SCROLL_STATE_DRAGGING) {
isPagerScroll = false;
}
}
}
最后的最后
今天刚好是九月十八号,提醒大家:
勿忘国耻,犯我华夏者,虽远必诛。