https://github.com/hackware1993/MagicIndicator
一、使用
1.导入依赖
repositories {
...
maven {
url "https://jitpack.io"
}
}
dependencies {
...
implementation 'com.github.hackware1993:MagicIndicator:1.6.0' // for support lib
implementation 'com.github.hackware1993:MagicIndicator:1.7.0' // for androidx
}
2.将 MagicIndicator 添加到您的布局 xml
3.通过代码找到MagicIndicator,初始化
MagicIndicator magicIndicator = (MagicIndicator) findViewById(R.id.magic_indicator);
CommonNavigator commonNavigator = new CommonNavigator(this);
commonNavigator.setAdapter(new CommonNavigatorAdapter() {
@Override
public int getCount() {
return mTitleDataList == null ? 0 : mTitleDataList.size();
}
@Override
public IPagerTitleView getTitleView(Context context, final int index) {
ColorTransitionPagerTitleView colorTransitionPagerTitleView = new ColorTransitionPagerTitleView(context);
colorTransitionPagerTitleView.setNormalColor(Color.GRAY);
colorTransitionPagerTitleView.setSelectedColor(Color.BLACK);
colorTransitionPagerTitleView.setText(mTitleDataList.get(index));
colorTransitionPagerTitleView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mViewPager.setCurrentItem(index);
}
});
return colorTransitionPagerTitleView;
}
@Override
public IPagerIndicator getIndicator(Context context) {
// public static final int MODE_MATCH_EDGE = 0; // 直线宽度 == title宽度 - 2 * mXOffset
// public static final int MODE_WRAP_CONTENT = 1; // 直线宽度 == title内容宽度 - 2 * mXOffset
// public static final int MODE_EXACTLY = 2; // 直线宽度 == mLineWidth
LinePagerIndicator indicator = new LinePagerIndicator(context);
indicator.setMode(LinePagerIndicator.MODE_WRAP_CONTENT);
return indicator;
}
});
magicIndicator.setNavigator(commonNavigator);
4.使用 ViewPager
ViewPagerHelper.bind(magicIndicator, mViewPager);
or work with Fragment Container(switch Fragment by hide()、show()):
mFramentContainerHelper = new FragmentContainerHelper(magicIndicator);
// ...
mFragmentContainerHelper.handlePageSelected(pageIndex); // invoke when switch Fragment
5. MagicIndicator 扩展
5.1 实现 IPagerTitleView 以自定义选项卡
public class MyPagerTitleView extends View implements IPagerTitleView {
public MyPagerTitleView(Context context) {
super(context);
}
@Override
public void onLeave(int index, int totalCount, float leavePercent, boolean leftToRight) {
}
@Override
public void onEnter(int index, int totalCount, float enterPercent, boolean leftToRight) {
}
@Override
public void onSelected(int index, int totalCount) {
}
@Override
public void onDeselected(int index, int totalCount) {
}
}
5.2实现 IPagerIndicator 自定义指标
public class MyPagerIndicator extends View implements IPagerIndicator {
public MyPagerIndicator(Context context) {
super(context);
}
@Override
public void onPageSelected(int position) {
}
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageScrollStateChanged(int state) {
}
@Override
public void onPositionDataProvide(List dataList) {
}
}
5.3使用 CommonPagerTitleView 加载自定义布局 xml
项目使用案列(kotlin)
字体选中加粗,指示器样式...
xml
....
....
kotlin:
private fun initPagerView() {
// FragmentPageResumeAdapter 3代表的是下面有三个fragment
val pagerAdapter = object : FragmentPageResumeAdapter(supportFragmentManager, 3) {
override fun getFragment(position: Int): Fragment {
return SocialCircleDetailsViewPagerFragment.newInstance(
id,
(position + 1).toString()
)
}
}
// 绑定pagerAdapter
binding.viewPager.adapter = pagerAdapter
// viewpager预加载
binding.viewPager.offscreenPageLimit = 3
val tabTitles = mutableListOf("综合", "最新", "最热")
// CommonNavigator:通用的ViewPager指示器,包含PagerTitle和PagerIndicator
// CircleNavigator:圆圈式的指示器
val commonNavigator = CommonNavigator(this)
commonNavigator.isAdjustMode = false
commonNavigator.adapter = object : CommonNavigatorAdapter() {
//指示器标题设置
override fun getTitleView(context: Context?, index: Int): IPagerTitleView {
/**
* ClipPagerTitleView: 类似今日头条切换效果的指示器标题
* ColorTransitionPagerTitleView:两种颜色过渡的指示器标题
* CommonPagerTitleView:通用的指示器标题,子元素内容由外部提供,事件回传给外部
* DummyPagerTitleView:空指示器标题,用于只需要指示器而不需要title的需求
* SimplePagerTitleView:带文本的指示器标题
*/
val titleView = object : SimplePagerTitleView(context) {
// 指示器标题选择状态设置
override fun onSelected(index: Int, totalCount: Int) {
super.onSelected(index, totalCount)
setTypeface(Typeface.DEFAULT, Typeface.BOLD) // 选中的话,字体加粗
}
override fun onDeselected(index: Int, totalCount: Int) {
super.onDeselected(index, totalCount)
setTypeface(Typeface.DEFAULT, Typeface.NORMAL) // 没选中的话,字体为默认样式
}
}
titleView.apply {
normalColor = getColor(R.color.color_66) // 指示器标题默认颜色
selectedColor = getColor(R.color.app_color) // 指示器标题选红颜色
textSize = 13f // 指示器标题字体大小
text = tabTitles[index] // 指示器标题文本
}
// 指示器标题点击事件
titleView.setOnClickListener {
binding.viewPager.currentItem = index
}
return titleView
}
// 数量
override fun getCount(): Int = tabTitles.size
// 指示器样式
override fun getIndicator(context: Context?): IPagerIndicator {
return LinePagerIndicator(context).apply {
/*
* LinePagerIndicator:直线viewpager指示器,带颜色渐变
* BezierPagerIndicator:贝塞尔曲线ViewPager指示器,带颜色渐变
* TestPagerIndicator:用于测试的指示器,可用来检测自定义的IMeasurablePagerTitleView是否正确测量内容区域
* TriangularPagerIndicator:带有小尖角的直线指示器
* WrapPagerIndicator:包裹住内容区域的指示器,类似天天快报的切换效果,需要和IMeasurablePagerTitleView配合使用
*/
mode = LinePagerIndicator.MODE_EXACTLY // 直线宽度 == mLineWidth
lineHeight = this.resources.getDimension(R.dimen.qb_px_1) // 指示器高度
lineWidth = this.resources.getDimension(R.dimen.qb_px_10) // 指示器宽度
roundRadius = UIUtil.dip2px(context, 1.0).toFloat() // 指示器圆角
// 设置指示器开始和结束动画效果
startInterpolator = AccelerateInterpolator()
endInterpolator = DecelerateInterpolator(2.0f)
yOffset = resources.getDimension(R.dimen.qb_px_7) // 指示器相对于底部的偏移量
setColors(getColor(this,R.color.color_FFAA00 ) ) // 指示器颜色
}
}
}
binding.magicIndicator.navigator = commonNavigator
ViewPagerHelper.bind(binding.magicIndicator, binding.viewPager)
}
2.指示器文本添加图片
val pagerAdapter = object : FragmentPageResumeAdapter(supportFragmentManager, 2) {
override fun getFragment(position: Int): Fragment {
return when (position) {
0 -> UserDynamicFragment.newInstance(userId)
else -> UserCollectionFragment.newInstance(userId, data?.collectSecStatus)
}
}
}
binding.viewPager.adapter = pagerAdapter
// title 列表
val tabTitles = listOf("动态${data?.momentsCount ?: "0"}", "收藏${data?.collectCount ?: "0"}")
val commonNavigator = CommonNavigator(this)
commonNavigator.isAdjustMode = true //自适应模式,适用于数目固定的、少量的title
commonNavigator.adapter = object : CommonNavigatorAdapter() {
@SuppressLint("InflateParams")
override fun getTitleView(context: Context?, index: Int): IPagerTitleView {
val titleView = ColorTransitionPagerTitleView(this).apply {
normalColor = getColor(this, R.color.color_666666)
selectedColor = getColor(this, R.color.color_FFAA00)
textSize = 14f
text = tabTitles[index]
setOnClickListener {
binding.viewPager.currentItem = index
}
}
// 核心代码:设置图片
val badgePagerTitleView = BadgePagerTitleView(this)
badgePagerTitleView.innerPagerTitleView = titleView
if (index == 1) {
// 设置加载图片
val badgeImageView = LayoutInflater.from(this)
.inflate(R.layout.view_collect_lock_view, null) as ImageView
badgePagerTitleView.badgeView = badgeImageView
// 设置X轴
badgePagerTitleView.xBadgeRule = BadgeRule(
BadgeAnchor.CONTENT_RIGHT, //角标的锚点
DensityUtil.dip2px(5f)
)
// 设置Y轴
badgePagerTitleView.yBadgeRule = BadgeRule(
BadgeAnchor.CONTENT_TOP, //角标的锚点
DensityUtil.dip2px(5f)
)
}
badgePagerTitleView.isAutoCancelBadge = false
return badgePagerTitleView
}
override fun getCount(): Int = tabTitles.size
override fun getIndicator(context: Context?): IPagerIndicator {
return LinePagerIndicator(context).apply {
setColors(getColor(this,R.color.color_FFBB33) )
}
}
}
binding.magicIndicator.navigator = commonNavigator
ViewPagerHelper.bind(binding.magicIndicator, binding.viewPager)
}
3.设置自定义指示器图片
private void initMagicIndicator4() {
MagicIndicator magicIndicator = (MagicIndicator) findViewById(R.id.magic_indicator4);
CommonNavigator commonNavigator = new CommonNavigator(this);
commonNavigator.setAdapter(new CommonNavigatorAdapter() {
@Override
public int getCount() {
return mDataList.size();
}
@Override
public IPagerTitleView getTitleView(Context context, final int index) {
SimplePagerTitleView simplePagerTitleView = new ColorTransitionPagerTitleView(context);
simplePagerTitleView.setNormalColor(Color.GRAY);
simplePagerTitleView.setSelectedColor(Color.WHITE);
simplePagerTitleView.setText(mDataList.get(index));
simplePagerTitleView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mViewPager.setCurrentItem(index);
}
});
return simplePagerTitleView;
}
@SuppressLint("UseCompatLoadingForDrawables")
@Override
public IPagerIndicator getIndicator(Context context) {
CommonPagerIndicator indicator = new CommonPagerIndicator(context);
indicator.setDrawableHeight(UIUtil.dip2px(context,6));
indicator.setDrawableWidth(UIUtil.dip2px(context,15));
indicator.setIndicatorDrawable(getResources().getDrawable(R.drawable.icon_indicator));
indicator.setMode(LinePagerIndicator.MODE_EXACTLY);
indicator.setStartInterpolator(new AccelerateInterpolator());
indicator.setEndInterpolator(new DecelerateInterpolator(1.6f));
indicator.setYOffset(UIUtil.dip2px(context,5));
return indicator;
}
});
magicIndicator.setNavigator(commonNavigator);
LinearLayout titleContainer = commonNavigator.getTitleContainer(); // must after setNavigator
titleContainer.setShowDividers(LinearLayout.SHOW_DIVIDER_MIDDLE);
titleContainer.setDividerDrawable(new ColorDrawable() {
@Override
public int getIntrinsicWidth() {
return UIUtil.dip2px(FixedTabExampleActivity.this, 15);
}
});
final FragmentContainerHelper fragmentContainerHelper = new FragmentContainerHelper(magicIndicator);
fragmentContainerHelper.setInterpolator(new OvershootInterpolator(2.0f));
fragmentContainerHelper.setDuration(300);
mViewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
@Override
public void onPageSelected(int position) {
fragmentContainerHelper.handlePageSelected(position);
}
});
}
最后,补充
1.通用的indicator,支持外面设置Drawable
public class CommonPagerIndicator extends View implements IPagerIndicator {
public static final int MODE_MATCH_EDGE = 0; // drawable宽度 == title宽度 - 2 * mXOffset
public static final int MODE_WRAP_CONTENT = 1; // drawable宽度 == title内容宽度 - 2 * mXOffset
public static final int MODE_EXACTLY = 2;
private int mMode; // 默认为MODE_MATCH_EDGE模式
private Drawable mIndicatorDrawable;
// 控制动画
private Interpolator mStartInterpolator = new LinearInterpolator();
private Interpolator mEndInterpolator = new LinearInterpolator();
private float mDrawableHeight;
private float mDrawableWidth;
private float mYOffset;
private float mXOffset;
private List mPositionDataList;
private Rect mDrawableRect = new Rect();
public CommonPagerIndicator(Context context) {
super(context);
}
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
if (mIndicatorDrawable == null) {
return;
}
if (mPositionDataList == null || mPositionDataList.isEmpty()) {
return;
}
// 计算锚点位置
PositionData current = FragmentContainerHelper.getImitativePositionData(mPositionDataList, position);
PositionData next = FragmentContainerHelper.getImitativePositionData(mPositionDataList, position + 1);
float leftX;
float nextLeftX;
float rightX;
float nextRightX;
if (mMode == MODE_MATCH_EDGE) {
leftX = current.mLeft + mXOffset;
nextLeftX = next.mLeft + mXOffset;
rightX = current.mRight - mXOffset;
nextRightX = next.mRight - mXOffset;
mDrawableRect.top = (int) mYOffset;
mDrawableRect.bottom = (int) (getHeight() - mYOffset);
} else if (mMode == MODE_WRAP_CONTENT) {
leftX = current.mContentLeft + mXOffset;
nextLeftX = next.mContentLeft + mXOffset;
rightX = current.mContentRight - mXOffset;
nextRightX = next.mContentRight - mXOffset;
mDrawableRect.top = (int) (current.mContentTop - mYOffset);
mDrawableRect.bottom = (int) (current.mContentBottom + mYOffset);
} else { // MODE_EXACTLY
leftX = current.mLeft + (current.width() - mDrawableWidth) / 2;
nextLeftX = next.mLeft + (next.width() - mDrawableWidth) / 2;
rightX = current.mLeft + (current.width() + mDrawableWidth) / 2;
nextRightX = next.mLeft + (next.width() + mDrawableWidth) / 2;
mDrawableRect.top = (int) (getHeight() - mDrawableHeight - mYOffset);
mDrawableRect.bottom = (int) (getHeight() - mYOffset);
}
mDrawableRect.left = (int) (leftX + (nextLeftX - leftX) * mStartInterpolator.getInterpolation(positionOffset));
mDrawableRect.right = (int) (rightX + (nextRightX - rightX) * mEndInterpolator.getInterpolation(positionOffset));
mIndicatorDrawable.setBounds(mDrawableRect);
invalidate();
}
@Override
public void onPageSelected(int position) {
}
@Override
public void onPageScrollStateChanged(int state) {
}
@Override
protected void onDraw(Canvas canvas) {
if (mIndicatorDrawable != null) {
mIndicatorDrawable.draw(canvas);
}
}
@Override
public void onPositionDataProvide(List dataList) {
mPositionDataList = dataList;
}
public Drawable getIndicatorDrawable() {
return mIndicatorDrawable;
}
public void setIndicatorDrawable(Drawable indicatorDrawable) {
mIndicatorDrawable = indicatorDrawable;
}
public Interpolator getStartInterpolator() {
return mStartInterpolator;
}
public void setStartInterpolator(Interpolator startInterpolator) {
mStartInterpolator = startInterpolator;
}
public Interpolator getEndInterpolator() {
return mEndInterpolator;
}
public void setEndInterpolator(Interpolator endInterpolator) {
mEndInterpolator = endInterpolator;
}
public int getMode() {
return mMode;
}
public void setMode(int mode) {
if (mode == MODE_EXACTLY || mode == MODE_MATCH_EDGE || mode == MODE_WRAP_CONTENT) {
mMode = mode;
} else {
throw new IllegalArgumentException("mode " + mode + " not supported.");
}
}
public float getDrawableHeight() {
return mDrawableHeight;
}
public void setDrawableHeight(float drawableHeight) {
mDrawableHeight = drawableHeight;
}
public float getDrawableWidth() {
return mDrawableWidth;
}
public void setDrawableWidth(float drawableWidth) {
mDrawableWidth = drawableWidth;
}
public float getYOffset() {
return mYOffset;
}
public void setYOffset(float yOffset) {
mYOffset = yOffset;
}
public float getXOffset() {
return mXOffset;
}
public void setXOffset(float xOffset) {
mXOffset = xOffset;
}
}
2 非手指跟随的小圆点指示器
public class DotPagerIndicator extends View implements IPagerIndicator {
private List mDataList;
private float mRadius;
private float mYOffset;
private int mDotColor;
private float mCircleCenterX;
private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
public DotPagerIndicator(Context context) {
super(context);
mRadius = UIUtil.dip2px(context, 3);
mYOffset = UIUtil.dip2px(context, 3);
mDotColor = Color.WHITE;
}
@Override
public void onPageSelected(int position) {
if (mDataList == null || mDataList.isEmpty()) {
return;
}
PositionData data = mDataList.get(position);
mCircleCenterX = data.mLeft + data.width() / 2;
invalidate();
}
@Override
public void onPositionDataProvide(List dataList) {
mDataList = dataList;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPaint.setColor(mDotColor);
canvas.drawCircle(mCircleCenterX, getHeight() - mYOffset - mRadius, mRadius, mPaint);
}
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageScrollStateChanged(int state) {
}
public float getRadius() {
return mRadius;
}
public void setRadius(float radius) {
mRadius = radius;
invalidate();
}
public float getYOffset() {
return mYOffset;
}
public void setYOffset(float yOffset) {
mYOffset = yOffset;
invalidate();
}
public int getDotColor() {
return mDotColor;
}
public void setDotColor(int dotColor) {
mDotColor = dotColor;
invalidate();
}
}