自定义TabLayout,实现神奇的选项卡效果

来源:互联网 发布:矩阵的模的计算公式 编辑:程序博客网 时间:2024/05/18 01:10



今日科技快讯


据外媒报道,在成为人类史上首位机器人公民1个月后,索菲娅最近宣布想要组建一个家庭。这款人形机器人是以奥黛丽·赫本为模本开发的,她在不久前接受采访时说,组建家庭是“非常重要的事情”。她补充说,如果她有个机器人女儿,她会以自己的名字为她取名,她相信每个机器人都应该有自己的家庭。


作者简介


本篇来自 披萨大叔 的投稿,主要讲解了如何开发一个自定义图片指示器并支持自定义Tab宽度的TabLayout,希望大家能够喜欢。

披萨大叔 的博客地址:

http://blog.csdn.net/qq_27258799


概述


Tab栏是APP中最常用的组件,如果我们没有太多个性化需求,直接用TabLayout就可以轻松实现。

但是如果我们的产品大大说不想用一条线作指示器了,想用个图片。或者像我们产品一样,想在主页展示4个半Tab(一共5个)时,原生TabLayout就不能满足了。

而前面我发过一篇,自定义三角下标的tablayout,来实现类似于今日头条tab效果,同时还有三角下标。

那时候我是继承HorizontalScrollView,但是滑动过程中有一些bug,因为要自己处理“什么时候该滑动”和“滑动多少”,后来在项目中逻辑太复杂,就想重新推翻重写一个。

当时也想过用TabLayout,但是TabLayout的滑动指示器是矩形,项目中要使用自定义图片,所以最开始就没继续思考,直接用了HorizontalScrollView。

现在回头想想,我真是傻,我直接继承TabLayout,把图片指示器画上去不就OK了,而且绘制图片指示器的逻辑跟自定义三角下标的tablayout一文里的逻辑一样!文末会贴上工程源码。


正文


先看看这次的整体效果:

这里再简单说一下绘制三角下标的原理。

