Android RecyclerView实现Gallery、修复LinearSnapHelper首尾item不居中的坑

学习自

http://mp.weixin.qq.com/s?__biz=MzAxMTI4MTkwNQ==&mid=2650824778&idx=1&sn=c7116f310e70a2403c5e5d88c724060b


学习总结:

算不上是RecyclerView的进阶使用

1.对recycler view动画的使用比较浅

2.没有重写LayoutManager

3.自动居中问题也通过自带api简单处理了

不过配合card view,让人感觉真的美爆了。


  

Android RecyclerView实现Gallery、修复LinearSnapHelper首尾item不居中的坑_第1张图片

是这样的效果 蛮漂亮的


1.自动居中

LinearSnapHelper mLinearySnapHelper = new LinearSnapHelper();

mLinearySnapHelper.attachToRecyclerView(mGalleryRecyclerView);

自带的api舒服的一匹,不过我们采用手势判断再解决一些事件问题,也可以轻松实现

linear是允许一次滑多个

pager开头的是只允许一次滑一个


2.设置边距

他就是想让分别给第一个item和最后一个item设置不一样的边距,不显得太突兀。

实现起来很简单,可以在adapter里直接写,也可以重写一个item decoration,都很好,没有孰优孰劣,看自己喜欢


3.实现动画

3个点:获取当前是第几个item、获取偏移值、根据偏移值去设置动画

获取第几个item直接onScrollListener里就可以得到,得到的方式有多种;

获取偏移值,不用说也能知道;

设置动画,也很简单;

有个地方讲的不是太清楚,他的动画原理应该是这样的

Android RecyclerView实现Gallery、修复LinearSnapHelper首尾item不居中的坑_第2张图片

是从左边顶部开始的,也可以是从底部开始的。我一开始有点太主观了,从中间开始考虑起来,耽误了一会。


4.高斯模糊

直接调用算法即可

效果



5.淡入淡出

高斯图切换的太过突兀,所以需要淡入淡出。用一个比较厉害的drawable::TransitionDrawable

用法你一看就能明白

private void setBlurImage(RelativeLayout bg) {
    int position = s.getPosition();
    Drawable d = array.get(position);

    if (old == null) {
        bg.setBackgroundDrawable(d);
    } else {
        TransitionDrawable transitionDrawable = new TransitionDrawable(new Drawable[]{old,d});
        bg.setBackgroundDrawable(transitionDrawable);
        transitionDrawable.startTransition(500);
    }

    old = d;
}

但是有个巨坑,第一个item和最后一个item不会默认居中,所以会有一个两次的调用。比如0滑到1是ok的,但是1滑到0,会在1滑到0这个原有的基础上再进行一个滑动->0滑到0。所以判重return即可。

private Drawable old;

private void setBlurImage(RelativeLayout bg) {
    int position = s.getPosition();
    Drawable d = array.get(position);

    if (d == old) {
        return;
    }

    if (old == null) {
        bg.setBackgroundDrawable(d);
    } else {
        TransitionDrawable transitionDrawable = new TransitionDrawable(new Drawable[]{old,d});
        bg.setBackgroundDrawable(transitionDrawable);
        transitionDrawable.startTransition(500);
    }

    old = d;
}

效果



全部代码,特别清晰,加了注释,原文用了高大上的架构,我看不懂

public class Test {

    private Context context;
    private ScrollManager s;
    private int[] res = {R.drawable.face_register,R.drawable.tmp_permission,R.drawable.village};
    private final SparseArray array;


