你知道android中的视差特效吗

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/H_Gao/article/details/53511696

阻尼效果(视差特效)

空间,微博很多地方都有这种下拉出现的”阻尼“效果,这种效果最早在ios上出现,如今android上这种功能也是很常见了。

先看效果图:
image

该功能可以分为两个点:

  1. 当ListView下拉的时候,顶部的HeaderView会有一个拉长的效果;
  2. 当下拉一段距离后,ListView会复位,执行一个简单的回弹动画。

这个功能实现起来挺简单的,下面来介绍如何实现:

第一个功能:(阻尼效果)

来看下关键代码:

	@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解释如下:

扫描二维码关注公众号,回复: 3659284 查看本文章
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<String>(MainActivity.this, android.R.layout.simple_list_item_1,Cheeses.NAMES));
	}
}

view_header.xml

<?xml version="1.0" encoding="UTF-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <ImageView
        android:id="@+id/iv"
        android:layout_width="match_parent"
        android:layout_height="180dp"
        android:scaleType="centerCrop"
        android:src="@drawable/parallax" />

</LinearLayout>

Activity中的逻辑比较简单,初始化下数据。

另外,注意在view_header.xml中设置图片采用的拉伸方式是centerCrop,scaleType有很多属性,这里必须采用这种方式处理,采用其他的会使图片拉伸的很不美观,关于scaleType的多种属性这里就不详细说明了,感兴趣的童鞋可以自己研究下,后面如果有时间也会有这方面的笔记贴出来,O(∩_∩)O。

欢迎关注我的公众号 ,不定期会有优质技术文章推送 。

微信扫一扫下方二维码即可关注
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/H_Gao/article/details/53511696