RecyclerView首个Item滑到顶部渐变

RecyclerView 首个Item顶部渐变效果

抖音“新鲜”Tab页上有个效果比较有意思,当Item滑到顶部的时候,item上的文字信息会有一个渐变的隐藏效果,如下图



这个效果实现起来不是特别难,但是有一些细节地方需要我们考虑一下,下面我就把我实现的方式分享一下,先来一张效果图


实现方式

  • 使用RecyclerView.OnScrollListener监听RecyclerView的滚动
  • 在onScrolled(RecyclerView recyclerView, int dx, int dy)方法中获取第一个Item
  • 获取第一个Item的滑动出屏幕的百分比
  • 根据计算获取当前文字区域的Alpha值

代码如下:

RecyclerView.OnScrollListener mScrollListener = new RecyclerView.OnScrollListener() {
        @Override
        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
            super.onScrollStateChanged(recyclerView, newState);
        }

        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);
            RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
            //这里我使用的是GridLayoutManager,并且RecyclerView只有两列
            if(manager instanceof GridLayoutManager){
                GridLayoutManager gridLayoutManager = (GridLayoutManager) manager;
                int firstVisiblePos = gridLayoutManager.findFirstVisibleItemPosition();
                setViewAplha(gridLayoutManager.findViewByPosition(firstVisiblePos));
                setViewAplha(gridLayoutManager.findViewByPosition(firstVisiblePos+1));

                int firstCompletelyVisible = gridLayoutManager.findFirstCompletelyVisibleItemPosition();
                ItemView view1 = (ItemView) gridLayoutManager.findViewByPosition(firstCompletelyVisible);
                ItemView view2 = (ItemView) gridLayoutManager.findViewByPosition(firstCompletelyVisible + 1);
                if(view1 != null){
                    view1.setAlpha(1);
                }
                if(view2 != null){
                    view2.setAlpha(1);
                }
            }
        }
    };


    float beginPercent = 0.2f;
    float endValue = 2;

    private void setViewAplha(View view){
        if (view == null || !(view instanceof ItemView))
            return;
        float p = UIUtils.px2dip(Math.abs((int) view.getY())) * 1.0f / UIUtils.px2dip(view.getHeight()) * 1.0f;
        float curPercent = Float.compare(p - beginPercent, 0.0f) < 0 ? 0.0f : p - beginPercent;
        curPercent = Float.compare(1, curPercent * endValue) < 0 ? 1 : curPercent * endValue;
        view.setAlpha(1 - curPercent);
    }

这里的ItemView是自定义的每一个内容区域的View,布局比较简单 只有一个TextView 代码如下

private static class ItemView extends LinearLayout{
        TextView textView;
        Context mContext;
        public ItemView(Context context) {
            super(context);
            mContext = context;
            init();
        }

        private void init(){
            View root = LayoutInflater.from(mContext).inflate(R.layout.layout_test_item,this);
            textView = (TextView) root.findViewById(R.id.item_text);
        }

        public void setText(String text){
            textView.setText(text);
        }

        public void setAlpha(float alpha){
            textView.setAlpha(alpha);
        }
    }

需要考虑的细节问题

  • Adapter Holder的复用
    我们都知道RecyclerView.Adapter会对Holder进行复用来节约内存的开销,如果假设一屏有十个item,当我们滑动到第十一个Item时会复用第一个Item的Holder,所以在onBindViewHolder方法中我们需要把文字区域的Alpha值设置为不透明,adaper的代码如下:
RecyclerView.Adapter adapter = new RecyclerView.Adapter() {
        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            ItemView itemView = new ItemView(parent.getContext());
            ViewHolder holder = new ViewHolder(itemView);
            return holder;
        }
        @Override
        public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
            ViewHolder holder1 = (ViewHolder) holder;
            holder1.itemView.setText("我是Item"+position);
            //需要把item设置不透明,否则可能会因为复用导致item刚显示就是看不见的
            holder1.itemView.setAlpha(1);
        }

        @Override
        public int getItemCount() {
            return 40;
        }
        class ViewHolder extends RecyclerView.ViewHolder{
            ItemView itemView;
            public ViewHolder(View itemView) {
                super(itemView);
                this.itemView = (ItemView) itemView;
            }
        }
    };
  • 快速滑动的问题
    RecyclerView 滑动时会有如下三种状态
 /**
     * The RecyclerView is not currently scrolling.
     * @see #getScrollState()
     */
    public static final int SCROLL_STATE_IDLE = 0;

    /**
     * The RecyclerView is currently being dragged by outside input such as user touch input.
     * @see #getScrollState()
     */
    public static final int SCROLL_STATE_DRAGGING = 1;

    /**
     * The RecyclerView is currently animating to a final position while not under
     * outside control.
     * @see #getScrollState()
     */
    public static final int SCROLL_STATE_SETTLING = 2;