    public Test(RecyclerView rv, final RelativeLayout bg) {
        context = rv.getContext();

        //常规
        rv.setAdapter(new Adapter());
        LinearLayoutManager m = new LinearLayoutManager(context);
        m.setOrientation(LinearLayoutManager.HORIZONTAL);
        rv.setLayoutManager(m);

        //自动居中
        LinearSnapHelper h = new LinearSnapHelper();
        h.attachToRecyclerView(rv);

        //页边距
        rv.addItemDecoration(new GalleryItemDecoration());

        //动画
        s = new ScrollManager(rv);

        //高斯模糊
        //初始化高斯模糊图
        array = init();
        setBlurImage(bg);
        rv.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
                if (newState == RecyclerView.SCROLL_STATE_IDLE) {
                    setBlurImage(bg);
                }
            }
        });
    }

    private SparseArray init() {
        SparseArray array = new SparseArray<>();
        for (int i = 0; i < 3; i ++) {
            int r = res[i];
            Bitmap b = BitmapFactory.decodeResource(context.getResources(), r);
            Bitmap nb = BlurBitmapUtil.blurBitmap(context, b, 15f);
            Drawable nd = new BitmapDrawable(nb);
            array.put(i,nd);
        }
        return array;
    }

    private Drawable old;

    private void setBlurImage(RelativeLayout bg) {
        int position = s.getPosition();
        Drawable d = array.get(position);

        if (d == old) {
            return;
        }

        if (old == null) {
            bg.setBackgroundDrawable(d);
        } else {
            TransitionDrawable transitionDrawable = new TransitionDrawable(new Drawable[]{old,d});
            bg.setBackgroundDrawable(transitionDrawable);
            transitionDrawable.startTransition(500);
        }

        old = d;
    }

    class Adapter extends RecyclerView.Adapter {

        @Override
        public CommonViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            return new CommonViewHolder(LayoutInflater.from(context).inflate(R.layout.item,parent,false));
        }

        @Override
        public void onBindViewHolder(CommonViewHolder holder, int position) {
            ImageView iv = (ImageView) holder.holdAndGetView(R.id.iv);
            Glide.with(context).load(res[position]).into(iv);
        }

        @Override
        public int getItemCount() {
            return res.length;
        }
    }
}
 
  
public class GalleryItemDecoration extends RecyclerView.ItemDecoration {
    private final String TAG = "GalleryItemDecoration";

    public static int mPageMargin = 0;          // 每一个页面默认页边距
    public static int mLeftPageVisibleWidth = 50; // 中间页面左右两边的页面可见部分宽度

    public static int mItemComusemY = 0;
    public static int mItemComusemX = 0;

    @Override
    public void getItemOffsets(Rect outRect, final View view, final RecyclerView parent, RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);

        //这个类的方法应该只会被调用k次 是一个专门对item操作的类

        final int position = parent.getChildAdapterPosition(view);
        final int itemCount = parent.getAdapter().getItemCount();

        parent.post(new Runnable() {
            @Override
            public void run() {
                LinearLayoutManager lm = (LinearLayoutManager) parent.getLayoutManager();

                if (lm.getOrientation() == LinearLayoutManager.HORIZONTAL) {
                    onSetHoritiontalParams(parent, view, position, itemCount);
                } else {
                    onSetVerticalParams(parent, view, position, itemCount);
                }
            }
        });
    }

    private void onSetVerticalParams(ViewGroup parent, View itemView, int position, int itemCount) {
        int itemNewWidth = parent.getWidth();
        int itemNewHeight = parent.getHeight() - dpToPx(4 * mPageMargin + 2 * mLeftPageVisibleWidth);

        mItemComusemY = itemNewHeight + dpToPx(2 * mPageMargin);

        // 适配第0页和最后一页没有左页面和右页面,让他们保持左边距和右边距和其他项一样
        int topMargin = position == 0 ? dpToPx(mLeftPageVisibleWidth + 2 * mPageMargin) : dpToPx(mPageMargin);
        int bottomMargin = position == itemCount - 1 ? dpToPx(mLeftPageVisibleWidth + 2 * mPageMargin) : dpToPx(mPageMargin);

        setLayoutParams(itemView, 0, topMargin, 0, bottomMargin, itemNewWidth, itemNewHeight);
    }

    private void onSetHoritiontalParams(ViewGroup parent, View itemView, int position, int itemCount) {
        //注意 我们xml中定义了padding,所以才没有贴顶
        int itemNewWidth = parent.getWidth() - dpToPx(4 * mPageMargin + 2 * mLeftPageVisibleWidth);
        int itemNewHeight = parent.getHeight();

        mItemComusemX = itemNewWidth + dpToPx(2 * mPageMargin);//这个引用啥意思 平均单个item整个的宽度

        //mPageMargin就是页之间的边距 默认是0 由于有padding 所以没有贴在一起 我觉得设计的非常不合理 但是无伤大雅

        int leftMargin = position == 0 ? dpToPx(mLeftPageVisibleWidth + 2 * mPageMargin) : dpToPx(mPageMargin);
        int rightMargin = position == itemCount - 1 ? dpToPx(mLeftPageVisibleWidth + 2 * mPageMargin) : dpToPx(mPageMargin);

        setLayoutParams(itemView, leftMargin, 0, rightMargin, 0, itemNewWidth, itemNewHeight);
    }

    private void setLayoutParams(View itemView, int left, int top, int right, int bottom, int itemWidth, int itemHeight) {
        RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) itemView.getLayoutParams();
        boolean mMarginChange = false;
        boolean mWidthChange = false;
        boolean mHeightChange = false;

        if (lp.leftMargin != left || lp.topMargin != top || lp.rightMargin != right || lp.bottomMargin != bottom) {
            lp.setMargins(left, top, right, bottom);
            mMarginChange = true;
        }
        if (lp.width != itemWidth) {
            lp.width = itemWidth;
            mWidthChange = true;
        }
        if (lp.height != itemHeight) {
            lp.height = itemHeight;
            mHeightChange = true;
        }

        // 这里的if考虑到的是缩放动画情况
        if (mWidthChange || mMarginChange || mHeightChange) {
            itemView.setLayoutParams(lp);
        }
    }

    private static int dpToPx(int dp) {
        return (int) (dp * Resources.getSystem().getDisplayMetrics().density);
    }
}

