你知道android中的视差特效吗

阻尼效果(视差特效)

空间,微博很多地方都有这种下拉出现的”阻尼“效果,这种效果最早在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解释如下:

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。

posted @ 2016-12-07 23:06  凌风天涯  阅读(197)  评论(0编辑  收藏  举报