学习自
http://mp.weixin.qq.com/s?__biz=MzAxMTI4MTkwNQ==&mid=2650824778&idx=1&sn=c7116f310e70a2403c5e5d88c724060b
学习总结:
算不上是RecyclerView的进阶使用
1.对recycler view动画的使用比较浅
2.没有重写LayoutManager
3.自动居中问题也通过自带api简单处理了
不过配合card view,让人感觉真的美爆了。
是这样的效果 蛮漂亮的
1.自动居中
LinearSnapHelper mLinearySnapHelper = new LinearSnapHelper();
mLinearySnapHelper.attachToRecyclerView(mGalleryRecyclerView);
自带的api舒服的一匹,不过我们采用手势判断再解决一些事件问题,也可以轻松实现
linear是允许一次滑多个
pager开头的是只允许一次滑一个
2.设置边距
他就是想让分别给第一个item和最后一个item设置不一样的边距,不显得太突兀。
实现起来很简单,可以在adapter里直接写,也可以重写一个item decoration,都很好,没有孰优孰劣,看自己喜欢
3.实现动画
3个点:获取当前是第几个item、获取偏移值、根据偏移值去设置动画
获取第几个item直接onScrollListener里就可以得到,得到的方式有多种;
获取偏移值,不用说也能知道;
设置动画,也很简单;
有个地方讲的不是太清楚,他的动画原理应该是这样的
是从左边顶部开始的,也可以是从底部开始的。我一开始有点太主观了,从中间开始考虑起来,耽误了一会。
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 SparseArrayarray; 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仿探探