public class ScrollManager {
    private static final String TAG = "ScrollManager";

    private RecyclerView mGalleryRecyclerView;

    private LinearSnapHelper mLinearySnapHelper;
    private PagerSnapHelper mPagerSnapHelper;

    private int mPosition = 0;

    // 使偏移量为左边距 + 左边Item的可视部分宽度
    private int mConsumeX = 0;
    private int mConsumeY = 0;
    // 滑动方向
    private int slideDirct = SLIDE_RIGHT;

    private static final int SLIDE_LEFT = 1;    // 左滑
    private static final int SLIDE_RIGHT = 2;   // 右滑
    private static final int SLIDE_TOP = 3;     // 上滑
    private static final int SLIDE_BOTTOM = 4;  // 下滑


    public ScrollManager(final RecyclerView mGalleryRecyclerView) {
        this.mGalleryRecyclerView = mGalleryRecyclerView;

        mGalleryRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                if (!mGalleryRecyclerView.isAttachedToWindow()) {//这里可能有错
                    return;
                }

                LinearLayoutManager lm = (LinearLayoutManager)mGalleryRecyclerView.getLayoutManager();
                if (lm.getOrientation() == LinearLayoutManager.HORIZONTAL) {
                    onHoritiontalScroll(recyclerView, dx);
                } else {
                    onVerticalScroll(recyclerView, dy);
                }
            }
        });
    }

