在日常开发中ViewPager加Fragment组合的方式非常常见,而在这种情况下通常会有一个指示器与之组合。这篇博客就来和大家一起实现ViewPager指示器。首先整理一下需求:
1. 首先是指示器整体是横向的,并且当宽度超出屏幕宽度时可以左右滑动,这里我们可以使用横向的ScrollView来实现。
2. 其次是我们的指示器是根据ViewPager的Adapter来确定个数的并且要获取到ViewPager的滑动状态,所以我们的指示器要拿到ViewPager和ViewPager的适配器的实例。
3. 再者就是当ViewPager滑动时我们的指示器和和ViewPager滑动同步的处理了
废话不说了,开始。(ps:表达能力有限( ▼-▼ ))
根据我们自定义View的步骤,第一步是自定义View属性了,在res/value下新建attrs文件并定义属性。
<?xmlversion="1.0"encoding="utf-8"?>
<resources>
<declare-styleablename="TabStrip">
<attrname="indicatorHeight"format="dimension"/>
<attrname="indicatorColor"format="color"/>
<attrname="indicatorMargin"format="dimension"/>
<attrname="indicatorTextColor"format="color"/>
<attrname="indicatorTextSize"format="dimension"/>
<attrname="selectedIndicatorTextSize"format="dimension"/>
</declare-styleable>
</resources>
接下来就是新建类并继承HorizontalScrollView并在构造方法中获取需要的自定义属性了。我们的整个类如下:
/**
* ViewPager指示器
*
* @authorfxx
*
*/
@SuppressLint("NewApi")
public class TabStrip extends HorizontalScrollView {
/**
* 指示器容器
*/
private LinearLayoutcontainer;
/**
* 指示器个数
*/
private int tabCount;
/**
* 当前tab位置,默认为0
*/
private int currentPosition
/**
* 选中的tab位置
*/
private int selectedPosition;
private float currentPositionOffset
private int lastScrollX
/**
* LayoutParams用于添加指示器到指示器容器中时使用,按等权重分配指示器宽度
*/
private LinearLayout.LayoutParamsexpandedTabLayoutParams;
/**
* 指示器颜色
*/
private int indicatorColor;
/**
* 文字颜色
*/
private int textColor;
/**
* 文字大小
*/
private int textSize;
/**
* 选中位置的文字大小
*/
private int selectedTextSize;
/**
* 指示器高度
*/
private int indicatorHeight;
/**
* 指示器左右间距
*/
private int indicatorMargin;
/**
* ViewPager
*/
private ViewPagerviewPager;
/**
* viewpager的适配器
*/
private PagerAdapterpagerAdapter;
/**
* page改变监听器
*/
private final PagerStateChangeListener pagerStateChangeListener =new PagerStateChangeListener();
/**
* 画笔
*/
private Paintpaint;
private Contextcontext;
public TabStrip(Contextcontext, AttributeSet attrs, int defStyleAttr,
intdefStyleRes) {
super(context,attrs, defStyleAttr,defStyleRes);
context, attrs, defStyleAttr, defStyleRes);
}
public TabStrip(Contextcontext, AttributeSet attrs, int defStyleAttr) {
super(context,attrs, defStyleAttr);
context, attrs, defStyleAttr, 0);
}
public TabStrip(Contextcontext, AttributeSet attrs) {
super(context,attrs);
context, attrs, 0, 0);
}
public TabStrip(Contextcontext) {
super(context);
context, null, 0, 0);
}
/**
* 初始化
*
* @param context
* @param attrs
* @param defStyleAttr
* @param defStyleRes
*/
private void init(Context context, AttributeSetattrs, intdefStyleAttr,
intdefStyleRes) {
this.context =context;
// 取消横向的滚动条
setHorizontalScrollBarEnabled(false);
// 指示器容器初始化
container = new LinearLayout(context);
container.setOrientation(LinearLayout.HORIZONTAL);
container.setLayoutParams(new LayoutParams(
android.widget.LinearLayout.LayoutParams.MATCH_PARENT,
android.widget.LinearLayout.LayoutParams.MATCH_PARENT));
// 添加指示器容器到scrollview
container);
// 获取屏幕相关信息
dm
typedArray =context.getTheme().obtainStyledAttributes(attrs,
R.styleable.TabStrip,defStyleAttr, defStyleRes);
int n = typedArray.getIndexCount();
for (inti = 0; i < n; i++) {
intattr = typedArray.getIndex(i);
switch (attr) {
// 指示器颜色,默认黄色
case R.styleable.TabStrip_indicatorColor:
indicatorColor =typedArray.getColor(attr, Color.YELLOW);
break;
// 指示器高度,默认2
case R.styleable.TabStrip_indicatorHeight:
indicatorHeight =typedArray.getDimensionPixelSize(attr, 2);
break;
// 指示器左右间距,默认20
case R.styleable.TabStrip_indicatorMargin:
indicatorMargin =typedArray.getDimensionPixelSize(attr, 20);
break;
// 文字颜色,默认黑色
case R.styleable.TabStrip_indicatorTextColor:
textColor = typedArray.getColor(attr, Color.BLACK);
break;
// 文字大小,默认15
case R.styleable.TabStrip_indicatorTextSize:
textSize = typedArray
attr,
(int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP, 15,dm))
/ 3;
break;
// 选中项的文字大小,默认18
case R.styleable.TabStrip_selectedIndicatorTextSize:
selectedTextSize =typedArray
attr,
(int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP, 18,dm))
/ 3;
break;
default:
break;
}
}
// typedArray回收
typedArray.recycle();
expandedTabLayoutParams =new LinearLayout.LayoutParams(0,
LayoutParams.MATCH_PARENT, 1.0f);
// 初始化画笔
paint = new Paint();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 如果指示器个数为0,直接结束绘画
if (tabCount
return;
}
// 获取onMeasure后的高
final int height
/*
* 画指示器下方的线
*/
// 设置颜色
paint.setColor(indicatorColor);
// 当前指示tab位置
currentTab = container.getChildAt(currentPosition);
// 当前tab的左边相对父容器的左边距
float leftPadding = currentTab.getLeft();
// 当前tab的右边相对于父容器左边距
float rightPadding = currentTab.getRight();
// 如果出现位移
if (currentPositionOffset > 0f &¤tPosition < tabCount
nextTab = container.getChildAt(currentPosition
final float nextTabLeft = nextTab.getLeft();
final float nextTabRight = nextTab.getRight();
leftPadding = (currentPositionOffset *nextTabLeft
currentPositionOffset) *leftPadding);
rightPadding = (currentPositionOffset *nextTabRight
currentPositionOffset) *rightPadding);
}
// 绘制
canvas.drawRect(leftPadding,height - indicatorHeight,rightPadding,
height, paint);
}
/**
* 设置ViewPager
*
* @param viewPager
*/
public void setViewPager(ViewPager viewPager) {
this.viewPager =viewPager;
if (viewPager.getAdapter() ==null) {
throw new IllegalStateException(
"ViewPager does not has aadapter instance");
} else {
pagerAdapter = viewPager.getAdapter();
}
viewPager.addOnPageChangeListener(pagerStateChangeListener);
update();
}
/**
* 更新界面
*/
private void update() {
// 指示器容器移除所有子view
container.removeAllViews();
// 获取指示器个数
tabCount = pagerAdapter.getCount();
// 逐个添加指示器
for (inti = 0; i < tabCount; i++) {
i, pagerAdapter.getPageTitle(i));
}
// 更新Tab样式
updateTabStyle();
getViewTreeObserver()
.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@Override
publicvoid onGlobalLayout() {
getViewTreeObserver()
.removeOnGlobalLayoutListener(this);
currentPosition =viewPager.getCurrentItem();
currentPosition, 0);
}
});
}
/**
* 滑动ScrollView
*
* @param position
* @param offset
*/
private void scrollToChild(intposition, intoffset) {
if (tabCount
return;
}
int newScrollX = container.getChildAt(position).getLeft() +offset;
if (newScrollX !=lastScrollX) {
lastScrollX = newScrollX;
newScrollX, 0);
}
}
/**
* 添加指示器
*
* @param position
* @param title
*/
private void addTab(final int position, CharSequence title) {
tvTab = new TextView(context);
tvTab.setText(title);
tvTab.setTextColor(textColor);
tvTab.setTextSize(textSize);
tvTab.setGravity(Gravity.CENTER);
tvTab.setSingleLine();
tvTab.setFocusable(true);
tvTab.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
viewPager.setCurrentItem(position);
}
});
tvTab.setPadding(indicatorMargin, 0,indicatorMargin, 0);
container.addView(tvTab,position, expandedTabLayoutParams);
}
/**
* 更新指示器样式
*/
private void updateTabStyle() {
for (inti = 0; i < tabCount; i++) {
tab = (TextView)container.getChildAt(i);
if (i ==selectedPosition) {
// 设置选中的指示器文字颜色和大小
tab.setTextColor(indicatorColor);
tab.setTextSize(selectedTextSize);
} else {
tab.setTextColor(textColor);
tab.setTextSize(textSize);
}
}
}
/**
* viewPager状态改变监听
*
* @authorfxx
*
*/
private class PagerStateChangeListener implements OnPageChangeListener {
@Override
public void onPageScrollStateChanged(intstate) {
// 滑动状态为停止时
if (state == ViewPager.SCROLL_STATE_IDLE) {
viewPager.getCurrentItem(), 0);
}
}
/**
* 滚动时,只要处理指示器下方横线的滚动
*/
@Override
public void onPageScrolled(intposition, floatpositionOffset,
intpositionOffsetPixels) {
currentPosition = position;
currentPositionOffset =positionOffset;
// 处理指示器下方横线的滚动
position, (int) (positionOffset
container.getChildAt(position).getWidth()));
invalidate();
}
/**
* page滚动结束
*/
@Override
public void onPageSelected(intposition) {
// 滚动结束后的未知
selectedPosition = position;
// 更新指示器状态
updateTabStyle();
}
}
}
至此,自定义ViewPager指示器的工作已经做完了,接下来就来验证一下,这里需要注意的是ViewPager的适配器需要重写getPageTitle()方法用于给指示器显示。在ViewPager完成适配器的设置后需要将ViewPager实例设置给我们的指示器,部分代码如下:
viewPager = (ViewPager) findViewById(R.id.viewpager);
tabStrip = (TabStrip) findViewById(R.id.tabstrip);
pageAdapter=newFragmentPageAdapter(getSupportFragmentManager());
viewPager.setAdapter(pageAdapter);
tabStrip.setViewPager(viewPager);
最后我们来看一下实现的效果如图:写的比较枯燥,感兴趣的童鞋下载源码看看吧