使用RecyclerView打造一個相片廊
這次我們使用RecyclerView這個強大的控制元件來實現一個相片廊,就是長這個模樣

photogallery.gif
一.自定義一個View繼承RecyclerView
public class PhotoGalleryView extends RecyclerView implements GalleryItemDecoration.OnItemSizeMeasuredListener{ private static final String TAG = "PhotoGalleryView"; //滑動方式:一次可以滑動多個item public static final int LINEAR_SNAP_HELPER = 0; //滑動方式:一次只滑動一個item public static final int PAGER_SNAP_HELPER = 1; //預設滑行速度 private int mFlingSpeed=5000; private GalleryItemDecoration mDecoration; private ScrollManager mScrollManager; //圖片的預設值 private int mInitPos=-1; private AnimManager mAnimManager; public PhotoGalleryView(Context context) { this(context,null); } public PhotoGalleryView(Context context, @Nullable AttributeSet attrs) { this(context, attrs,0); } public PhotoGalleryView(Context context, @Nullable AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.PhotoGalleryView); int helper = ta.getInteger(R.styleable.PhotoGalleryView_helper,LINEAR_SNAP_HELPER); ta.recycle(); mAnimManager=new AnimManager(); //新增上裝飾 attachDecoration(); //選擇滑行的方式 attachToRecyclerHelper(helper); }
help表示滑動的方式,是我們自定義的一個屬性,在我們新建的attrs.xml檔案中可以看到它有兩個值,一個是LinearSnapHelper,另一個是PagerSnapHelper。
<?xml version="1.0" encoding="utf-8"?> <resources > <declare-styleable name="PhotoGalleryView"> <attr name="helper" format="string"> <enum name="LinearSnapHelper" value="0" /> <enum name="PagerSnapHelper" value="1" /> </attr> </declare-styleable> </resources>
LinearSnapHelper和PagerSnapHelper是什麼? RecyclerView在24.2.0版本中新增了SnapHelper這個輔助類,用於輔助RecyclerView在滾動結束時將Item對齊到某個位置。特別是列表橫向滑動時,很多時候不會讓列表滑到任意位置,而是會有一定的規則限制,這時候就可以通過SnapHelper來定義對齊規則了。 SnapHelper是一個抽象類,官方提供了一個LinearSnapHelper的子類,可以讓RecyclerView滾動停止時相應的Item停留中間位置。25.1.0版本中官方又提供了一個PagerSnapHelper的子類,可以使RecyclerView像ViewPager一樣的效果,一次只能滑一頁,而且居中顯示。**
它的使用方式很簡單:
LinearSnapHelper mLinearSnapHelper=new LinearSnapHelper(); mLinearSnapHelper.attachToRecyclerView(mPhotoGalleryView);
如果你想對LinearSnapHelper的實現原理比較好奇,可以看看這位大佬的部落格,寫的很好:
ofollow,noindex">讓你明明白白的使用RecyclerView——SnapHelper詳解
二.設定不同圖片的間距距離
為了讓滑動過程圖片的展示顯得更美觀,我們需要自己設定圖片的間距。需要注意的是第一張和最後一張圖片,它們的位置比較特殊,一個沒有左圖片,一個沒有右圖片。所以在設定間距時需要和其他圖片區分開來。
只需要自定義一個ItemDecoration繼承RecyclerView.ItemDecoration並重寫getItemOffsets方法,在這個方法裡面根據不同的圖片位置也就是item的position設定不同的間距。
public class GalleryItemDecoration extends RecyclerView.ItemDecoration { @Override public void getItemOffsets(Rect outRect, final View itemView, final RecyclerView parent, RecyclerView.State state) { super.getItemOffsets(outRect, itemView, parent, state); final int position=parent.getChildAdapterPosition(itemView); final int itemCount=state.getItemCount(); parent.post(new Runnable() { @Override public void run() { if(((PhotoGalleryView)parent).getOrientation()== LinearLayoutManager.HORIZONTAL){ onSetHorizontalParams(parent, itemView, position, itemCount); }else{ onSetVerticalParams(parent, itemView, position, itemCount); } } }); }
private void onSetHorizontalParams(ViewGroup parent, View itemView, int position, int itemCount) { int itemNewWidth = parent.getWidth() - OsUtil.dpToPx(4 * mPageMargin + 2 * mPageVisibleWidth); int itemNewHeight = parent.getHeight(); mItemConsumeX = itemNewWidth + OsUtil.dpToPx(2 * mPageMargin); // 適配第0頁和最後一頁沒有左頁面和右頁面,讓他們保持左邊距和右邊距和其他項一樣 int leftMargin = position == 0 ? OsUtil.dpToPx(mPageVisibleWidth + 2 * mPageMargin) : OsUtil.dpToPx(mPageMargin); int rightMargin = position == itemCount - 1 ? OsUtil.dpToPx(mPageVisibleWidth + 2 * mPageMargin) : OsUtil.dpToPx(mPageMargin); Log.d(TAG, "getItemOffsets---> postion="+position+"itemCount="+itemCount+"leftMargin->"+leftMargin); setLayoutParams(itemView, leftMargin, 0, rightMargin, 0, itemNewWidth, itemNewHeight); }
三.實現漸變的動畫
滑動時,中間圖片滑動到左邊時從大變小,右邊圖片滑動到中間時從小變大這個相對來說比較複雜
1.首先可以根據RecyclerView的OnScrollListener來不斷獲取滑動的距離。
class GalleryScrollerListener extends RecyclerView.OnScrollListener { @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { Log.d(TAG, "ScrollManager newState=" + newState); super.onScrollStateChanged(recyclerView, newState); } @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); if (mPhotoGalleryView.getOrientation() == LinearLayoutManager.HORIZONTAL) { onHorizontalScroll(recyclerView, dx); } else { onVerticalScroll(recyclerView, dy); } } }
2.根據滑動的總距離和滑動每一頁理論消耗的距離就能夠得到圖片的position
位置浮點值=總消耗距離 / 每一頁理論消耗距離
由於計算的結果是浮點數,把它取整就是這張圖片對應的位置。
它的小數點部分就可以取出來作為我們圖片伸縮的條件。(這波操作還是很靈性的),
//水平滾動 private void onHorizontalScroll(RecyclerView recyclerView, int dx) { mConsumeX+=dx; // 讓RecyclerView測繪完成後再呼叫,避免GalleryAdapterHelper.mItemWidth的值拿不到 recyclerView.post(new Runnable() { @Override public void run() { int shouldConsumeX=mPhotoGalleryView.getDecoration().mItemConsumeX; // 位置浮點值(即總消耗距離 / 每一頁理論消耗距離 = 一個浮點型的位置值) float offset=(float) mConsumeX/(float) shouldConsumeX; // 獲取當前頁移動的百分值 float percent=offset-((int)offset); mPosition=(int) offset; Log.i(TAG, "ScrollManager offset=" + offset + "; percent=" + percent + "; mConsumeX=" + mConsumeX + "; shouldConsumeX=" + shouldConsumeX + "; position=" + mPosition); //設定由大到小的動畫 mPhotoGalleryView.getAnimManager().setAnimation(mPhotoGalleryView,mPosition,percent); } }); }
3.根據偏移率也是上面說到小數點部分做動畫
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); } } }
四.實現背景的高斯模糊
為了讓不同的圖片和同一張背景放一起顯得很突兀,不好看。我們可以使用高斯模糊。我們可以直接使用別人寫好的高斯模糊類,它用法也是非常簡單
/** * 設定高斯模糊 * @param forceUpdate */ private void setBlurImage(boolean forceUpdate) { BeautifulPhotoAdapter adapter=(BeautifulPhotoAdapter) mPhotoGalleryView.getAdapter(); final int mCurViewPosition=mPhotoGalleryView.getScrolledPosition(); boolean isSamePosAndNotUpdate = (mCurViewPosition == mLastDraPosition) && !forceUpdate; if (adapter == null || mPhotoGalleryView == null || isSamePosAndNotUpdate) { return; } mPhotoGalleryView.post(new Runnable() { @Override public void run() { } }); // 獲取當前位置的圖片資源ID int resourceId=adapter.getResId(mCurViewPosition); // 將該資源圖片轉為Bitmap Bitmap bitmap= BitmapFactory.decodeResource(getResources(),resourceId); // 將該Bitmap高斯模糊後返回到resBlurBmp Bitmap resBlurBmp = BlurBitmapUtil.blurBitmap(mPhotoGalleryView.getContext(),bitmap,15); // 再將resBlurBmp轉為Drawable Drawable resBlurDrawable = new BitmapDrawable(resBlurBmp); // 獲取前一頁的Drawable Drawable preBlurDrawable = mTSDraCacheMap.get(KEY_PRE_DRAW) == null ? resBlurDrawable : mTSDraCacheMap.get(KEY_PRE_DRAW); /* 以下為淡入淡出效果 */ Drawable[] drawableArr = {preBlurDrawable, resBlurDrawable}; TransitionDrawable transitionDrawable = new TransitionDrawable(drawableArr); mContainer.setBackgroundDrawable(transitionDrawable); transitionDrawable.startTransition(500); // 存入到cache中 mTSDraCacheMap.put(KEY_PRE_DRAW, resBlurDrawable); // 記錄上一次高斯模糊的位置 mLastDraPosition = mCurViewPosition; }
這篇文章的內容大多來自這位大佬的寫的部落格,對於一些細節在他的部落格裡寫的很詳細。
看完部落格之後,如果想實現這樣一個效果,建議先把github上的clone下來,有很多細節需要在原始碼中細細體會。
關於我寫的RecyclerView系列的其他文章也可以看看哦
1.值得深入學習的控制元件-RecyclerView(入門篇) https://www.jianshu.com/p/4260f7c6eb94
2.值得深入學習的控制元件-RecyclerView(進階篇)
https://www.jianshu.com/p/3e4f3a44ceba3.值得深入學習的控制元件-RecyclerView(原始碼解析篇)
https://www.jianshu.com/p/10298503c1344.值得深入學習的控制元件-RecyclerView(時間軸篇)
https://www.jianshu.com/p/fc1694308f13突然覺得自己有點像知識的搬運工,啊哈哈[手打滑稽笑]。