前言

TabLayout 我相信大家都有使用过,但是官方并没有对Indicator可以设置为图片的支持,只能是简单的水平线,也就是滑动杆那个玩意,那么如何做到可以用图片来做指示器呢?作为一个喜欢探索新颖解决方案的我,现在给大家带来了大家都很期待的问题的解决方案,下面我来讲讲我的思路和解决方案

先上效果

这是我将要做到的事情,实现TabLayout的Indicator可自定义image

效果图

分析源码

既然TabLayout 能够和ViewPager手势联动那就说明Tablayout和ViewPager有关联,先来看一下他们是如何关联的

tab_layout.setupWithViewPager(viewpager)

通过这个方法Tablayout和ViewPager相遇了,继续跟进这个方法执行最终会执行到这里

private void setupWithViewPager(@Nullable final ViewPager viewPager, boolean autoRefresh,

boolean implicitSetup) {

...

if (viewPager != null) {

//保存一个成员变量引用

mViewPager = viewPager;

// Add our custom OnPageChangeListener to the ViewPager

if (mPageChangeListener == null) {

mPageChangeListener = new TabLayoutOnPageChangeListener(this);

}

mPageChangeListener.reset();

//关注点1

viewPager.addOnPageChangeListener(mPageChangeListener);

//关注点2

// Now we'll add a tab selected listener to set ViewPager's current item

//注释很明显翻译过来就是添加一个选项选择监听器来设置ViewPager的当前项

mCurrentVpSelectedListener = new ViewPagerOnTabSelectedListener(viewPager);

addOnTabSelectedListener(mCurrentVpSelectedListener);

}

...

}

关注点1添加监听ViewPager的滑动事件,关注点2添加Tablayout选择事件来设置ViewPager的当前项,关注点2注释已经解释得很清楚,重点分析关注点1ViewPager滑动事件,我们lou一眼

public static class TabLayoutOnPageChangeListener implements ViewPager.OnPageChangeListener {

//Viewpager滑动偏移量回调

@Override

public void onPageScrolled(final int position, final float positionOffset,

final int positionOffsetPixels) {

final TabLayout tabLayout = mTabLayoutRef.get();

if (tabLayout != null) {

// Only update the text selection if we're not settling, or we are settling after

// being dragged

final boolean updateText = mScrollState != SCROLL_STATE_SETTLING ||

mPreviousScrollState == SCROLL_STATE_DRAGGING;

// Update the indicator if we're not settling after being idle. This is caused

// from a setCurrentItem() call and will be handled by an animation from

// onPageSelected() instead.

final boolean updateIndicator = !(mScrollState == SCROLL_STATE_SETTLING

&& mPreviousScrollState == SCROLL_STATE_IDLE);

//将ViewPager的滑动信息设置给tablayout,这是一个关键的信息!!!

tabLayout.setScrollPosition(position, positionOffset, updateText, updateIndicator);

}

}

}

...

//在这个方法里面调用了SlidingTabStrip的setIndicatorPositionFromTabPosition方法获取滑动数据

void setScrollPosition(int position, float positionOffset, boolean updateSelectedText,

boolean updateIndicatorPosition) {

···

// Set the indicator position, if enabled

if (updateIndicatorPosition) {

//设置Indicator的位置

mTabStrip.setIndicatorPositionFromTabPosition(position, positionOffset);

}

···

}

//用变量保存参数并执行 updateIndicatorPosition方法

void setIndicatorPositionFromTabPosition(int position, float positionOffset) {

if (mIndicatorAnimator != null && mIndicatorAnimator.isRunning()) {

mIndicatorAnimator.cancel();

}

mSelectedPosition = position;

mSelectionOffset = positionOffset;

//更新Indicator位置

updateIndicatorPosition();

}

//通过viewpager的滑动偏移量计算选项卡左边和右边的位置