当我们快速滑动时可能会由于滑动速度过快,导致在下次回调时可能已经是下一个item元素,例如,在当前onScrolled方法回调时,findFirstVisibleItemPosition可能是item4,然后我们计算出文字区域的Alpha是0.6,因为滑动过快,下次回调onScrolled方法时,findFirstVisibleItemPosition可能就变成了item6,Alpha值是0.8,因为我们缺少了item4的完整状态,就导致了当快速滑动停止时,可能item6是正确的透明度,但是我们希望item4是完全不透明的,但是实际却是0.6
为了解决这个问题,我尝试了几种方法,像监听滑动状态啊、判断onScrolled中的dy参数啊,后来我发现我有点给想的麻烦了,其实不管滑动速度多快,每个item在滑动到第一个完全可展示的位置时,是一定会被我们知道的,比如虽然我们不能拿到item4从完全透明到完全不透明的所有状态,但是当item6是第一个item时,item4一定是第一可以完全展示的item(我的RecyclerView有两列),所以我们就可以通过这个点来下手,通过LayoutManager的findFirstCompletelyVisibleItemPosition来找到第一个完全可见的Item位置,具体体现就是刚才RecyclerView.OnScrollListener 里面的代码

           int firstCompletelyVisible = gridLayoutManager.findFirstCompletelyVisibleItemPosition();
           ItemView view1 = (ItemView) gridLayoutManager.findViewByPosition(firstCompletelyVisible);
           ItemView view2 = (ItemView) gridLayoutManager.findViewByPosition(firstCompletelyVisible + 1);
            if(view1 != null){
                view1.setAlpha(1);
            }
            if(view2 != null){
                view2.setAlpha(1);
            }
  • Alpha值的计算
    这里还有一个要考虑的问题,就是Alpha的取值问题,仔细看抖音中的效果,并不是item刚开始滑动就开始改变透明度,而是大概Item滑出屏幕1/5左右开始,这里就需要我们做一点点小小的计算,当然我的计算方法可能有点low,计算方法就是刚才的setViewAplha里面所做的,在这在粘贴一下
    float beginPercent = 0.2f;
    float endValue = 2;

    private void setViewAplha(View view){
        if (view == null || !(view instanceof ItemView))
            return;
        float p = UIUtils.px2dip(Math.abs((int) view.getY())) * 1.0f / UIUtils.px2dip(view.getHeight()) * 1.0f;
        float curPercent = Float.compare(p - beginPercent, 0.0f) < 0 ? 0.0f : p - beginPercent;
        curPercent = Float.compare(1, curPercent * endValue) < 0 ? 1 : curPercent * endValue;
        view.setAlpha(1 - curPercent);
    }

这个beginPercent变量表示我想要在Item滑出屏幕多少时,才开始改变Alpha值,这里我定义成了0.2也就是1/5,然后在第一次计算curPercent时

  float p = UIUtils.px2dip(Math.abs((int) view.getY())) * 1.0f / UIUtils.px2dip(view.getHeight()) * 1.0f;
  float curPercent = Float.compare(p - beginPercent, 0.0f) < 0 ? 0.0f : p - beginPercent;

可见如果当p<0.2时,我的curPercent会等于0,后续在做什么计算都不会改变文字区域的Alpha值
现在已经可以设置从item滑动到多少才开始出现渐变的效果了,那么下一步我们就应该定义当item滑动到什么位置时文字区域完全透明也就是Alpha值等于0,这时候endValue的作用就来了,我们可以通过给curPercent乘一个值来让它达到指定位置时可以变成1,然后1-curPercent=0

curPercent = Float.compare(1, curPercent * endValue) < 0 ? 1 : curPercent * endValue;

这里我的endValue=2,可以在item滑出屏幕70%时,文字区域完全透明,我的计算方法如下表格

curPercent 与0.2的差值(滑动从1/5开始计算)
0.0 -
0.1 -
0.2 -
0.3 0.1
0.4 0.2
0.5 0.3
0.6 0.4
0.7 0.5
0.8 0.6
0.9 0.7

所以如果我想让Item滑动到70%时完全透明,我只要设置endValue是2既可,因为此时curPercent是0.7-0.2=0.5 然后乘2刚好等于1,之前一直都会小于1,这样的话,我们可以随意设定滑动渐变从哪开始到哪结束,比如我现在希望item滑出屏幕30%开始 同样在item滑出屏幕70%完全透明那么根据下面这个表格

curPercent 与0.3的差值(滑动从30%开始计算)
0.0 -
0.1 -
0.2 -
0.3 -
0.4 0.1
0.5 0.2
0.6 0.3
0.7 0.4
0.8 0.5
0.9 0.6

我们可以计算出 endValue=3( (0.7-0.3)* endValue =1.2 )即可保证在滑动到70%时 完全的透明

ok 到这我们就可以完美表现出这种渐变的效果了,这是我第一次写简书,写的还有点小激动呢。欢迎所有读过的人 批评指正~

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

推荐阅读更多精彩内容

  • Tangram是阿里出品、用于快速实现组合布局的框架模型,在手机天猫Android&iOS版 内广泛使用 该框架提...
    wintersweett阅读 3,254评论 0 1
  • 生活本来就不容易,而我们的不努力只会让生活变得更加无赖 前几天依据《Android群英传》的学习写了一篇笔记是关于...
    AmatorLee阅读 3,731评论 7 23
  • 写在前面:参考YoKey,感谢。附上他的链接:http://www.jianshu.com/p/d30fd8da4...
    喜欢丶下雨天阅读 3,211评论 0 9
  • 这篇文章分三个部分,简单跟大家讲一下 RecyclerView 的常用方法与奇葩用法;工作原理与ListView比...
    LucasAdam阅读 4,377评论 0 27
  • “本文参加#感悟三下乡,青春筑梦行#活动,本人承诺,文章内容为原创,且未在其他平台发表过。” 我,叫李新蓉 是来自...
    阿蓉_9a4a阅读 783评论 0 6