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 的方式,这样改动的地方比较小。条条大路通罗马,凡事多一种思考,现在做软件开发的话遇到什么需求,网上也许就已经有答案了,但其实最重要的还是要理解别人的思想,不然下次需求如果有点小变化自己还是搞不定。