//    public void updateComsume() {
//        mConsumeX += dpToPx(GalleryItemDecoration.mLeftPageVisibleWidth + GalleryItemDecoration.mPageMargin * 2);
//        mConsumeY += dpToPx(GalleryItemDecoration.mLeftPageVisibleWidth + GalleryItemDecoration.mPageMargin * 2);
//    }

    /**
     * 垂直滑动
     *
     * @param recyclerView
     * @param dy
     */
    private void onVerticalScroll(final RecyclerView recyclerView, int dy) {
        mConsumeY += dy;

        if (dy > 0) {
            slideDirct = SLIDE_BOTTOM;
        } else {
            slideDirct = SLIDE_TOP;
        }

        // 让RecyclerView测绘完成后再调用,避免GalleryAdapterHelper.mItemHeight的值拿不到
        recyclerView.post(new Runnable() {
            @Override
            public void run() {
                int shouldConsumeY = GalleryItemDecoration.mItemComusemY;
                // 获取当前的位置
                int position = getPosition(mConsumeY, shouldConsumeY);
                float offset = (float) mConsumeY / (float) shouldConsumeY;     // 位置浮点值(即总消耗距离 / 每一页理论消耗距离 = 一个浮点型的位置值)
                // 避免offset值取整时进一,从而影响了percent值
                if (offset >= ((LinearLayoutManager)mGalleryRecyclerView.getLayoutManager()).findFirstVisibleItemPosition() + 1 && slideDirct == SLIDE_BOTTOM) {
                    return;
                }
                // 获取当前页移动的百分值
                float percent = offset - ((int) offset);



                // 设置动画变化
                AnimManager.getInstance().setAnimation(recyclerView, position, percent);
            }
        });
    }

    /**
     * 水平滑动
     *
     * @param recyclerView
     * @param dx
     */
    private void onHoritiontalScroll(final RecyclerView recyclerView, final int dx) {
        mConsumeX += dx;

        if (dx > 0) {
            // 右滑
            slideDirct = SLIDE_RIGHT;
        } else {
            // 左滑
            slideDirct = SLIDE_LEFT;
        }

        //获取position
        // 让RecyclerView测绘完成后再调用,避免GalleryAdapterHelper.mItemWidth的值拿不到
        recyclerView.post(new Runnable() {
            @Override
            public void run() {
                int shouldConsumeX = GalleryItemDecoration.mItemComusemX;//获取图片宽+2*padding
                int position = getPosition(mConsumeX, shouldConsumeX);

                float offset = (float) mConsumeX / (float) shouldConsumeX;     // 位置浮点值(即总消耗距离 / 每一页理论消耗距离 = 一个浮点型的位置值)

                // 避免offset值取整时进一,从而影响了percent值
                //我觉得是应该避免一下 但是他这个避免方式我看不懂
                if (offset >= ((LinearLayoutManager)mGalleryRecyclerView.getLayoutManager()).findFirstVisibleItemPosition() + 1 && slideDirct == SLIDE_RIGHT) {
                    return;
                }

                // 获取当前页移动的百分值 所以这里获取的
                float percent = offset - ((int) offset);

                // 设置动画变化
                AnimManager.getInstance().setAnimation(recyclerView, position, percent);
            }
        });
    }


    /**
     * 获取位置
     *
     * @param mConsumeX      实际消耗距离
     * @param shouldConsumeX 理论消耗距离
     * @return
     */
    private int getPosition(int mConsumeX, int shouldConsumeX) {
        float offset = (float) mConsumeX / (float) shouldConsumeX;
        int position = Math.round(offset);        // 四舍五入获取位置
        mPosition = position;
        return position;
    }

    public int getPosition() {
        return mPosition;
    }

    private static int dpToPx(int dp) {
        return (int) (dp * Resources.getSystem().getDisplayMetrics().density);
    }
}
 
  
public class AnimManager {
    private static final String TAG = "AnimManager";

    private static AnimManager INSTANCE;

    public static final int ANIM_BOTTOM_TO_TOP = 0;
    public static final int ANIM_TOP_TO_BOTTOM = 1;

    private int mAnimType = ANIM_BOTTOM_TO_TOP; //动画类型
    private float mAnimFactor = 0.2f;   //变化因子