private void updateIndicatorPosition() {

final View selectedTitle = getChildAt(mSelectedPosition);

int left, right;

if (selectedTitle != null && selectedTitle.getWidth() > 0) {

left = selectedTitle.getLeft();

right = selectedTitle.getRight();

if (mSelectionOffset > 0f && mSelectedPosition < getChildCount() - 1) {

// Draw the selection partway between the tabs

View nextTitle = getChildAt(mSelectedPosition + 1);

left = (int) (mSelectionOffset * nextTitle.getLeft() +

(1.0f - mSelectionOffset) * left);

right = (int) (mSelectionOffset * nextTitle.getRight() +

(1.0f - mSelectionOffset) * right);

}

} else {

left = right = -1;

}

setIndicatorPosition(left, right);

}

void setIndicatorPosition(int left, int right) {

if (left != mIndicatorLeft || right != mIndicatorRight) {

// If the indicator's left/right has changed, invalidate

mIndicatorLeft = left;

mIndicatorRight = right;

//执行这个方法最终会执行view.postInvalidate()触发view的重绘

ViewCompat.postInvalidateOnAnimation(this);

}

}

@Override

public void draw(Canvas canvas) {

super.draw(canvas);

// Thick colored underline below the current selection

// 根据前面计算过后的位置信息绘制indicator滑动杆

if (mIndicatorLeft >= 0 && mIndicatorRight > mIndicatorLeft) {

canvas.drawRect(mIndicatorLeft, getHeight() - mSelectedIndicatorHeight,

mIndicatorRight, getHeight(), mSelectedIndicatorPaint);

}

}

看到这里那么恭喜你Tablayout的Indicator和ViewPager联动的流程你已经掌握了。我来总结一下流程

1.通过调用setupWithViewPager为ViewPager添加滑动监听

2.并在监听回调里面把数据传递给Tablayout的内部类SlidingTabStrip就是指示器

3.SlidingTabStrip接收到数据之后调用 ViewCompat.postInvalidateOnAnimation(this);这个方法将会执行view.postInvalidate()触发view的重绘

实现想法

自己造个轮子实现将Tablayout的Indecator换成图片,说干就干

遇到问题

问题来了,Tablayout的的滑动杆是在SlidingTabStrip这个类中的draw方法里面进行的绘制的,在外部不能对其进行修改,于是我就想既然不能动你,那我就干掉你,另辟蹊径找其他方法代替你,所以我决定自己写个Indecator类去监听viewpager的滑动事件,并更新显示位置

解决思路

从源码分析中我们已经知道要更新Indecator的位置就是要拿到viewPager的滑动偏移量,计算出他的左右位置然后通知view重绘,那我也自己写个Indecator来做同样的事代码如下

/**

* Created by xwc on 2018/6/6

*

*/

public class ImageIndicator extends View {

public ImageIndicator(Context context) {

super(context);

}

public ImageIndicator(Context context, @Nullable AttributeSet attrs) {

super(context, attrs);

}

public ImageIndicator(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {

super(context, attrs, defStyleAttr);

}

private float mIndicatorLeft = -1;

private int mIndicatorRight = -1;

//滑动的图片

private Bitmap mBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.progressbar);

private Paint mSelectedIndicatorPaint = new Paint();

int mSelectedPosition = -1;

float mSelectionOffset;

LinearLayout tab;

TabLayout tabs;

public void setIndicatorPositionFromTabPosition(int position, float positionOffset) {

mSelectedPosition = position;

mSelectionOffset = positionOffset;

updateIndicatorPosition();

}

public void setupWithTabLayout(TabLayout tabs) {

this.tabs = tabs;

if (tab == null) {

tab = getTabStrip();

}

}

//计算滑动杆位置

private void updateIndicatorPosition() {

if (tab == null) {

return;

}

final View selectedTitle = tab.getChildAt(mSelectedPosition);

int left, right;

if (selectedTitle != null && selectedTitle.getWidth() > 0) {

left = selectedTitle.getLeft();

right = selectedTitle.getRight();

if (mSelectionOffset > 0f && mSelectedPosition < tab.getChildCount() - 1) {

View nextTitle = tab.getChildAt(mSelectedPosition + 1);

left = (int) (mSelectionOffset * nextTitle.getLeft() +

(1.0f - mSelectionOffset) * left);

right = (int) (mSelectionOffset * nextTitle.getRight() +

(1.0f - mSelectionOffset) * right);

}

} else {

left = right = -1;

}

setIndicatorPosition(left, right);

}

void setIndicatorPosition(int left, int right) {

if (left != mIndicatorLeft || right != mIndicatorRight) {

mIndicatorLeft = left;

mIndicatorRight = right;

//通知view重绘

ViewCompat.postInvalidateOnAnimation(this);

}

}

@Override

protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

//绘制图片

if (mIndicatorLeft >= 0 && mIndicatorRight > mIndicatorLeft) {

canvas.drawBitmap(mBitmap, mIndicatorLeft, getHeight() - mBitmap.getHeight(), mSelectedIndicatorPaint);

}

}

