前言
因为项目需要,需要开发一个如下界面:
android原生的TabLayout不能完成上图的需求。
实现思路
在TabLayout之上绘制一个View,达到背景的效果。这个View的位置通过ViewPager的OnPageChangeListener确定。
layout布局文件如下:
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="50dp">
<com.test.view.ShapeIndicatorView
android:id="@+id/custom_indicator"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="4dp"
app:mode="img_mode"
app:strokecolor="@color/app_brand_color"
app:roundraduis="40"
app:img="@drawable/bg_indicator"
/>
<android.support.design.widget.TabLayout
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabIndicatorColor="@color/app_brand_color"
app:tabIndicatorHeight="1dp"
app:tabSelectedTextColor="@color/app_brand_color"
app:tabTextColor="@color/app_content_normal_color"
/>
</FrameLayout>
<View
style="@style/basic_settings_item_divider"/>
<android.support.v4.view.ViewPager
android:id="@+id/viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
ShapeIndicatorView是一个自定义的覆盖在TabLayout之上的背景View。源码如下:
public class ShapeIndicatorView extends View implements TabLayout.OnTabSelectedListener,ViewPager.OnPageChangeListener{
...
private TabLayout mTabLayout;
private ViewPager mViewPager;
...
public void setupWithTabLayout(final TabLayout tableLayout) {
mTabLayout = tableLayout;
tableLayout.setSelectedTabIndicatorColor(Color.TRANSPARENT);
tableLayout.setOnTabSelectedListener(this);
tableLayout.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
@Override
public void onScrollChanged() {
if (mTabLayout.getScrollX() != getScrollX())
scrollTo(mTabLayout.getScrollX(), mTabLayout.getScrollY());
}
});
ViewCompat.setElevation(this, ViewCompat.getElevation(mTabLayout));
tableLayout.post(new Runnable() {
@Override
public void run() {
if (mTabLayout.getTabCount() > 0)
onTabSelected(mTabLayout.getTabAt(0));
}
});
//清除Tab background
for (int tab = 0; tab < tableLayout.getTabCount(); tab++) {
View tabView = getTabViewByPosition(tab);
tabView.setBackgroundResource(0);
}
}
public void setupWithViewPager(ViewPager viewPager) {
mViewPager = viewPager;
viewPager.addOnPageChangeListener(this);
}
...
}
ShapeIndicatorView内部持有TabLayout和ViewPager的对象实例,在Activity中调用时需要调用setupWithTabLayout和setupWithViewPager函数。
实现TabLayout.OnTabSelectedListener和ViewPager.OnPageChangeListener接口,一个目的是为了保证TabLayout和ViewPager的状态保持一致,另一目的是为了在正确的时机绘制ShapeIndicatorView,具体代码如下:
@Override
public void onTabSelected(TabLayout.Tab tab) {
if (mViewPager != null) {
if (tab.getPosition() != mViewPager.getCurrentItem())
mViewPager.setCurrentItem(tab.getPosition());
} else {
generateShape(tab.getPosition(), 0);
invalidate();
}
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {
}
@Override
public void onTabReselected(TabLayout.Tab tab) {
}
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
generateShape(position, positionOffset);
invalidate();
}
@Override
public void onPageSelected(int position) {
if (mTabLayout.getSelectedTabPosition() != position)
mTabLayout.getTabAt(position).select();
}
@Override
public void onPageScrollStateChanged(int state) {
}
generateShape函数是确定ShapeIndicatorView需要绘制的样式以及位置。代码如下:
private void generateShape(int position, float positionOffset) {
RectF range = new RectF();
View tabView = getTabViewByPosition(position);
if (tabView == null)
return;
int left, top, right, bottom;
left = top = right = bottom = 0;
if (positionOffset > 0.f && position < mTabLayout.getTabCount() - 1) {
View nextTabView = getTabViewByPosition(position + 1);
left += (int) (nextTabView.getLeft() * positionOffset + tabView.getLeft() * (1.f - positionOffset));
right += (int) (nextTabView.getRight() * positionOffset + tabView.getRight() * (1.f - positionOffset));
left += mShapeHorizontalSpace;
right -= mShapeHorizontalSpace;
top = tabView.getTop() + getPaddingTop();
bottom = tabView.getBottom() - getPaddingBottom();
range.set(left, top, right, bottom);
} else {
left = tabView.getLeft() + mShapeHorizontalSpace;
right = tabView.getRight() - mShapeHorizontalSpace;
top = tabView.getTop() + getPaddingTop();
bottom = tabView.getBottom() - getPaddingBottom();
range.set(left, top, right, bottom);
if (range.isEmpty()) {
return;
}
}
switch (mode){
case ImgMode:
mImgRect = new Rect(left,top,right,bottom);
break;
case DrawMode:
if(null ==mDrawPath)
mDrawPath = new Path();
Rect tabsRect = getTabArea();
tabsRect.right += range.width();
tabsRect.left -= range.width();
mDrawPath.reset();
mDrawPath.moveTo(range.left,range.bottom);
mDrawPath.lineTo(range.left,range.top);
mDrawPath.lineTo(range.right,range.top);
mDrawPath.lineTo(range.right,range.bottom);
mDrawPath.lineTo(range.left,range.bottom);
mDrawPath.close();
break;
}
}
最后在ShapeIndicatorView的onDraw方法中drawShape方法,真正绘制:
private void drawShape(Canvas canvas) {
int savePos = canvas.save();
switch (mode){
case ImgMode:
if(mImgRect == null || mImgRect.isEmpty())
return;
canvas.drawBitmap(bitmap,null,mImgRect,mImgPaint);
break;
case DrawMode:
if(mDrawPath == null || mDrawPath.isEmpty())
return;
canvas.drawPath(mDrawPath,mDrawPaint);
break;
}
canvas.restoreToCount(savePos);
}
项目源代码地址:[https://github.com/PaulZJ/ShapeIndicatorViewDemo/tree/master]