空间,微博很多地方都有这种下拉出现的”阻尼“效果,这种效果最早在ios上出现,如今android上这种功能也是很常见了。
先看效果图:
该功能可以分为两个点:
这个功能实现起来挺简单的,下面来介绍如何实现:
来看下关键代码:
@Override
protected boolean overScrollBy(int deltaX, int deltaY, int scrollX,
int scrollY, int scrollRangeX, int scrollRangeY,
int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) {
Log.d("TAG", "deltaY: " +deltaY + " scrollY: " + scrollY + " scrollRangeY: " + scrollRangeY
+ " maxOverScrollY: " + maxOverScrollY + " isTouchEvent: " + isTouchEvent);
// 手指拉动 并且 是下拉
if(isTouchEvent && deltaY < 0) {
// 把拉动的瞬时变化量的绝对值交给Header, 就可以实现放大效果
if(mImage.getHeight() <= drawableHeight ) {
//①将deltaY除以3使产生阻尼效果
int newHeight = mImage.getHeight() + Math.abs(deltaY/3.0f);
mImage.getLayoutParams().height = newHeight;
使View重绘
mImage.requestLayout();
}
}
return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX,
scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent);
}
看到上面的代码,该段逻辑主要针对第一个功能,要重写overScrollBy方法,通过监听下拉的移动的高度赋给ListView的HeaderView,使View重绘来实现这种效果。
注意上面的①处,将下拉的幅度按比例的缩小来赋值给图片的高度时,就会产生很难下拉的效果,即“阻尼”效果。
当手指松开时,需要执行回弹动画。手指松开,即ACTION_UP,重写onTouchEvent来实现相关逻辑。
看如下代码:
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_UP:
// 执行回弹动画, 方式一: 属性动画\值动画
// 从当前高度mImage.getHeight(), 执行动画到原始高度mOriginalHeight
final int startHeight = mImage.getHeight();
final int endHeight = mOriginalHeight;
// valueAnimator(startHeight, endHeight);
// 执行回弹动画, 方式二: 自定义Animation
ResetAnimation animation = new ResetAnimation(mImage, startHeight, endHeight);
startAnimation(animation);
break;
}
return super.onTouchEvent(ev);
}
private void valueAnimator(final int startHeight, final int endHeight) {
ValueAnimator mValueAnim = ValueAnimator.ofInt(1);
mValueAnim.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator mAnim) {
//①获取动画的百分比
float fraction = mAnim.getAnimatedFraction();
// percent 0.0 -> 1.0
Log.d(TAG, "fraction: " +fraction);
Integer newHeight = evaluate(fraction, startHeight, endHeight);
mImage.getLayoutParams().height = newHeight;
mImage.requestLayout();
}
});
//设置动画的插值器,OvershootInterpolator向前甩一定值后再回到原来位置
mValueAnim.setInterpolator(new OvershootInterpolator());
mValueAnim.setDuration(500);
mValueAnim.start();
}
public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
int startInt = startValue;
return (int)(startInt + fraction * (endValue - startInt));
}
看到上面的逻辑,在onTouchEvent监听到ACTION_UP后,需要执行回弹动画,这部分既可以用属性动画来实现,也可以自定义动画的方式来实现。
这两种方式都是需要将ListView重置为初始状态,不过是以动画的方式来实现。
注意上面①处的代码,float fraction = mAnim.getAnimatedFraction();getAnimatedFraction的API解释如下:
Returns the current animation fraction, which is the elapsed/interpolated
fraction used in the most recent frame update on the animation.
返回当前动画的百分比,这个百分比能够在动画执行中用于更新当前的片段。
上面除了采用属性动画外,还可以使用自定义动画的方式。
看下自定义动画的逻辑:
package com.wind.parallax.ui;
import android.view.animation.Animation;
import android.view.animation.OvershootInterpolator;
import android.view.animation.Transformation;
import android.widget.ImageView;
public class ResetAnimation extends Animation {
private final ImageView mImage;
private final int startHeight;
private final int endHeight;
public ResetAnimation(ImageView mImage, int startHeight, int endHeight) {
this.mImage = mImage;
this.startHeight = startHeight;
this.endHeight = endHeight;
setInterpolator(new OvershootInterpolator());
//设置动画执行时长
setDuration(500);
}
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
// interpolatedTime 0.0f -> 1.0f
Integer newHeight = evaluate(interpolatedTime, startHeight, endHeight);
mImage.getLayoutParams().height = newHeight;
mImage.requestLayout();
super.applyTransformation(interpolatedTime, t);
}
/**
* 类型估值器
* @param fraction
* @param startValue
* @param endValue
* @return
*/
public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
int startInt = startValue;
return (int)(startInt + fraction * (endValue - startInt));
}
}
该自定义动画就是将最上面的属性动画改写下,这部分的逻辑挺简单的,就不再重复解释了。
最后看下MainActivity和布局中的逻辑。
MainActivity.java
package com.wind.parallax;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import com.wind.parallax.ui.MyListView;
import com.wind.parallax.utils.Cheeses;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final MyListView mListView = (MyListView) findViewById(R.id.lv);
final View mHeaderView = View.inflate(MainActivity.this, R.layout.view_header, null);
final ImageView mImage = (ImageView) mHeaderView.findViewById(R.id.iv);
mHeaderView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@SuppressLint("NewApi")
@Override
public void onGlobalLayout() {
// 当布局填充结束之后, 此方法会被调用
mListView.setParallaxImage(mImage);
mHeaderView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
});
mListView.addHeaderView(mHeaderView);
mListView.setAdapter(new ArrayAdapter(MainActivity.this, android.R.layout.simple_list_item_1,Cheeses.NAMES));
}
}
view_header.xml
Activity中的逻辑比较简单,初始化下数据。
另外,注意在view_header.xml中设置图片采用的拉伸方式是centerCrop,scaleType有很多属性,这里必须采用这种方式处理,采用其他的会使图片拉伸的很不美观,关于scaleType的多种属性这里就不详细说明了,感兴趣的童鞋可以自己研究下,后面如果有时间也会有这方面的笔记贴出来,O(∩_∩)O。