/**

* tabs : TabLayout

* 通过反射TabLayout拿到SlidingTabStrip这个内部类

*/

public LinearLayout getTabStrip() {

Class> tabLayout = tabs.getClass();

Field tabStrip = null;

try {

tabStrip = tabLayout.getDeclaredField("mTabStrip");

} catch (NoSuchFieldException e) {

e.printStackTrace();

}

tabStrip.setAccessible(true);

try {

tab = (LinearLayout) tabStrip.get(tabs);

return tab;

} catch (Exception e) {

e.printStackTrace();

}

return tab;

}

}

使用:

viewPager.setAdapter(new MyViewPagerAdapter());

tabLayout.setupWithViewPager(viewPager);

imageIndicator.setupWithTabLayout(tabLayout);

viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {

@Override

public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

// 将滑动偏移量传给Indicator

imageIndicator.setIndicatorPositionFromTabPosition(position, positionOffset);

}

@Override

public void onPageSelected(int position) {

}

@Override

public void onPageScrollStateChanged(int state) {

}

});

番外

如果你反射了Tablayout,想要改变他属性或者他的内部类属性,请在项目发布时加上混淆,避免出现反射无效!

-keep public class android.support.design.widget.TabLayout{

*;

}

-keep class android.support.design.widget.TabLayout$* {

*;

}

总结

借鉴老罗的一句话我们在项目中碰到问题的时候,通常第一反应都是到网上去搜索答案。但是有时候有些问题,网络并不能给出满意的答案。这时候就千万不要忘了你所拥有的一个大招——从代码中找答案!以上就是我的解决方案,查看源码从中获得灵感,解决实际问题!坚决不做码农,我是工程师

具体使用请查看 Demo

欢迎评论点赞交流

