另类实现 ScrollView 下拉头部放大

1 前言

前两天接到一个在列表下拉时头部放大的需求,也就是这样的效果:

2 思路

查了查资料,这样的效果一般都是通过自定义 ScrollView 来实现,不过我总觉得这样挺麻烦的,需要去修改原来的 XML 文件,而且局限性也挺大,需要缩放的控件必须放在第一个,在看了别人实现的思路(自定义scrollView实现顶部图片下拉放大)后,决定自己用一个工具类来实现这种效果。

在上面那位前辈的文章中他是自定义了一个 View 继承自 ScrollView,然后实现 onTouchListener 接口,重写 onTouch() 方法,在 onTouch() 方法中在做缩放的判断,所以说最重要的逻辑其实就是在 onTouch() 方法中。既然如此,我们其实只需要拿到需要监测滚动的 ScrollView,然后给它设置一个 onTouchListener 即可,何必还要自定义一个 ScrollView 并修改原来的布局文件呢。

3 代码实现

/**
 * Description:滚动缩放的工具类
 * Created by 禽兽先生
 * Created on 2018/10/22
 */
public class ScrollZoomUtil {
    //控件原始高
    private static int sZoomViewOriginWidth = 0;
    //控件原始宽
    private static int sZoomViewOriginHeight = 0;
    //被监听的可滚动控件按下时的纵坐标
    private static float sLastY = 0;
    //是否开始缩放的标志位
    private static boolean sStartZoom = false;

    public static void scrollZoom(final View scrollView, final View zoomView) {
        zoomView.post(new Runnable() {
            @Override
            public void run() {
                //记录控件原始宽高
                sZoomViewOriginWidth = zoomView.getMeasuredWidth();
                sZoomViewOriginHeight = zoomView.getMeasuredHeight();
            }
        });
        scrollView.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        //当被监听的可滚动控件已经滚动到顶部时才可以进行缩放,置标志位为 true,记录下按下的纵坐标
                        if (scrollView.getScrollY() == 0) {
                            sStartZoom = true;
                            sLastY = event.getY();
                            return true;
                        }
                        break;
                    case MotionEvent.ACTION_MOVE:
                        if (sStartZoom) {
                            //计算滑动的距离
                            float distanceY = event.getY() - sLastY;
                            if (distanceY > 0) {
                                //当滑动距离大于 0 时表示下拉操作,对需要缩放的控件进行放大操作
                                zoom(zoomView, distanceY);
                            } else {
                                //当滑动距离小于 0 时表示上拉操作,判断需要缩放的控件的当前宽高,如果比原始宽高大则先缩放至原始宽高
                                if (zoomView.getMeasuredWidth() > sZoomViewOriginWidth) {
                                    zoom(zoomView, distanceY);
                                } else {
                                    //需要缩放的控件的当前宽高与原始宽高相等时再进行上拉,则进行正常的滚动操作
                                    break;
                                }
                            }
                            return true;
                        }
                    case MotionEvent.ACTION_UP:
                        //手指抬起后恢复原始状态
                        sLastY = 0;
                        sStartZoom = false;
                        restore(zoomView);
                        break;
                }
                return false;
            }
        });
    }

    /**
     * Description:缩放
     * Date:2018/10/22
     */
    private static void zoom(View zoomView, float distanceY) {
        if (sZoomViewOriginWidth <= 0 || sZoomViewOriginHeight <= 0) {
            return;
        }
        ViewGroup.LayoutParams layoutParams = zoomView.getLayoutParams();
        //控件高为原始高度加上滑动距离乘以一个系数(与滑动距离 1:1 的话变化太大)
        layoutParams.height = (int) (sZoomViewOriginHeight + distanceY * 0.4);
        //控件宽等比例变化
        layoutParams.width = (int) ((sZoomViewOriginWidth * (sZoomViewOriginHeight + distanceY * 0.4)) / sZoomViewOriginHeight);
        zoomView.setLayoutParams(layoutParams);
    }

    /**
     * Description:恢复成初始状态
     * Date:2018/10/22
     */
    private static void restore(final View zoomView) {
        //利用属性动画恢复成原始宽高,使其有一个过渡效果
        ValueAnimator widthValueAnimator = ObjectAnimator.ofInt(zoomView.getMeasuredWidth(), sZoomViewOriginWidth);
        ValueAnimator heightValueAnimator = ObjectAnimator.ofInt(zoomView.getMeasuredHeight(), sZoomViewOriginHeight);
        widthValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                ViewGroup.LayoutParams layoutParams = zoomView.getLayoutParams();
                layoutParams.width = (int) animation.getAnimatedValue();
                zoomView.setLayoutParams(layoutParams);
            }
        });
        heightValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                ViewGroup.LayoutParams layoutParams = zoomView.getLayoutParams();
                layoutParams.height = (int) animation.getAnimatedValue();
                zoomView.setLayoutParams(layoutParams);
            }
        });
        AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.play(widthValueAnimator).with(heightValueAnimator);
        //这个动画时长是每个动画持续的时长而不是总时长
        animatorSet.setDuration(300);
        animatorSet.start();
    }
}

我在计算需要缩放的控件的宽高变化时比较粗糙,高度的变化就是滑动距离乘以一个系数,然后宽度等比例变化,不过效果看起来还行。使用的时候布局文件不用做任何改变:

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/scroll_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <ImageView
            android:id="@+id/iv_zoom"
            android:layout_width="match_parent"
            android:layout_height="300dp"
            android:layout_gravity="center_horizontal"
            android:scaleType="fitXY"
            android:src="@drawable/img_1" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:layout_margin="15dp"
            android:text="我是 TextView" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:layout_margin="15dp"
            android:text="我是 TextView" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:layout_margin="15dp"
            android:text="我是 TextView" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:layout_margin="15dp"
            android:text="我是 TextView" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:layout_margin="15dp"
            android:text="我是 TextView" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:layout_margin="15dp"
            android:text="我是 TextView" />

        <ImageView
            android:layout_width="match_parent"
            android:layout_height="300dp"
            android:layout_gravity="center_horizontal"
            android:scaleType="fitXY"
            android:src="@drawable/img_2" />
    </LinearLayout>

</ScrollView>

只需要在 Activity 中调用一下工具类传入一下需要检测和需要缩放的控件即可:

        ScrollView scrollView = findViewById(R.id.scroll_view);
        ImageView ivZoom = findViewById(R.id.iv_zoom);
        ScrollZoomUtil.scrollZoom(scrollView, ivZoom);

效果就是开头的那种效果。

4 总结

对于这种下拉缩放的需求,个人比较喜欢这种用一个外部方法来替代自定义 ScrollView 的方式,这样改动的地方比较小。条条大路通罗马,凡事多一种思考,现在做软件开发的话遇到什么需求,网上也许就已经有答案了,但其实最重要的还是要理解别人的思想,不然下次需求如果有点小变化自己还是搞不定。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值