仿陌陌选项卡:文字大小变化的SlidingScaleTabLayout

前言

不知不觉博客又一个月没有更新了。最近在看flutter,本来想写一篇flutter相关的内容,仔细想想又觉得内容太多,等年后回来再写一个系列吧。临近年底,在大家工作热情逐渐消退的气氛中,我们聊一点简单的。

最近公司发布了新版本的UI,其中一个效果是模仿陌陌的:


在这里插入图片描述

首先我们简单分析一下效果:

  • 被选中的Tab文字大,其他Tab文字小
  • 被选中的字体加粗

其他的效果就先忽略了,因为我一直使用的是FlycoTabLayout这个框架,动画效果已经满足了,唯一欠缺的就是这个文字大小切换的效果。

FlycoTabLayout框架github地址

正文

有了之前的分析,我们开始做准备工作:

效果1:随着ViewPager的滑动,文字大小也随之变化

这个效果非常简单,ViewPager自带Transform监听:

/**
 * Created by li.zhipeng on 2019/1/3.
 * <p>
 * tab文字大小切换的动画类
 */
public class TabScaleTransformer implements ViewPager.PageTransformer {

    private SlidingScaleTabLayout slidingScaleTabLayout;

    private PagerAdapter pagerAdapter;

    private List<IViewPagerTransformer> transformers = null;

    public TabScaleTransformer(SlidingScaleTabLayout slidingScaleTabLayout, PagerAdapter pagerAdapter) {
        this.slidingScaleTabLayout = slidingScaleTabLayout;
        this.pagerAdapter = pagerAdapter;
    }

    @Override
    public void transformPage(@NonNull View view, final float position) {
        // position的值区间[-1 ,1]
        // [-1, 0] 表示在左边的选项卡滑动到中心的比例
        // [0, 1] 表示在右边的选项卡滑动到中心的比例
    }

    public List<IViewPagerTransformer> getTransformers() {
        return transformers;
    }

    public void setTransformers(List<IViewPagerTransformer> transformers) {
        this.transformers = transformers;
    }
}
// 设置ViewPager的滑动动画
viewPager.setPageTransformer(true, new TabScaleTransformer())

position的含义已经在注释中说明了,因为在transformPage回调中我们得不到具体的位置信息,所以我们只能通过参数view和PagerAdapter,得到Tab的position。PagerAdapter已经有写好的方法供我们调用,那就是:

@NonNull
@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
        TextView textView = new TextView(SlidingScaleTabLayoutActivity.this);
        textView.setBackgroundColor(colors[position]);
        textView.setText(getPageTitle(position));
        // 设置位置Tag
        textView.setTag(position);
        container.addView(textView);
        return textView;
}
        
@Override
public int getItemPosition(@NonNull Object object) {
        View view = (View) object;
        // 获取位置tag
        return (int) view.getTag();
}

其中参数object是一个View类型的对象,就算我们使用的FragmentPagerAdapter,仍然是View类型(Fragment的布局),而不是Fragment。所以我的解决方案是,在添加的View中设置position的Tag,然后通过getItemPosition返回view的Tag即可。

最后我们还得弄清楚setPageTransformer方法的每个参数的作用:

setPageTransformer最多有三个参数:
...
boolean reverseDrawingOrder:
如果是true,绘制的顺序是从后向前,也就是前面的View会覆盖后面的View。如果是false,绘制的顺序会相反。
...
PageTransformer transformer:滑动的效果实现类
...
int pageLayerType:默认开启硬件加速,如果我们的View中包含了SurefaceView,并且没有设置setZOrderOnTop,但是SurfaceView仍然后显示在最上面,所以为了解决这个问题,需要设置View.LAYER_TYPE_NONE。

效果2:文字大小变化效果

说到大小的变化,第一反应肯定是使用Scale:

Scale最大的特点是不用重新测量View,相对于其他方式,效果更高效流畅。

但是很遗憾,这种方案最终还是失败了,直接把遇到的坑分享给大家:

  1. 因为Scale不会重新测量,导致Tab之间的间距不正确。
  2. 缩放需要设置缩放的中心点,通过transformPage方法无法知道当前哪一个是被选中的,除非再引用ViewPager,实现起来相对复杂。

最终还是采用了土办法,直接设置TextSize,肯定没跑了:

@Override
    public void transformPage(@NonNull View view, final float position) {
        final TextView currentTab = slidingScaleTabLayout.getTitle(pagerAdapter.getItemPosition(view));
        if (currentTab == null) {
            return;
        }
        // 必须要在View调用post更新样式,否则可能无效
        currentTab.post(new Runnable() {
            @Override
            public void run() {
                if (position >= -1 && position <= 1) { // [-1,1]
                    currentTab.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSelectSize - Math.abs((textSelectSize - textUnSelectSize) * position));
                } else {
                    currentTab.setTextSize(TypedValue.COMPLEX_UNIT_PX, textUnSelectSize);
                }
            }
        });
        // 回调设置的页面切换效果设置
        if (transformers != null && transformers.size() > 0) {
            for (IViewPagerTransformer transformer : transformers) {
                transformer.transformPage(view, position);
            }
        }
    }

