美文网首页Android recycleviewAndroid开发Android知识
RecyclerView首个Item滑到顶部渐变

RecyclerView首个Item滑到顶部渐变

作者: HumorousMan | 来源:发表于2017-06-03 22:17 被阅读457次

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

    相关文章

      网友评论

        本文标题:RecyclerView首个Item滑到顶部渐变

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