    public static AnimManager getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new AnimManager();
        }
        return INSTANCE;
    }

    public void setAnimation(RecyclerView recyclerView, int position, float percent) {
        switch (mAnimType) {
            case ANIM_BOTTOM_TO_TOP:
                setBottomToTopAnim(recyclerView, position, percent);
                break;
            case ANIM_TOP_TO_BOTTOM:
                setTopToBottomAnim(recyclerView, position, percent);
                break;
            default:
                setBottomToTopAnim(recyclerView, position, percent);
                break;
        }
    }


    /**
     * 从下到上的动画效果
     *
     * @param recyclerView
     * @param position
     * @param percent
     */
    private void setBottomToTopAnim(RecyclerView recyclerView, int position, float percent) {
        View mCurView = recyclerView.getLayoutManager().findViewByPosition(position);       // 中间页
        View mRightView = recyclerView.getLayoutManager().findViewByPosition(position + 1); // 左边页
        View mLeftView = recyclerView.getLayoutManager().findViewByPosition(position - 1);  // 右边页


        if (percent <= 0.5) {
            if (mLeftView != null) {
                mLeftView.setScaleX((1 - mAnimFactor) + percent * mAnimFactor);
                mLeftView.setScaleY((1 - mAnimFactor) + percent * mAnimFactor);
            }
            if (mCurView != null) {
                mCurView.setScaleX(1 - percent * mAnimFactor);
                mCurView.setScaleY(1 - percent * mAnimFactor);
            }
            if (mRightView != null) {
                mRightView.setScaleX((1 - mAnimFactor) + percent * mAnimFactor);
                mRightView.setScaleY((1 - mAnimFactor) + percent * mAnimFactor);
            }
        } else {
            if (mLeftView != null) {
                mLeftView.setScaleX(1 - percent * mAnimFactor);
                mLeftView.setScaleY(1 - percent * mAnimFactor);
            }
            if (mCurView != null) {
                mCurView.setScaleX((1 - mAnimFactor) + percent * mAnimFactor);
                mCurView.setScaleY((1 - mAnimFactor) + percent * mAnimFactor);
            }
            if (mRightView != null) {
                mRightView.setScaleX(1 - percent * mAnimFactor);
                mRightView.setScaleY(1 - percent * mAnimFactor);
            }
        }
    }


    /***
     * 从上到下的效果
     */
    private void setTopToBottomAnim(RecyclerView recyclerView, int position, float percent) {
        View mCurView = recyclerView.getLayoutManager().findViewByPosition(position);       // 中间页
        View mRightView = recyclerView.getLayoutManager().findViewByPosition(position + 1); // 左边页
        View mLeftView = recyclerView.getLayoutManager().findViewByPosition(position - 1);  // 右边页

        if (percent <= 0.5) {
            if (mLeftView != null) {
                mLeftView.setScaleX(1 - percent * mAnimFactor);
                mLeftView.setScaleY(1 - percent * mAnimFactor);
            }
            if (mCurView != null) {
                mCurView.setScaleX((1 - mAnimFactor) + percent * mAnimFactor);
                mCurView.setScaleY((1 - mAnimFactor) + percent * mAnimFactor);
            }
            if (mRightView != null) {
                mRightView.setScaleX(1 - percent * mAnimFactor);
                mRightView.setScaleY(1 - percent * mAnimFactor);
            }

        } else {
            if (mLeftView != null) {
                mLeftView.setScaleX((1 - mAnimFactor) + percent * mAnimFactor);
                mLeftView.setScaleY((1 - mAnimFactor) + percent * mAnimFactor);
            }
            if (mCurView != null) {
                mCurView.setScaleX(1 - percent * mAnimFactor);
                mCurView.setScaleY(1 - percent * mAnimFactor);
            }
            if (mRightView != null) {
                mRightView.setScaleX((1 - mAnimFactor) + percent * mAnimFactor);
                mRightView.setScaleY((1 - mAnimFactor) + percent * mAnimFactor);
            }
        }
    }

    public void setmAnimFactor(float mAnimFactor) {
        this.mAnimFactor = mAnimFactor;
    }

    public void setmAnimType(int mAnimType) {
        this.mAnimType = mAnimType;
    }
}

public class BlurBitmapUtil {
    //图片缩放比例
    private static final float BITMAP_SCALE = 0.4f;

    /**
     * 模糊图片的具体方法
     *
     * @param context 上下文对象
     * @param image   需要模糊的图片
     * @return 模糊处理后的图片
     */
    public static Bitmap blurBitmap(Context context, Bitmap image, float blurRadius) {
        // 计算图片缩小后的长宽
        int width = Math.round(image.getWidth() * BITMAP_SCALE);
        int height = Math.round(image.getHeight() * BITMAP_SCALE);

        // 将缩小后的图片做为预渲染的图片
        Bitmap inputBitmap = Bitmap.createScaledBitmap(image, width, height, false);
        // 创建一张渲染后的输出图片
        Bitmap outputBitmap = Bitmap.createBitmap(inputBitmap);

        // 创建RenderScript内核对象
        RenderScript rs = RenderScript.create(context);
        // 创建一个模糊效果的RenderScript的工具对象
        ScriptIntrinsicBlur blurScript = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));

        // 由于RenderScript并没有使用VM来分配内存,所以需要使用Allocation类来创建和分配内存空间
        // 创建Allocation对象的时候其实内存是空的,需要使用copyTo()将数据填充进去
        Allocation tmpIn = Allocation.createFromBitmap(rs, inputBitmap);
        Allocation tmpOut = Allocation.createFromBitmap(rs, outputBitmap);

        // 设置渲染的模糊程度, 25f是最大模糊度
        blurScript.setRadius(blurRadius);
        // 设置blurScript对象的输入内存
        blurScript.setInput(tmpIn);
        // 将输出数据保存到输出内存中
        blurScript.forEach(tmpOut);

        // 将数据填充到Allocation中
        tmpOut.copyTo(outputBitmap);

        return outputBitmap;
    }
}


想学习重写LayoutManager的,可以看看鸿洋公众号里的recycler view仿探探

你可能感兴趣的:(Android)