模仿微信朋友圈界面,主要实现:1、能够下拉背景图随着变化,2、松手能够弹回并带着动画效果。
一.自定义一个View并继承ListView;
实现以上效果,要重写ListView的两个方法:
overScrollBy();onTouchEvent()
overScrollBy()方法有9个参数,这里解释几个实现以上效果的主要参数:
//deltaY:竖直方向的瞬时值移量、变化量dx,顶部到头为 - ,底部到头为 +
//scrollY:竖直方向的偏移量/变化量
//scrollRangeY:竖直方向滑动的范围
//maxOverScrollY:竖直方向最大滑动范围
//isTouchEvent:是否是手指触摸,如果是手指拉为true,如果是惯性为false
根据deltaY、isTouchEvent的值,可以进行判断是否是在进行手指下拉。如果是,我们就可以把拉动瞬间变化量的绝对值给ListView的Header,从而实现放大的效果,注意在进行放大时,放大的高度不能超过原始图片的大小。
代码如下
@Override
protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY,
int scrollRangeX, int scrollRangeY, int maxOverScrollX,
int maxOverScrollY, boolean isTouchEvent) {
Log.i(TAG, "overScrollBy: deltaY:" + deltaY + " scrollY:" + scrollY + " scrollRangeY:" +
scrollRangeY + " maxOverScrollY:" + maxOverScrollY + " isTouchEvent:" + isTouchEvent);
//手指拉动并且是下拉
if (isTouchEvent && deltaY < 0) {
//把拉动的瞬时变化量的绝对值交给Header,就可以实现放大的效果
if (mImageView.getHeight() <= drawableHeight) {
//高度不超出图片的大小时,才让其生效
int newHeight = (int) (mImageView.getHeight()+ Math.abs(deltaY/3.0f));
mImageView.getLayoutParams().height = newHeight;
mImageView.requestLayout();
}
}
return super.overScrollBy(deltaX, deltaY, scrollX, scrollY,
scrollRangeX, scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent);
}
onTouchEvent()方法主要是判断手指抬起,抬起时进行一系列的动画:
弹回动画执行方式可以为属性动画、自定义动画
属性动画:创建一个属性动画对象,添加动画更新监听,AnimatorUpdateListener(),实现该接口的onAnimationUpdate方法,然后获取一个百分数,利用估值器获取更新后的图片高度,将新的高度设置为背景图片的高度。然后设置震动,代码如下:
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_UP:
//执行回弹动画,方式一:属性动画/值动画
//从当前的imageView.getHeight(),执行到原始的高
final int startHeight=mImageView.getHeight();
final int endHeight=originalHeight;
valueAnimation(startHeight,endHeight);
break;
default:
break;
}
return super.onTouchEvent(ev);
}
private void valueAnimation(final int startHeight, final int endHeight){
ValueAnimator mValueAnimator=ValueAnimator.ofInt(1);
mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float fraction=animation.getAnimatedFraction();
//百分数
Log.i(TAG, "onAnimationUpdate: "+fraction);
Integer newHeight= evaluate(fraction,startHeight,endHeight);
mImageView.getLayoutParams().height=newHeight;
mImageView.requestLayout();
}
});
mValueAnimator.setInterpolator(new OvershootInterpolator(4));
mValueAnimator.setDuration(500);
mValueAnimator.start();
}
/**
* 估值器
* @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));
}
自定义动画:代码如下:
public class ResetAnimation extends Animation {
private ImageView mImageView;
private int mStartHeight;
private int mEndHeight;
public ResetAnimation(ImageView imageView, int startHeight, int endHeight) {
mImageView=imageView;
mStartHeight=startHeight;
mEndHeight=endHeight;
setInterpolator(new OvershootInterpolator());
setDuration(500);
}
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
//interpolatedTime 0.0f~1.0f
Integer newHeight= evaluate(interpolatedTime,mStartHeight,mEndHeight);
mImageView.getLayoutParams().height=newHeight;
mImageView.requestLayout();
super.applyTransformation(interpolatedTime, t);
}
public Integer evaluate(float fraction, Integer startValue, Integer endValue){
int startInt=startValue;
return (int)(startInt+fraction*(endValue-startInt));
}
}
然后在onTouchEvent方法中修改:
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_UP:
// 方式二:自定义Animation
//从当前的imageView.getHeight(),执行到原始的高
final int startHeight=mImageView.getHeight();
final int endHeight=originalHeight;
// valueAnimation(startHeight,endHeight);
ResetAnimation reset=new ResetAnimation(mImageView,startHeight,endHeight);
startAnimation(reset);
break;
default:
break;
}
return super.onTouchEvent(ev);
}
注意:在引用自定义的ListView时,要给它添加一个绘制的监听,就是监听布局是否填充完毕,然后再去获取背景图片和所在控件的高,不然获取到的控件高为0。
完整代码:
MainActivity
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final MyListView myListView= (MyListView) findViewById(R.id.lv);
//添加Header
final View headerView =View.inflate(this,R.layout.view_header,null);
final ImageView imageView= (ImageView) headerView.findViewById(R.id.iv);
myListView.addHeaderView(headerView);
myListView.setOverScrollMode(View.OVER_SCROLL_NEVER);
headerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
//当布局填充之后,此方法会被调用
myListView.setParallaxImage(imageView);
headerView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
});
//填充数据
myListView.setAdapter(new ArrayAdapter(MainActivity.this,android.R.layout.simple_list_item_1, Cheeses.NAMES));
}
}
MyListView
import android.animation.ValueAnimator;
import android.content.Context;
import android.support.annotation.IntegerRes;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.animation.OvershootInterpolator;
import android.widget.ImageView;
import android.widget.ListView;
/**
* 视差特效ListView
* Created by Administrator on 2017/4/13.
*/
public class MyListView extends ListView {
private final String TAG = "SENDI";
private int drawableHeight;
private int originalHeight;
private ImageView mImageView;
public MyListView(Context context) {
super(context);
}
public MyListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY,
int scrollRangeX, int scrollRangeY, int maxOverScrollX,
int maxOverScrollY, boolean isTouchEvent) {
Log.i(TAG, "overScrollBy: deltaY:" + deltaY + " scrollY:" + scrollY + " scrollRangeY:" +
scrollRangeY + " maxOverScrollY:" + maxOverScrollY + " isTouchEvent:" + isTouchEvent);
//手指拉动并且是下拉
if (isTouchEvent && deltaY < 0) {
//把拉动的瞬时变化量的绝对值交给Header,就可以实现放大的效果
if (mImageView.getHeight() <= drawableHeight) {
//高度不超出图片的大小时,才让其生效
int newHeight = (int) (mImageView.getHeight()+ Math.abs(deltaY/3.0f));
mImageView.getLayoutParams().height = newHeight;
mImageView.requestLayout();
}
}
return super.overScrollBy(deltaX, deltaY, scrollX, scrollY,
scrollRangeX, scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent);
}
/**
* 设置ImageView图片,拿到引用
*/
public void setParallaxImage(ImageView imageView) {
mImageView = imageView;
originalHeight = imageView.getHeight();//160
// int measureHeight=imageView.getMeasuredHeight();
drawableHeight = imageView.getDrawable().getIntrinsicHeight();//图片的高
Log.i(TAG, "setParallaxImage: height:" + originalHeight +
" drawableHeight:" + drawableHeight);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_UP:
//执行回弹动画,方式一:属性动画/值动画
// 方式二:自定义Animation
//从当前的imageView.getHeight(),执行到原始的高
final int startHeight=mImageView.getHeight();
final int endHeight=originalHeight;
// valueAnimation(startHeight,endHeight);
ResetAnimation reset=new ResetAnimation(mImageView,startHeight,endHeight);
startAnimation(reset);
break;
default:
break;
}
return super.onTouchEvent(ev);
}
private void valueAnimation(final int startHeight, final int endHeight){
ValueAnimator mValueAnimator=ValueAnimator.ofInt(1);
mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float fraction=animation.getAnimatedFraction();
//百分数
Log.i(TAG, "onAnimationUpdate: "+fraction);
Integer newHeight= evaluate(fraction,startHeight,endHeight);
mImageView.getLayoutParams().height=newHeight;
mImageView.requestLayout();
}
});
mValueAnimator.setInterpolator(new OvershootInterpolator(4));
mValueAnimator.setDuration(500);
mValueAnimator.start();
}
/**
* 估值器
* @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));
}
}
import android.view.animation.Animation;
import android.view.animation.OvershootInterpolator;
import android.view.animation.Transformation;
import android.widget.ImageView;
/**
* Created by Administrator on 2017/4/13.
*/
public class ResetAnimation extends Animation {
private ImageView mImageView;
private int mStartHeight;
private int mEndHeight;
public ResetAnimation(ImageView imageView, int startHeight, int endHeight) {
mImageView=imageView;
mStartHeight=startHeight;
mEndHeight=endHeight;
setInterpolator(new OvershootInterpolator());
setDuration(500);
}
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
//interpolatedTime 0.0f~1.0f
Integer newHeight= evaluate(interpolatedTime,mStartHeight,mEndHeight);
mImageView.getLayoutParams().height=newHeight;
mImageView.requestLayout();
super.applyTransformation(interpolatedTime, t);
}
public Integer evaluate(float fraction, Integer startValue, Integer endValue){
int startInt=startValue;
return (int)(startInt+fraction*(endValue-startInt));
}
}