美文网首页我收藏的Android开发文章Android开发经验谈Android开发
仿陌陌选项卡:文字大小变化的SlidingScaleTabLay

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

作者: 珠穆朗玛小王子 | 来源:发表于2019-01-17 14:01 被阅读36次

    前言

    不知不觉博客又一个月没有更新了。最近在看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地址

    相关文章

      网友评论

        本文标题:仿陌陌选项卡:文字大小变化的SlidingScaleTabLay

        本文链接:https://www.haomeiwen.com/subject/bclidqtx.html