android tablayout放图片,Android TabLayout的Indicator如何设置为图片相关推荐

  1. html怎么写入图片位置,如何在css中设置插入图片定位

    在做网页开发时,客户给的素材图片通常都是连在一起的,这样做也是为了缩短响应时间.下面就给大家具体说一下 ,如何通过css属性来定位图片. 如何在css中设置插入图片定位 首先设置固定图片的css属性是 ...

  2. Android 天气APP(二十九)壁纸设置、图片查看、图片保存

    上一篇:Android 天气APP(二十八)地图搜索定位 效果图 开发流程 一.前情提要 二.正式开发 1. 列表数据填充 2. 浮动按钮的交互 3. 其他优化 4. 运行效果图 三.文末 一.前情提 ...

  3. html 背景图片不重复显示,css如何设置背景图片不重复?

    css设置背景图片,默认的样式是图片重复,有的时候页面只需要一个不重复的背景图片,这就需要使用到css背景图片重复属性background-repeat: no-repeat;进行设置. css使用b ...

  4. 如何给html文件加背景图片,怎么在文件夹中设置背景图片

    在文件夹中设置美丽的背景图片,可以让你的电脑显得与众不同,下面是学习啦小编给大家整理的一些有关在文件夹中设置背景图片的方法,希望对大家有帮助! 在文件夹中设置背景图片的方法 打开你要设置背景的文件夹, ...

  5. php图片背景平铺,css如何设置背景图片的平铺方式?css设置背景图片平铺的方法(图文详解)...

    css如何设置背景图片的平铺方式?本文就给大家介绍css是如何设置背景图片平铺的方法,让大家了解在css中设置背景图片水平方法平铺.垂直方向平铺,或者是不平铺的方法.有一定的参考价值,有需要的朋友可以 ...

  6. android 按钮放中间,Android实现button居中的方法

    本文实例讲述了Android实现button居中的方法.分享给大家供大家参考.具体如下: 通过在main.xml 或者其他xml 布局文件中布局Button的时候,选择Android:gravity= ...

  7. unity3dui中图片加不上_Unity3d中设置UISprite图片灰显方法

    做Unity开发过程中,有需求让未开启的功能模块的入口灰色显示.因为没有滤镜的概念,所以没flash那么方便.解决思路还是Shader,以前按网上其他帖子的方法,一直没有成功实现过.这两天比较闲一点, ...

  8. android picasso 三级缓存,Android中图片的三级缓存浅析

    图片的三级缓存机制一般是指应用加载图片的时候,分别去访问内存,文件和网络而获取图片数据的一种行为.以下内容只是简单的介绍了三级缓存的思想和大致流程,还有很多细节未进行处理,如果想深入研究可以在Gith ...

  9. .net的label的背景如何设置成为透明_css如何设置背景图片?background属性添加背景图片...

    在前端开发过程中,为了页面的美观,往往都会给html页面添加背景图片.那么如何利用css设置html中用图片做背景?本章就给大家介绍css怎样设置背景图片.有一定的参考价值,有需要的朋友可以参考一下, ...

  10. html背景图片只显示一张图片,img只显示图片一部分 或 css设置背景图片只显示图片指定区域(示例代码)...

    17:14 2016/3/22 img只显示图片一部分 或 css设置背景图片只显示图片指定区域 background-position: 100% 56%; 设置背景图片显示图片的哪个坐标区域,图片 ...

最新文章

  1. Nature子刊:王四宝组揭示按蚊肠道共生菌抗疟的分子机制
  2. Servlet-监听器(ServletContext、Request、Session)
  3. linux apache守护进程,Linux基础命令---httpd守护进程
  4. PAT甲级1151 LCA in a Binary Tree (30 分):[C++题解]LCA、最低公共祖先、哈希表映射
  5. baidumap vue 判断范围_懂一点前端—Vue快速入门
  6. PHP 开启或关闭错误提示
  7. Flutter报错 Navigator operation requested with a context that does not include a Navigator.
  8. Android之多种Bitmap效果
  9. js扁平数组对象转成树结构
  10. 我们为什么需要实施实验室管理系统?
  11. 《Adobe Photoshop CS4中文版经典教程》目录—导读
  12. iphone 操作手势种类
  13. Linux下安装docker与kubernetes(k8s)
  14. uniapp之APP开发
  15. 界面布局上大胆突破,关注到的细分领域
  16. java 实现输出水仙花数
  17. hasnext() java_关于Java:为什么HasNeXT()false,但hasNextLine() 是真的?
  18. intellij idea weblogic 下面 怎么远程断点
  19. MFC_改变窗口大小,随着鼠标的拖动改变
  20. auto-drawing

热门文章

  1. win11键盘失灵 windows11键盘失控 键盘没反应
  2. 2021年山东省安全员C证考试题库及山东省安全员C证考试报名
  3. PDF Expert for Mac最新2020注册码激活版下载
  4. 微信公众号代运营的的技巧有哪些(6)
  5. 苹果手机换电池对手机有影响吗_电池寿命真的影响手机性能~iPhone手机更换电池后性能对比...
  6. 苹果x电池多少毫安_苹果x掉电快,苹果x换电池多少钱
  7. linux 的压缩文件格式,常见压缩文件的格式及linux中打包或解压的方法
  8. Python(2)——根据英文字符输出中文星期
  9. android 实现果冻动画效果,Android高仿path小球刷新效果,类似IOS果冻效果
  10. 服务器如何装系统和数据库,中软系统服务器及数据库安装规范(含维护).pdf