zoukankan      html  css  js  c++  java
  • 两种图片下拉放大效果实现(自定义CoordinatorLayout以及自定义Recylerview)

    一、自定义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()
                    }
                }
            })
  • 相关阅读:
    java中next()、nextInt()、nextLine()区别
    原码、反码、补码及移位运算
    微信小程序开发流程(适用于新手学习)
    Dubbo 常用的容错机制
    分布式系统性能注意点
    陌上人如玉,公子世无双!
    五步工作法
    四个凡是
    Javacpu 和内存问题排查步骤
    开启JMC功能
  • 原文地址:https://www.cnblogs.com/fangg/p/13267306.html
Copyright © 2011-2022 走看看