自定义图片指示器

  • 初始化指示器坐标:

 private void initTranslationParams(LinearLayout llTab, int screenWidth) {         //mSlideIcon 是图片指示器         if (mSlideIcon == null) {             return;         }         tabWidth = (int) (screenWidth / (mTabVisibleCount + mLastTabVisibleRatio));         View firstView = llTab.getChildAt(0);         if (firstView != null) {             //初始位置:第一个tab正下方             this.mInitTranslationX = (firstView.getLeft() + tabWidth / 2 - this.mSlideIcon.getWidth() / 2);             this.mInitTranslationY = (getBottom() - getTop() - this.mSlideIcon.getHeight());         }     }
  • 监听ViewPager的滑动:

 viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {             @Override             public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {                 //传入当前滑动位置,重绘指示器                 topTabLayout.redrawIndicator(position, positionOffset);             }             @Override             public void onPageSelected(int position) {             }             @Override             public void onPageScrollStateChanged(int state) {             }         });
  • 重新计算指示器位置:

public void redrawIndicator(int position, float positionOffset) {    mTranslationX = (int) ((position + positionOffset) * tabWidth);    invalidate();  }
  • 绘制指示器

  @Override   protected void dispatchDraw(Canvas canvas) {        if (mSlideIcon == null) {            return;        }        canvas.save();        // 平移到正确的位置,修正tabs的平移量        canvas.translate(mInitTranslationX + mTranslationX, this.mInitTranslationY);        canvas.drawBitmap(this.mSlideIcon, 0, 0, null);        canvas.restore();        super.dispatchDraw(canvas);    }

自定义Tab宽度

本文主要想记录一下自定义Tab宽度,因为我们项目中,顶部tab栏,有5个Tab,但是最后一个只露出半个(想打死产品有木有)。

我们看下TabLayout的源码,没有任何设置Tab宽度的接口,只能在XML中通过

 app:tabMaxWidth=""  app:tabMinWidth=""

来锁定Tab宽度,然后解析出来:

mRequestedTabMinWidth = a.getDimensionPixelSize(R.styleable.TabLayout_tabMinWidth,                 INVALID_WIDTH); mRequestedTabMaxWidth = a.getDimensionPixelSize(R.styleable.TabLayout_tabMaxWidth,                 INVALID_WIDTH);

但是我们这里需要结合手机屏幕宽度和其他业务因素动态的计算出Tab的宽度,在代码里设置。怎么办?

我首先想到利用反射直接修改这两个值,而mRequestedTabMinWidth和mRequestedTabMinWidth又是final的,但是!

但是请注意,这两个变量都不是在定义时初始化的,所以,如果使用反射是可以在代码里设置这两个值的。这里多提一句:

  • 如果final变量在定义时就已经初始化了,那么反射是无法修改值的

  • 如果final变量在定义时没有初始化,是可以用反射修改值的

所以方案1来了。

方案1:反射修改Tab宽度

 final Class<?> clz = TabLayout.class;     try {         final Field requestedTabMaxWidthField = clz.getDeclaredField("mRequestedTabMaxWidth");         final Field requestedTabMinWidthField = clz.getDeclaredField("mRequestedTabMinWidth");         requestedTabMaxWidthField.setAccessible(true);         requestedTabMaxWidthField.set(this, 宽度);         requestedTabMinWidthField.setAccessible(true);         requestedTabMinWidthField.set(this, 宽度);     } catch (final NoSuchFieldException e) {         e.printStackTrace();     } catch (final SecurityException e) {         e.printStackTrace();     } catch (final IllegalArgumentException e) {         e.printStackTrace();     } catch (final IllegalAccessException e) {         e.printStackTrace();     }

这样就可以自定义Tab的宽度了。

下面是方案2,不使用反射,使用异步方法。

方案2:异步修改Tab宽度

当Tab绘制完成以后,我们重新设置TabView的参数,重绘一次,也可以修改Tab的宽度。

public SlidingTabLayout(Context context, AttributeSet attrs) {     super(context, attrs);     post(new Runnable() {         @Override         public void run() {             resetTabParams();         }     }); } private void resetTabParams() {      LinearLayout tabStrip = getTabStrip();          if (tabStrip == null) {              return;          }          for (int i = 0; i < tabStrip.getChildCount(); i++) {              LinearLayout tabView = (LinearLayout) tabStrip.getChildAt(i);              LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(宽度, LinearLayout.LayoutParams                      .WRAP_CONTENT);              tabView.setLayoutParams(params);      } } //反射拿到TabLayout里的mTabStrip(mTabStrip是TabView的父容器) @Nullable public LinearLayout getTabStrip() {     Class<?> tabLayout = TabLayout.class;     Field tabStrip = null;     try {         tabStrip = tabLayout.getDeclaredField("mTabStrip");     } catch (NoSuchFieldException e) {         e.printStackTrace();     }     tabStrip.setAccessible(true);     LinearLayout llTab = null;     try {         llTab = (LinearLayout) tabStrip.get(this);     } catch (IllegalAccessException e) {         e.printStackTrace();     }     llTab.setClipChildren(false);     return llTab; }

如此我们便可以自定义Tab的宽度啦~

最后一个问题

最后我还遇到一个问题,我们原来的Tab栏上有飘出的气泡,多余部分不能被截断,因此需要设置:

setClipChildren(false); setClipToPadding(false);

那在TabLayout中要给谁设置呢?

这里我踩了几个坑,我们先来看下TabLayout的结构:

其中mCustomView是我们自定义的Tab布局,Tab类是对TabView和mCustomView的包装。很显然,我们需要对TabView设置布局参数,所以方案来了:

LinearLayout tabStrip = getTabStrip();   if (tabStrip == null) {        return;    }    for (int i = 0; i < tabStrip.getChildCount(); i++) {        LinearLayout tabView = (LinearLayout) tabStrip.getChildAt(i);        //tab中的图标可以超出父容器        tabView.setClipChildren(false);        tabView.setClipToPadding(false);        tabView.setPadding(0, 30, 0, 30);     }

getTabStrip()方法上面贴过。

这样,我们就可以做出下面类似的气泡效果:

项目地址

http://blog.csdn.net/qq_27258799/article/details/78413926

github项目地址:

https://github.com/unclepizza/TabLayoutDemo


结语


到此为止,本文的内容已经讲完了。觉得不错的同学可以关注下作者的博客,当然对本篇文章的内容有疑问,也可以到作者的博客中去咨询。


欢迎长按下图 -> 识别图中二维码

或者 扫一扫 关注我的公众号

阅读全文
0 0
原创粉丝点击