zoukankan      html  css  js  c++  java
  • RecyclerView实现Gallery画廊效果

    使用RecyclerView实现一个画廊效果,主要是使用support库中最新加入的PagerSnapHelper类,通过计算滑动偏移来计算scale的值。

    基本实现

    首先需要为RecyclerView添加一个滚动监听,然后为RecyclerView的第一个与最后一个itemView添加一个ItemDecoration,使位于第一个与最后一个itemView的位置居中对齐。

     
      public void attachToRecyclerView(final RecyclerView recyclerView) {
            this.recyclerView = recyclerView;
            snapHelper.attachToRecyclerView(recyclerView);
            recyclerView.addOnScrollListener(scrollListener);
            recyclerView.addItemDecoration(new ScalableCardItemDecoration());
            recyclerView.post(new Runnable() {
                @Override
                public void run() {
                    pageScrolled();
                }
            });
        }
    
    

    ScalableCardItemDecoration用于计算itemView左右剩余空间,然后为第一个itemView与最后一个itemView添加偏移量。

    
     private static class ScalableCardItemDecoration extends RecyclerView.ItemDecoration {
    
    
            @Override
            public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
                RecyclerView.ViewHolder holder = parent.getChildViewHolder(view);
                int position = holder.getAdapterPosition() == RecyclerView.NO_POSITION ? holder.getOldPosition() : holder.getAdapterPosition();
                RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
                int itemCount = layoutManager.getItemCount();
    
                if(position != 0 && position != itemCount - 1){
                    return;
                }
    
                int peekWidth = getPeekWidth(parent, view);
                boolean isVertical = layoutManager.canScrollVertically();
                //移除item时adapter position为-1。
    
                if (isVertical) {
                    if (position == 0) {
                        outRect.set(0, peekWidth, 0, 0);
                    } else if (position == itemCount - 1) {
                        outRect.set(0, 0, 0, peekWidth);
                    } else {
                        outRect.set(0, 0, 0, 0);
                    }
                } else {
                    if (position == 0) {
                        outRect.set(peekWidth, 0, 0, 0);
                    } else if (position == itemCount - 1) {
                        outRect.set(0, 0, peekWidth, 0);
                    } else {
                        outRect.set(0, 0, 0, 0);
                    }
                }
            }
        }
    

    在为item添加ItemDecoration时,需要计算两边的空间。这里需要手动测量itemView的宽高, 然后计算第一个itemView左边的偏移与最后一个itemView右边的偏移。这里碰到个问题,如果LayoutMangaer的方面是垂直或水平的且RecyclerView对应的的高度或宽度设为wrap_content时,这里计算的值不会正确。

     public static int getPeekWidth(RecyclerView recyclerView, View itemView) {
            RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
            boolean isVertical = layoutManager.canScrollVertically();
            int position = recyclerView.getChildAdapterPosition(itemView);
            //TODO RecyclerView使用wrap_content时,获取的宽度可能会是0。
            int parentWidth = recyclerView.getMeasuredWidth();
            int parentHeight = recyclerView.getMeasuredHeight(); //有时会拿到0
            parentWidth = parentWidth == 0 ? recyclerView.getWidth() : parentWidth;
            parentHeight = parentHeight == 0 ? recyclerView.getHeight() : parentHeight;
            int parentEnd = isVertical ? parentHeight : parentWidth;
            int parentCenter = parentEnd / 2;
    
            int itemSize = isVertical ? itemView.getMeasuredHeight() : itemView.getMeasuredWidth();
    
            if (itemSize == 0) {
    
                ViewGroup.LayoutParams layoutParams = itemView.getLayoutParams();
                int widthMeasureSpec =
                        RecyclerView.LayoutManager.getChildMeasureSpec(parentWidth,
                                layoutManager.getWidthMode(),
                                recyclerView.getPaddingLeft() + recyclerView.getPaddingRight(),
                                layoutParams.width, layoutManager.canScrollHorizontally());
    
                int heightMeasureSpec =
                        RecyclerView.LayoutManager.getChildMeasureSpec(parentHeight,
                                layoutManager.getHeightMode(),
                                recyclerView.getPaddingTop() + recyclerView.getPaddingBottom(),
                                layoutParams.height, layoutManager.canScrollVertically());
    
    
                itemView.measure(widthMeasureSpec, heightMeasureSpec);
                itemSize = isVertical ? itemView.getMeasuredHeight() : itemView.getMeasuredWidth();
            }
    
    
            /*
                计算ItemDecoration的大小,确保插入的大小正好使view的start + itemSize / 2等于parentCenter。
             */
            int startOffset = parentCenter - itemSize / 2;
            int endOffset = parentEnd - (startOffset + itemSize);
    
            return position == 0 ? startOffset : endOffset;
        }
    
    
    

    添加完ItemDecoration后,我们需要在RecyclerView每次滚动的时候计算左、中、右3个itemView的缩放比例。

    
     private void pageScrolled() {
            if (recyclerView == null || recyclerView.getChildCount() == 0)
                return;
    
            RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
    
            View snapingView = snapHelper.findSnapView(layoutManager);
            int snapingViewPosition = recyclerView.getChildAdapterPosition(snapingView);
            View leftSnapingView = layoutManager.findViewByPosition(snapingViewPosition - 1);
            View rightSnapingView = layoutManager.findViewByPosition(snapingViewPosition + 1);
    
    
            float leftSnapingOffset = calculateOffset(recyclerView, leftSnapingView);
            float rightSnapingOffset = calculateOffset(recyclerView, rightSnapingView);
            float currentSnapingOffset = calculateOffset(recyclerView, snapingView);
    
            if (snapingView != null) {
                snapingView.setScaleX(currentSnapingOffset);
                snapingView.setScaleY(currentSnapingOffset);
            }
    
            if (leftSnapingView != null) {
                leftSnapingView.setScaleX(leftSnapingOffset);
                leftSnapingView.setScaleY(leftSnapingOffset);
            }
    
            if (rightSnapingView != null) {
                rightSnapingView.setScaleX(rightSnapingOffset);
                rightSnapingView.setScaleY(rightSnapingOffset);
            }
    
    
            if(snapingView != null && currentSnapingOffset >= 1){
                OnPageChangeListener listener = pageChangeListenerRef != null ? pageChangeListenerRef.get(): null;
                if(listener != null)
                    listener.onPageSelected(snapingViewPosition);
            }
    
            Log.d(TAG, String.format("left: %f, right: %f, current: %f", leftSnapingOffset, rightSnapingOffset, currentSnapingOffset));
        }
       
    

    计算缩放比例时是根据左、中、右三个itemView的中间点与RecyclerView中间点的距离来计算的。

    
     private float calculateOffset(RecyclerView recyclerView, View view) {
            if (view == null)
                return -1;
    
    
            RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
            boolean isVertical = layoutManager.canScrollVertically();
            int viewStart = isVertical ? view.getTop() : view.getLeft();
            int viewEnd = isVertical ? view.getBottom() : view.getRight();
            int centerX = isVertical ? recyclerView.getHeight() / 2 : recyclerView.getWidth() / 2;
            int childCenter = (viewStart + viewEnd) / 2;
            int distance =   Math.abs(childCenter - centerX);
    
            if (distance > centerX)
                return STAY_SCALE;
    
            float offset = 1.f - (distance / (float) centerX);
            return (1.f - STAY_SCALE) * offset + STAY_SCALE;
        }
    

    项目地址: https://github.com/yjwfn/recyclerview-gallery




    《架构文摘》每天一篇架构领域重磅好文,涉及一线互联网公司应用架构(高可用、高性 能、高稳定)、大数据、机器学习等各个热门领域。

  • 相关阅读:
    高效读写的队列:ConcurrentLinkedQueue
    线程池的堆栈问题
    内部类(嵌套类)
    线程池的内部实现
    线程池
    线程阻塞工具类:LockSupport
    CountDownLatch(倒计时器)、CyclicBarrier(循环栅栏)
    ReentrantReadWriteLock(读写锁)
    ReentrantLock(重入锁)的好搭档:Condition 条件
    ReentrantLock
  • 原文地址:https://www.cnblogs.com/xwgblog/p/7580812.html
Copyright © 2011-2022 走看看