一、自定義CoordinatorLayout實現圖片放大功能
本文是基於折疊布局實現的圖片上拉滑動,下拉圖片放大,松手放大的效果,先看下效果圖。
實現原理:
1.使用CoordinatorLayout自帶效果實現上滑。
2.重寫CoordinatorLayout觸摸事件,在分發之前判斷當前是否是在最頂部,並且是下拉操作。
是:進行圖片放大,平移下面布局;松手后執行圖片回彈動畫,恢復布局。
否:不處理事件,滑動事件自動交給下面的Nestscrollview
代碼實現如下:
public class CustomCoordinatorLayout extends CoordinatorLayout { private View mZoomView; private int mZoomViewWidth; private int mZoomViewHeight; private float firstPosition;//記錄第一次按下的位置
private boolean isScrolling;//是否正在縮放
private boolean isScrollDown;//是否下滑
private float mScrollRate = 0.6f;//縮放系數,縮放系數越大,變化的越大
private float mReplyRate = 0.3f;//回調系數,越大,回調越慢
private View mMoveView; private View mMoveView2; private int height,height2; public CustomCoordinatorLayout(@NonNull Context context) { super(context); } public CustomCoordinatorLayout(@NonNull Context context, @Nullable AttributeSet attrs) { super(context, attrs); } public CustomCoordinatorLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } public void setmZoomView(View mZoomView) { this.mZoomView = mZoomView; } public void setmMoveView(View mMoveView1,View mMoveView2) { this.mMoveView = mMoveView1; this.mMoveView2 = mMoveView2; height = mMoveView.getMeasuredHeight(); height2 = mMoveView2.getMeasuredHeight(); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { int[] location = new int[2]; mZoomView.getLocationOnScreen(location); int y = location[1]; if (mZoomViewWidth <= 0 || mZoomViewHeight <= 0) { mZoomViewWidth = mZoomView.getMeasuredWidth(); mZoomViewHeight = mZoomView.getMeasuredHeight(); } switch (ev.getAction()) { case MotionEvent.ACTION_UP: if(isScrollDown) break; //手指離開后恢復圖片
isScrolling = false; replyImage(); break; case MotionEvent.ACTION_MOVE: if(y != 0) return super.dispatchTouchEvent(ev); isScrollDown = false; if (!isScrolling) { if (getScrollY() == 0) { firstPosition = ev.getY();// 滾動到頂部時記錄位置,否則正常返回
} else { break; } } int distance = (int) ((ev.getY() - firstPosition) * mScrollRate); // 滾動距離乘以一個系數
if (distance < 0) { // 當前位置比記錄位置要小,正常返回
isScrollDown = true; break; } // 處理放大
isScrolling = true; setZoom(distance); return super.dispatchTouchEvent(ev); } return super.dispatchTouchEvent(ev); } private void scrollDown(float zoom) { mMoveView2.setScrollY(-(int)(height2 * ((height2 + zoom) / height2))); } //回彈動畫
private void replyImage() { float distance = mZoomView.getMeasuredWidth() - mZoomViewWidth; ValueAnimator valueAnimator = ValueAnimator.ofFloat(distance, 0f).setDuration((long) (distance * mReplyRate)); valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { setZoom((Float) animation.getAnimatedValue()); } }); valueAnimator.start(); mMoveView.setScrollY(height); mMoveView2.setScrollY(height2); } public void setZoom(float zoom) { if (mZoomViewWidth <= 0 || mZoomViewHeight <= 0) { return; } ViewGroup.LayoutParams lp = mZoomView.getLayoutParams(); lp.width = (int) (mZoomViewWidth * ((mZoomViewWidth + zoom) / mZoomViewWidth)); lp.height = (int) (mZoomViewHeight * ((mZoomViewWidth + zoom) / mZoomViewWidth)); ((MarginLayoutParams) lp).setMargins(-(lp.width - mZoomViewWidth) / 2, 0, 0, 0); mZoomView.setLayoutParams(lp); try { CollapsingToolbarLayout parent = (CollapsingToolbarLayout) (mMoveView.getParent()); ViewGroup.LayoutParams layoutParams = parent.getLayoutParams(); layoutParams.height = lp.height; parent.setLayoutParams(layoutParams); }catch (Exception e){ } } }
布局文件結構:
<?xml version="1.0" encoding="utf-8"?>
<com.ingtube.common.widget.CustomCoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto" android:orientation="vertical" android:id="@+id/cl_layout">
<com.google.android.material.appbar.AppBarLayout android:id="@+id/appbar_layout" android:layout_width="match_parent" android:layout_height = "wrap_content"
>
<com.google.android.material.appbar.CollapsingToolbarLayout android:id="@+id/csl_layout" android:layout_width="match_parent" android:layout_height="wrap_content" android:fitsSystemWindows="true" app:layout_scrollFlags="scroll|exitUntilCollapsed"> //要放大的圖片 <com.facebook.drawee.view.SimpleDraweeView android:id="@+id/mine_iv_avatar" android:layout_width="match_parent" android:layout_height="230dp" android:alpha="0.4" android:scaleType="centerCrop" app:layout_collapseMode="parallax"
/> //平移布局一 <RelativeLayout android:id="@+id/rl_layout" android:layout_width="match_parent" android:layout_height="230dp" android:layout_gravity="bottom" app:layout_collapseMode="parallax" app:layout_collapseParallaxMultiplier="0"
>
<LinearLayout android:id="@+id/mine_ll_name" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginStart="24dp" android:layout_marginTop="50dp" android:gravity="center_vertical">
<TextView android:id="@+id/mine_tv_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:maxLines="1" android:textColor="@color/yt_color_white" android:textSize="@dimen/yt_text_name" android:textStyle="normal" tools:text="節操君" />
<ImageView android:id="@+id/iv_ensure_icon" android:layout_width="20dp" android:layout_height="20dp" android:layout_marginStart="14dp" android:src="@drawable/ic_ensure_icon" />
</LinearLayout>
<TextView android:id="@+id/mine_tv_info" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/mine_ll_name" android:layout_alignStart="@id/mine_ll_name" android:layout_marginTop="6dp" android:alpha="0.6" android:background="@drawable/shape_bg_gray_round_stroke" android:paddingLeft="6dp" android:paddingTop="2dp" android:paddingEnd="6dp" android:paddingBottom="2dp" android:text="我的主頁" android:textColor="@color/yt_color_white" android:textSize="@dimen/yt_text_t1" />
<View android:id="@+id/v_personal_info_dot" android:layout_width="8dp" android:layout_height="8dp" android:layout_alignTop="@+id/mine_tv_info" android:layout_alignEnd="@+id/mine_tv_info" android:background="@drawable/shape_red_dot" />
<TextView android:id="@+id/tv_mine_user_setting" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignBottom="@id/mine_tv_info" android:layout_marginStart="8dp" android:layout_toEndOf="@+id/mine_tv_info" android:alpha="0.6" android:background="@drawable/shape_bg_gray_round_stroke" android:paddingStart="6dp" android:paddingTop="2dp" android:paddingEnd="6dp" android:paddingBottom="2dp" android:text="編輯主頁" android:textColor="@color/yt_color_white" android:textSize="@dimen/yt_text_t1" /> //代碼省略 ... </RelativeLayout>
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView android:id="@+id/nestedScrollView" android:layout_width="match_parent" android:layout_height="match_parent" android:fillViewport="true" app:layout_behavior="@string/appbar_scrolling_view_behavior”> //平移布局二 <LinearLayout android:id="@+id/ll_layout" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"
> //代碼省略 ... </LinearLayout>
</androidx.core.widget.NestedScrollView>
</com.ingtube.common.widget.CustomCoordinatorLayout>
使用就非常簡單了,在代碼中設置要放大的view以及需要平移的view就可以了。
private fun zoomImage() { cl_layout.setmMoveView(rl_layout,ll_layout) cl_layout.setmZoomView(mine_iv_avatar) }
以上只是個例子用於實現特定布局的動畫效果,實際應用可根據場景來自定義view進行操作。
二、自定義recylerView實現圖片放大效果
實現效果如下:
實現原理:
重寫recylerview的onTouchEvent方法,在頂部往下滑的時候,進行第一個item圖片放大及布局下移操作。
好處:多布局中自帶滑動,不用處理其他item的滑動,流暢程度100%
代碼實現如下:
public class ZoomRecyclerView extends RecyclerView { private View mZoomView; private int mZoomViewWidth; private int mZoomViewHeight; private int mViewParentHeight; private float mScrollRate = 0.3f;//縮放系數,縮放系數越大,變化的越大
private float mReplyRate = 0.3f;//回調系數,越大,回調越慢 // 記錄首次按下位置
private float mFirstPosition = 0; // 是否正在放大
private Boolean mScaling = false; LinearLayoutManager mLinearLayoutManager ; public ZoomRecyclerView( Context context) { super(context); } public ZoomRecyclerView( Context context, AttributeSet attrs) { super(context, attrs); } public ZoomRecyclerView( Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } public void setZoomView(SimpleDraweeView v, LinearLayoutManager linearLayoutManager) { this.mZoomView = v; mLinearLayoutManager = linearLayoutManager ; } @Override public boolean onTouchEvent(MotionEvent event) { if(mZoomView !=null){ if (mZoomViewWidth <= 0 || mZoomViewHeight <= 0) { mZoomViewWidth = mZoomView.getMeasuredWidth(); mZoomViewHeight = mZoomView.getMeasuredHeight(); } if(mViewParentHeight <= 0) { try { RelativeLayout parent = (RelativeLayout) mZoomView.getParent(); ViewGroup.LayoutParams layoutParams = parent.getLayoutParams(); mViewParentHeight = layoutParams.height; }catch (Exception e){} } //判斷觸摸事件
switch (event.getAction()) { //觸摸結束
case MotionEvent.ACTION_UP: mScaling = false; replyImage(); break; //觸摸中
case MotionEvent.ACTION_MOVE: //判斷是否正在放大 mScaling 的默認值為false
if (!mScaling) { //當圖片也就是第一個item完全可見的時候,記錄觸摸屏幕的位置
if (mLinearLayoutManager.findViewByPosition(mLinearLayoutManager.findFirstVisibleItemPosition()).getTop() == 0) { //記錄首次按下位置
mFirstPosition = event.getY(); } else { break; } } // 滾動距離乘以一個系數
int distance = (int) ((event.getY() - mFirstPosition) * mScrollRate); if (distance < 0) { break; } // 處理放大
mScaling = true; setZoom(distance); default: break; } } return super.onTouchEvent(event); } private void setZoom(float distance) { if (mZoomViewWidth <= 0 || mZoomViewHeight <= 0) { return; } ViewGroup.LayoutParams lp = mZoomView.getLayoutParams(); lp.width = (int) (mZoomViewWidth * ((mZoomViewWidth + distance) / mZoomViewWidth)); lp.height = (int) (mZoomViewHeight * ((mZoomViewWidth + distance) / mZoomViewWidth)); mZoomView.setLayoutParams(lp); try { RelativeLayout parent = (RelativeLayout)mZoomView.getParent(); ViewGroup.LayoutParams layoutParams = parent.getLayoutParams(); layoutParams.height = (int) (mViewParentHeight * ((mZoomViewWidth + distance) / mZoomViewWidth)); parent.setLayoutParams(layoutParams); }catch (Exception e){ } } /** * 圖片回彈動畫 */
private void replyImage() { float distance = mZoomView.getMeasuredWidth() - mZoomViewWidth; ValueAnimator valueAnimator = ValueAnimator.ofFloat(distance, 0f).setDuration((long) (distance * mReplyRate)); valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { setZoom((Float) animation.getAnimatedValue()); } }); valueAnimator.start(); }
布局很簡單:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical">
<com.ingtube.common.widget.ZoomRecyclerView android:id="@+id/rv_personal_info" android:layout_width="match_parent" android:layout_height="wrap_content" />
</RelativeLayout>
使用也非常簡單了。
實現recylerview的滑動監聽,在布局為0的時候,設置圖片放大及布局下移操作。
rv_personal_info.addOnScrollListener(object : RecyclerView.OnScrollListener() { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { this@PersonalInfoActivity.scrollY = recyclerView.computeVerticalScrollOffset() if (scrollY == 0 && pageItems.size != 0) { rv_personal_info.setZoomView(personalPageHeadViewBinder!!.getZoomView(), rv_personal_info.layoutManager as? LinearLayoutManager) } }catch (e:Exception){ e.printStackTrace() } } })