阻尼效果总结(顶部图片放大效果)

顶部图片放大效果

接着上一篇的继续说,因为篇幅太长,所以切割了。

阻尼效果总结(上下左右拉动橡皮筋效果)

这次来看一个顶部图片放大的效果:

这里写图片描述

这个效果也很常见,说是很常见,但是我怎么没见到。。。

这里的代码其实跟上一个走的是同一个套路,只不过这次指定了一个图片控件:

package com.xiey94.damp.view;

import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ImageView;
import android.widget.ScrollView;

import com.xiey94.damp.util.showLog;

/**
 * @author : xiey
 * @project name : As30.
 * @package name  : com.xiey94.damp.view.
 * @date : 2018/1/19.
 * @signature : do my best.
 * @from : http://blog.csdn.net/baiyuliang2013/article/details/25815407
 * @explain : 头部图片进行拉伸,但是头部以下不跟着动,不方便调整
 */

public class MyScrollView extends ScrollView {
    private View inner;

    private float y;

    //记录位置
    private Rect normal = new Rect();
    //过滤到第一次移动
    private boolean isCount = false;
    //是否开始移动
    private boolean isMoveing = false;
    private ImageView imageView;
    private int initTop, initBootom;
    private int top, bottom;

    public void setImageView(ImageView imageView) {
        this.imageView = imageView;
        int h = imageView.getHeight();
        showLog.show(h);
    }

    public MyScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    /**
     * 获取第一个View,也就是整个
     */
    @Override
    protected void onFinishInflate() {
        if (getChildCount() > 0) {
            inner = getChildAt(0);
        }
        super.onFinishInflate();
    }

    /**
     * 事件处理
     */
    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if (inner != null) {
            commonTouchEvent(ev);
        }
        return super.onTouchEvent(ev);
    }

    /**
     * 事件处理
     */
    private void commonTouchEvent(MotionEvent ev) {
        int action = ev.getAction();

        switch (action) {
            case MotionEvent.ACTION_DOWN:
                top = initTop = imageView.getTop();
                bottom = initBootom = imageView.getBottom();
                break;
            case MotionEvent.ACTION_MOVE:
                final float preY = y;
                float nowY = ev.getY();
                int dalteY = (int) (nowY - preY);
                if (!isCount) {
                    dalteY = 0;
                }

                //当前往上滑,脱离了阻尼的范畴
                if (dalteY < 0 && top <= initTop) {
                    return;
                }

                isNeedMove();

                if (isMoveing) {
                    //保存位置信息
                    if (normal.isEmpty()) {
                        normal.set(inner.getLeft(), inner.getTop(), inner.getRight(), inner.getBottom());
                    }
                    //移动布局
                    inner.layout(inner.getLeft(), inner.getTop() + dalteY / 5, inner.getRight(), inner.getBottom() + dalteY / 5);

                    top = top - dalteY / 5;
                    bottom = initBootom;
                    imageView.layout(imageView.getLeft(), top, imageView.getRight(), bottom);
                    isCount = true;
                    y = nowY;
                }

                break;
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                isMoveing = false;
                if (isNeedAnimation()) {
                    animation();
                }
                break;
            default:
                break;
        }
    }

    // 是否需要开启动画
    public boolean isNeedAnimation() {
        return !normal.isEmpty();
    }

    /***
     * 回缩动画
     */
    public void animation() {
        AnimatorSet set = new AnimatorSet();
        ObjectAnimator animator = ObjectAnimator.ofFloat(imageView, "translationY", top, initTop);
        ObjectAnimator animator2 = ObjectAnimator.ofFloat(inner, "translationY", inner.getTop(), normal.top);
        //此处定义的是100毫秒,这里有个弊端:那就是当你100毫秒没有执行完又点击滑动了,此时图片的高度就变大了,会一直变大,
        // 你可以把时间设置的更短一些,让他掌握不了人类的速度,或者设置一个标签
        set.setDuration(100);
        set.playTogether(animator, animator2);
        set.start();
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                Float value = (Float) animation.getAnimatedValue();
                showLog.show("" + value);
                imageView.layout(imageView.getLeft(), initTop, imageView.getRight(), (int) (initBootom - value));
            }
        });

        inner.layout(normal.left, normal.top, normal.right, normal.bottom);
        normal.setEmpty();
        isCount = false;
        y = 0;// 手指松开要归0.

    }

    public void isNeedMove() {
        int offset = inner.getMeasuredHeight() - getHeight();
        int scrollY = getScrollY();
        // 0是顶部,后面那个是底部
//      if (scrollY == 0 || scrollY == offset) {
//          isMoveing = true;
//      }
        if (scrollY == 0) {
            isMoveing = true;
        }
    }
}

