要点:
1. ImageView 设置: android:scaleType="centerCrop" 中间压缩,两边展示,只显示控件高度
2. 监听 ListView 的overScrollBy方法 下拉滑动
deltaY: 瞬时变化变量 ListView 的
int newHeight = (int) (mImage.getHeight() + Math.abs(deltaY / 3.0f));
mImage.getLayoutParams().height = newHeight; // 下拉视差动画,展示全图
mImage.requestLayout();
3. MotionEvent.ACTION_UP 松手
int startHeight = mImage.getHeight(); // 已经下拉高度
从 startHeight -> mOriginalHeight 通过估值器 计算出来,在规定时间内,【图片从给一个高度到另外一个高度】
动态设置 图片高度
mOriginalHeight = mImage.getHeight(); // 显示高度
drawableHeight = mImage.getDrawable().getIntrinsicHeight(); // 图片原始高度滑动
核心代码 MyListView
package com.itheima.parallaxdemo.ui;
import android.animation.ValueAnimator;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.animation.OvershootInterpolator;
import android.view.animation.TranslateAnimation;
import android.widget.ImageView;
import android.widget.ListView;
/**
* 视差特效ListView
* overScrollBy
* @author poplar
*
*/
public class MyListView extends ListView {
private static final String TAG = "TAG";
private int mOriginalHeight;
private int drawableHeight;
private ImageView mImage;
public MyListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public MyListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyListView(Context context) {
super(context);
}
/**
* 设置ImageView图片, 拿到引用
* @param mImage
*/
public void setParallaxImage(ImageView mImage) {
this.mImage = mImage;
mOriginalHeight = mImage.getHeight(); // 160,当前显示图片高度
drawableHeight = mImage.getDrawable().getIntrinsicHeight(); // 488,图片原始高度
Log.d(TAG, "height: " + mOriginalHeight + " drawableHeight: " + drawableHeight);
}
@Override
protected boolean overScrollBy(int deltaX, int deltaY,
int scrollX, int scrollY, int scrollRangeX, int scrollRangeY,
int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) {
// deltaY : 竖直方向的瞬时偏移量 / 变化量 dx 顶部到头下拉为-, 底部到头上拉为+
// scrollY : 竖直方向的偏移量 / 变化量
// scrollRangeY : 竖直方向滑动的范围
// maxOverScrollY : 竖直方向最大滑动范围
// isTouchEvent : 是否是手指触摸滑动, true为手指, false为惯性
Log.d(TAG, "deltaY: " +deltaY + " scrollY: " + scrollY + " scrollRangeY: " + scrollRangeY
+ " maxOverScrollY: " + maxOverScrollY + " isTouchEvent: " + isTouchEvent);
// 手指拉动 并且 是下拉
if(isTouchEvent && deltaY < 0){
// 把拉动的瞬时变化量的绝对值交给Header, 就可以实现放大效果
if(mImage.getHeight() <= drawableHeight){
// 除以3避免 滑动过大
int newHeight = (int) (mImage.getHeight() + Math.abs(deltaY / 3.0f));
// 高度不超出图片最大高度时,才让其生效
mImage.getLayoutParams().height = newHeight;
mImage.requestLayout();
}
}
return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX,
scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent);
}
@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 ValueAnimator.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();
}
});
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));
}
}
通过 Animation 继承 ResetAnimation
package com.itheima.parallaxdemo.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 代码实现:
package com.itheima.parallaxdemo;
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.itheima.parallaxdemo.ui.MyListView;
import com.itheima.parallaxdemo.util.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);
mListView.setOverScrollMode(View.OVER_SCROLL_NEVER);
// 加Header
final View mHeaderView = View.inflate(MainActivity.this, R.layout.view_header, null);
final ImageView mImage = (ImageView) mHeaderView.findViewById(R.id.iv);
mListView.addHeaderView(mHeaderView);
mHeaderView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
// 当布局填充结束之后, 此方法会被调用
mListView.setParallaxImage(mImage);
mHeaderView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
});
// 填充数据
mListView.setAdapter(new ArrayAdapter<String>(MainActivity.this, android.R.layout.simple_list_item_1, Cheeses.NAMES));
}
}
上图效果: