顶部图片放大效果
接着上一篇的继续说,因为篇幅太长,所以切割了。
这次来看一个顶部图片放大的效果:
这个效果也很常见,说是很常见,但是我怎么没见到。。。
这里的代码其实跟上一个走的是同一个套路,只不过这次指定了一个图片控件:
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的时候,这个没少见,必备的一个回调方法。
如果没有滑动完就让他继续滑动,并不停的高边图片的高度达到还原。
这里就有一个疑问了,顶部是没有问题的,但是在底部,我并没有针对底部做什么操作处理,底部是怎么还原的呢?至今还不是很理解,希望有知道的大神能够告知。
参考1:ScrollView的阻尼回弹效果实现(仿qq空间)
参考2:android 实现背景图片伸缩ScrollView之阻尼特效(仿多米,PaPa个人页面特效也称为阻尼效果)
参考3:DampView阻尼效果