可以看到,从上到下,同一个套路,只不过改了一点点,那我们就来找不同:

    public void setImageView(ImageView imageView) {
        this.imageView = imageView;
    }

既然要指定一个图片,这个肯定少不了的。

case MotionEvent.ACTION_DOWN:
      top = initTop = imageView.getTop();
      bottom = initBootom = imageView.getBottom();
      break;

获取最初的图片顶部和底部。

//当下往上滑,脱离了阻尼的范畴
if (dalteY < 0 && top <= initTop) {
      return;
}

这个因为我们只关注顶部图片,所以当我们从下往上滑的时候就不管了;其实这里应该考虑一下怎么处理,如果是放大状态往上拉应该有一个过度过程,但这里我们先不考虑,也不去考虑,根据自己的需求自己来,如果套路我们都摸清楚了,怎么玩还不是我们说了算。

if (isMoveing) {
                    //保存位置信息
                    if (normal.isEmpty()) {
                        normal.set(inner.getLeft(), inner.getTop(), inner.getRight(), inner.getBottom());
                    }
                    //移动布局
                    inner.layout(inner.getLeft(), inner.getTop() + dalteY / 5, inner.getRight(), inner.getBottom() + dalteY / 5);

                    top = top - dalteY / 5;
                    bottom = initBootom;
                    imageView.layout(imageView.getLeft(), top, imageView.getRight(), bottom);
                    isCount = true;
                    y = nowY;
                }

这里不同的就是

top = top - dalteY / 5;
bottom = initBootom;
imageView.layout(imageView.getLeft(), top, imageView.getRight(), bottom);

这里可能会有一些奇怪,这里要往下拉,所以,图片上面会有一些留白,但是这是我们不允许的,我们要抢占这段留白,但是图片有突破不了ScrollView,所以这里要

android:clipChildren="false"

用到这个,这个是什么意思呢,看名字,裁剪子View

android 关于 clipToPadding 和 clipChildren区别和作用

再一个不同就是回归动画:

public void animation() {
        AnimatorSet set = new AnimatorSet();
        ObjectAnimator animator = ObjectAnimator.ofFloat(imageView, "translationY", top, initTop);
        ObjectAnimator animator2 = ObjectAnimator.ofFloat(inner, "translationY", inner.getTop(), normal.top);
        //此处定义的是100毫秒,这里有个弊端:那就是当你100毫秒没有执行完又点击滑动了,此时图片的高度就变大了,会一直变大,
        // 你可以把时间设置的更短一些,让他掌握不了人类的速度,或者设置一个标签
        set.setDuration(100);
        set.playTogether(animator, animator2);
        set.start();
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                Float value = (Float) animation.getAnimatedValue();
                showLog.show("" + value);
                imageView.layout(imageView.getLeft(), initTop, imageView.getRight(), (int) (initBootom - value));
            }
        });

        inner.layout(normal.left, normal.top, normal.right, normal.bottom);
        normal.setEmpty();
        isCount = false;
        y = 0;// 手指松开要归0.

    }

这里用的是属性动画,两个View的动画同时执行,并不断的改变ImageView的位置信息。

其余的都一样了。
贴一下布局:

<?xml version="1.0" encoding="utf-8"?>
<com.xiey94.damp.view.MyScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/myScrollView"
    android:scrollbars="none"
    android:clipChildren="false"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

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

        <RelativeLayout
            android:clipChildren="false"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <ImageView
                android:id="@+id/image"
                android:layout_width="match_parent"
                android:layout_height="240dip"
                android:scaleType="centerCrop"
                android:src="@drawable/test" />

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_centerInParent="true"
                android:gravity="center"
                android:orientation="vertical">

                <ImageView
                    android:id="@+id/header"
                    android:layout_width="60dip"
                    android:layout_height="60dip"
                    android:scaleType="centerCrop"
                    android:src="@mipmap/ic_launcher_round" />

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="登录"
                    android:textColor="#ffffff"
                    android:textSize="16sp" />
            </LinearLayout>

        </RelativeLayout>

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="10dip"
            android:text="@string/big_text"
            android:textSize="18sp" />
    </LinearLayout>
</com.xiey94.damp.view.MyScrollView>

还有根据上面差不多的一样的代码,也是参考后缝缝补补

package com.xiey94.damp.view;

import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ImageView;
import android.widget.ScrollView;

import com.xiey94.damp.util.showLog;

/**
 * @author : xiey
 * @project name : As30.
 * @package name  : com.xiey94.damp.view.
 * @date : 2018/1/22.
 * @signature : do my best.
 * @from : http://www.haolizi.net/example/view_799.html
 * @explain :
 */

public class PersonalScrollView extends ScrollView {