以上就是实现文字大小变化的代码,其中我们调用了View.post设置选中的状态,是因为首次进入页面,我们设置显示的View位置不是0的话,会导致设置的文字大小没有变化,所以通过View.post方法,让View在测量结束后,再次设置文字大小。

然后因为我们占用了viewPager.setPageTransformer这个方法, 所以最好扩展一个API,供使用者设置滑动效果。最后就是改造我们的SlidingScaleTabLayout。

改造SlidingTabLayout

虽然我们可以通过之前的方式实现大部分的效果,但是还得修改SlidingTabLayout的代码,例如SlidingTabLayout中的文字是居中的,但是我们需要的是靠近底部的。所以不如在SlidingTabLayout的基础上,改造出我们的新类:SlidingScaleTabLayout。

1、新增自定义属性

<!-- 选中的文字大小 -->
<attr name="tl_textSelectSize" />
<!-- 未选中的文字大小 -->
<attr name="tl_textUnSelectSize" />
<!-- 上边距 -->
<attr name="tl_tab_marginTop" />
<!-- 下边距 -->
<attr name="tl_tab_marginBottom" />
<!-- tab的位置 -->
<attr name="tl_tab_gravity" format="enum">
     <enum name="Top" value="0" />
     <enum name="Bottom" value="1" />
     <enum name="Center" value="2" />
</attr>

通过代码获取自定义属性:

mTextUnSelectSize = ta.getDimension(R.styleable.SlidingScaleTabLayout_tl_textUnSelectSize, sp2px(14));
// 被选中的文字大小,默认额未选中的大小一样
mTextSelectSize = ta.getDimension(R.styleable.SlidingScaleTabLayout_tl_textSelectSize, mTextUnSelectSize);

// 得到设置的上下间距和gravity
mTabMarginTop = ta.getDimensionPixelSize(R.styleable.SlidingScaleTabLayout_tl_tab_marginTop, 0);
mTabMarginBottom = ta.getDimensionPixelSize(R.styleable.SlidingScaleTabLayout_tl_tab_marginBottom, 0);
mTabGravity = ta.getInt(R.styleable.SlidingScaleTabLayout_tl_tab_gravity, CENTER);

找到设置Tab参数的方法,把设置的自定义属性添加进入

// 找到所有设置Tab文字大小的地方,修改文字大小的设置
tv_tab_title.setTextSize(TypedValue.COMPLEX_UNIT_PX, position == mCurrentTab ? mTextSelectSize : mTextUnSelectSize);

// 找到生成LayoutParams的方法,设置Gravity,marginTop,marginBottom
private void setTabLayoutParams(TextView title) {
        RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) title.getLayoutParams();
        params.topMargin = mTabMarginTop;
        params.bottomMargin = mTabMarginBottom;
        if (mTabGravity == TOP) {
            params.addRule(RelativeLayout.ALIGN_PARENT_TOP);
        } else if (mTabGravity == BOTTOM) {
            params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
        } else {
            params.addRule(RelativeLayout.CENTER_VERTICAL);
        }
        title.setLayoutParams(params);
    }

设置TabScaleTransformer

SlidingScaleTabLayout有多个setViewPager方法,把我们封装的设置TabScaleTransformer的方法,添加进去:

private void initTransformer() {
        // 如果选中状态的文字大小和未选中状态的文字大小是不同的,开启缩放
        if (mTextUnSelectSize != mTextSelectSize) {
            defaultTransformer = new TabScaleTransformer(this, mViewPager.getAdapter(), mTextSelectSize, mTextUnSelectSize);
            this.mViewPager.setPageTransformer(true, defaultTransformer);
        }
    }

// 新增设置Transformer的api
public void setTransformers(List<IViewPagerTransformer> transformers) {
    this.defaultTransformer.setTransformers(transformers);
}
    
public List<IViewPagerTransformer> getTransformers() {
    return defaultTransformer.getTransformers();
}   

赶紧看一下我们的效果:


在这里插入图片描述

总结

能看到这里的绝对是真爱了,我已经把代码开源在github上方便大家使用,有什么问题欢迎留言。

FlycoTabLayoutZ github地址

补充

很多朋友都咨询了相同的问题,在这里补充一下:

问题1:怎么和Fragment一起配合适用?
我刚刚更新了Demo,提供一种可参考的思路,主要是在Fragment的根布局下添加位置Tag,然后在从getItemPosition取出来。

问题2:文字切换抖的厉害,有什么办法解决吗?
github已经更新,具体原因和解决方案可以查看
SlidingScaleTabLayout(2):解决标题文字变化抖动的问题

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容