    //孩子VIew,也就是整个
    private View inner;
    //点击时的Y坐标
    private float touchY;
    //Y轴滑动的距离
    private float deltaY;
    //首次点击的Y坐标
    private float initTouchY;
    //记录位置
    private Rect normal = new Rect();
    //是否开始移动
    private boolean isMoveing = false;
    //背景图控件
    private ImageView imageView;
    //初始高度
    private int initTop, initBottom;
    //拖动时高度
    private int current_Top, current_Bottom;

    //状态:上部、下部、默认
    private enum State {
        UP, DOWN, NORMAL
    }

    //默认状态
    private State state = State.NORMAL;

    //注入背景图
    public void setImageView(ImageView imageView) {
        this.imageView = imageView;
    }


    public PersonalScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onFinishInflate() {
        if (getChildCount() > 0) {
            inner = getChildAt(0);
        }
        super.onFinishInflate();
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if (inner != null) {
            commonTouchEvent(ev);
        }
        return super.onTouchEvent(ev);
    }

    /**
     * 触摸事件
     */
    public void commonTouchEvent(MotionEvent ev) {
        int action = ev.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                initTouchY = ev.getY();
                current_Top = initTop = imageView.getTop();
                current_Bottom = initBottom = imageView.getBottom();
                break;
            case MotionEvent.ACTION_MOVE:
                touchY = ev.getY();
                //滑动距离
                deltaY = touchY - initTouchY;
                //首次Touch要判断方位
                if (deltaY < 0 && state == State.NORMAL) {
                    //往上滑
                    state = State.UP;
                } else if (deltaY > 0 && state == State.NORMAL) {
                    //往下滑
                    state = State.DOWN;
                }

                if (state == State.UP) {
                    deltaY = deltaY < 0 ? deltaY : 0;
                    isMoveing = false;
                } else if (state == State.DOWN) {
                    if (getScaleY() <= deltaY) {
                        isMoveing = true;
                    }
                    deltaY = deltaY < 0 ? 0 : deltaY;
                }

                if (isMoveing) {
                    if (normal.isEmpty()) {
                        normal.set(inner.getLeft(), inner.getTop(), inner.getRight(), inner.getBottom());
                    }

                    //移动布局
                    float inner_move_H = deltaY / 5;

                    inner.layout(normal.left, (int) (normal.top + inner_move_H), normal.right, (int) (normal.bottom + inner_move_H));

                    float image_move_H = deltaY / 10;
                    current_Top = (int) (initTop - image_move_H * 2);
                    current_Bottom = (int) (initBottom);
                    imageView.layout(imageView.getLeft(), current_Top, imageView.getRight(), current_Bottom);

                }
                break;
            case MotionEvent.ACTION_UP:
                if (isNeedAnimation()) {
                    animation();
                }

                //到顶部的时候
                if (getScaleY() == 0) {
                    state = State.NORMAL;
                }

                isMoveing = false;
                touchY = 0;
                break;
            default:
                break;
        }
    }

    public void animation() {
        ObjectAnimator animator = ObjectAnimator.ofFloat(imageView, "translationY", current_Top, initTop);
        ObjectAnimator animator2 = ObjectAnimator.ofFloat(inner, "translationY", inner.getTop(), normal.top);
        AnimatorSet set = new AnimatorSet();
        set.setDuration(200);
        set.playTogether(animator, animator2);
        set.start();
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                Float value = (Float) animation.getAnimatedValue();
                showLog.show("" + value);
                imageView.layout(imageView.getLeft(), initTop, imageView.getRight(), (int) (initBottom - value));
            }
        });
        inner.layout(normal.left, normal.top, normal.right, normal.bottom);
        normal.setEmpty();
    }

    /**
     * 是否需要开启动画
     */
    public boolean isNeedAnimation() {
        return !normal.isEmpty();
    }


}

和上面是一样的套路,代码几乎不变;但是有一点需要注意,那就是那个动画时间,如果你不停的在那快速滑动,导致一个动画没执行完另一个动画又开始了,最终图片就出差错了,不过一般没有人会这么无聊,而且你也可以在那设置一个标签卡住。

还有最后一个也是一样的效果,不过加了底部也有阻尼效果的作用:

这里写图片描述

package com.xiey94.damp.view;

import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.ScrollView;
import android.widget.Scroller;

import com.xiey94.damp.util.showLog;

/**
 * @author : xiey
 * @project name : As30.
 * @package name  : com.xiey94.damp.view.
 * @date : 2018/1/19.
 * @signature : do my best.
 * @from : https://www.cnblogs.com/anni-qianqian/p/5755734.html
 * @explain :
 */

public class DampView extends ScrollView {

    public static final int DURATION = 200;
    private Scroller mScroller;
    private int top;
    private float startY, currentY;
    private ImageView imageView;
    private int imageViewH;
    private boolean isFirst = true;
    private View inner;

    public DampView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mScroller = new Scroller(context);
    }

    public void setImageView(ImageView imageView) {
        this.imageView = imageView;
    }

    @Override
    protected void onFinishInflate() {
        if (getChildCount() > 0) {
            inner = getChildAt(0);
        }
        super.onFinishInflate();
    }

    @Override
    protected int computeScrollDeltaToGetChildRectOnScreen(Rect rect) {
        return 0;
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        super.dispatchTouchEvent(ev);
        int action = ev.getAction();
        if (!mScroller.isFinished()) {
            return super.onTouchEvent(ev);
        }
        currentY = ev.getY();

        switch (action) {
            case MotionEvent.ACTION_DOWN:
                top = imageView.getBottom();
                imageViewH = imageView.getHeight();
                startY = currentY;
                break;
            case MotionEvent.ACTION_MOVE:
                if (imageView.isShown() && getScrollY() == 0) {
                    if (isFirst) {
                        startY = currentY;
                        isFirst = false;
                    }
                    int height = (int) (top + (currentY - startY) / 5);
                    ViewGroup.LayoutParams params = imageView.getLayoutParams();
                    params.height = height;
                    imageView.setLayoutParams(params);
                }

                if (inner.getMeasuredHeight() <= getScrollY() + getHeight()) {
                    if (isFirst) {
                        startY = currentY;
                        isFirst = false;
                    }

                    int height = (int) ((currentY - startY) / 5);
                    inner.layout(inner.getLeft(), 0, inner.getRight(), inner.getMeasuredHeight() - height);
                }

                break;
            case MotionEvent.ACTION_UP:
                mScroller.startScroll(0, imageView.getBottom(), 0, imageViewH - imageView.getBottom(), DURATION);
                isFirst = true;
                break;
            default:
                break;
        }
        return true;
    }

    @Override
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            ViewGroup.LayoutParams params = imageView.getLayoutParams();
            params.height = mScroller.getCurrY();
            showLog.show(mScroller.getCurrY());
            imageView.setLayoutParams(params);
        }
    }
}

这个跟之前的不一样,他是用到了Scroller这个辅助类;开始的还是一样,多了一个scroller的初始化,

在ACTION_MOVE中做了两段判断

if (imageView.isShown() && getScrollY() == 0) {
                    if (isFirst) {
                        startY = currentY;
                        isFirst = false;
                    }
                    int height = (int) (top + (currentY - startY) / 5);
                    ViewGroup.LayoutParams params = imageView.getLayoutParams();
                    params.height = height;
                    imageView.setLayoutParams(params);
                }

过滤第一次,防止跳跃,用LayoutParams来改变ImageView的大小,这个主要是作用于顶部的,第二个判断是作用与底部的:

if (inner.getMeasuredHeight() <= getScrollY() + getHeight()) {
                    if (isFirst) {
                        startY = currentY;
                        isFirst = false;
                    }

                    int height = (int) ((currentY - startY) / 5);
                    inner.layout(inner.getLeft(), 0, inner.getRight(), inner.getMeasuredHeight() - height);
                }

这里为什么一个用layout一个用LayoutParams:

view.layout()或者LayoutParams改变View的大小

inner.layout(inner.getLeft(), 0, inner.getRight(), inner.getMeasuredHeight() - height);

这里解释一下:主要是底部,因为是从下往上滑,所以这个height是个负值,所以负负得正,增加高度。

ACTION_UP:

 mScroller.startScroll(0, imageView.getBottom(), 0, imageViewH - imageView.getBottom(), DURATION);

当从上往下滑图片变大松开之后要复原,这里从imageView.getBottom()增加一个imageViewH - imageView.getBottom(),这是怎样的一个变化过程,其实把它们加起来就是一个imageViewH,也就是原始图片高度。

@Override
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            ViewGroup.LayoutParams params = imageView.getLayoutParams();
            params.height = mScroller.getCurrY();
            showLog.show(mScroller.getCurrY());
            imageView.setLayoutParams(params);
        }
    }

学scroller的时候,这个没少见,必备的一个回调方法。
如果没有滑动完就让他继续滑动,并不停的高边图片的高度达到还原。

这里就有一个疑问了,顶部是没有问题的,但是在底部,我并没有针对底部做什么操作处理,底部是怎么还原的呢?至今还不是很理解,希望有知道的大神能够告知。




所有代码:GitHub
或者CSDN资源:damp



参考1:ScrollView的阻尼回弹效果实现(仿qq空间)
参考2:android 实现背景图片伸缩ScrollView之阻尼特效(仿多米,PaPa个人页面特效也称为阻尼效果)
参考3:DampView阻尼效果


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值