有趣的视差效果(Parallax effect)

Parallax (视差)这个名词是源自希腊文的παράλλαξις(parallaxis),意思是”改变”。从不同的位置观察,越近的物体有着越大的视差,因此视差可以确定物体的距离。——Wikipedia

简单的说就是,距离理我们远的东西我们看起来移动的慢,距离近的东西移动速度看起来更快。效果如图所示:

Parallax

“Parallax”作者Nathaniel Domek 来自维基共享资源

用这个简单的原理就可以实现android应用里常见的欢迎页动画了,效果图如下:(gif录制软件帧率太低,效果不好,点开看动图)
demo1 demo2

先从右图(横向的视差效果)开始解释如何实现吧。这个是一个ViewPager,我们只要自定义它的翻页时候的动画就行,所以我们只要自己实现一个ViewPager.PageTransformer就可以了,我们的视差效果也都是在里面实现的。每一页的布局如下,最上面的是big_title,中间的是icon_img,下面的叫small_title.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
                android:background="@drawable/p1"
              android:layout_width="match_parent"
              android:layout_height="match_parent">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceLarge"
        android:text="A Large Title"
        android:id="@+id/big_title"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        android:textSize="45sp"
        android:textColor="#fff"
        android:layout_marginTop="70dp"/>

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/icon_img"
        android:src="@drawable/icon1"
        android:layout_centerInParent="true"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="16dp"
        android:textSize="20sp"
        android:text="   An awesome parallax effect!\nBring more joy to the user"
        android:id="@+id/small_title"
        android:layout_below="@+id/icon_img"
        android:layout_centerInParent="true"
        android:textColor="#fff"
        android:layout_marginTop="32dp"/>
</RelativeLayout>

我们的目标是实现这三个东西已不同的速度向滑动方向移动。

  • 如何移动呢?

用View的TranslationX(view相对于最左的偏移)来实现对View的移动。

  • 不同的速度如何确定?

假设big_title距离我们最近移动速度最快,small_title距离我们最远移动速度最慢。icon_img在中间。

下面来看PageTransformer的代码:

/**
 * Created by Jiaqi Ning on 28/4/2015.
 */
public class ParallaxTransformer implements ViewPager.PageTransformer {
    private boolean isSpeedReverse;
    private int[] resIds;
    private float speedEffect;
    private float distanceEffect;

    public ParallaxTransformer(float speed,float distance,
                               int[] resIds,boolean isSpeedReverse){
        this.isSpeedReverse = isSpeedReverse;
        this.resIds = resIds;
        this.speedEffect = speed;
        this.distanceEffect = distance;
        if (isSpeedReverse)
            this.speedEffect *= -1;
    }
    @Override
    public void transformPage(View page, float position) {

       float moveLength = page.getWidth() * speedEffect;
        for (int i = 0;i < resIds.length;i++){
            View view = page.findViewById(resIds[i]);
            if (view != null){
                view.setTranslationX(moveLength * position);
            }
            moveLength *= distanceEffect;
        }

    }
}

首先构造这个类需要加入Parallax效果的views的id数组(index越小的说明“距离”越近,为什么请看代码)。moveLength相当于移动的速度大小,最终要移动的距离由参数position*moveLength来决定(在ViewPager里position的范围是-1~1,当前页完全沾满屏幕在正中间的时候position返回0,当前页完全在左边不可见时为-1,同理滑动到右边完全不可见时是1)。

这样用就可以啦:

 // Instantiate a ViewPager and a PagerAdapter.
        mPager = (ViewPager) findViewById(R.id.pager);
        mPagerAdapter = new SlidePagerAdapter(getSupportFragmentManager(), MainActivity.TYPE.NORMAL_TYPE);
        mPager.setAdapter(mPagerAdapter);
        int[] resId = {R.id.title_big,R.id.icon_img,R.id.small_title};
        mPager.setPageTransformer(true, new ParallaxTransformer(speed, distance, resId, false));

上面实现的只是横向滑动的Parallax效果,垂直的怎么实现呢?已这个界面为例QQ截图20150505213817图片应该保持和页面的运动方向相反,比如整个页面向上运动,图片应该向下运动。为什么是图片要和页面运动方向相反呢?因为当页面向上滚动的时候,图片也随之向上移动了,如果我们设置translationY让它已相反的方向移动,就造成了一种它在慢速移动的效果。

这里还会有一个问题,因为垂直滚动需要用ScrollView,但是自带的ScrollView并没有提供给我们onScrollChangeListener,所以我们要自己写一个这样的接口来告知我们页面滚动的情况:

/**
 * @author Cyril Mottier
 */
public class NotifyingScrollView extends ScrollView {
    public interface OnScrollChangedListener {
        void onScrollChanged(ScrollView who, int l, int t, int oldl, int oldt);
    }
    private OnScrollChangedListener mOnScrollChangedListener;
    
    public NotifyingScrollView(Context context) {
        super(context);
    }
    public NotifyingScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public NotifyingScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }


    public void setOnScrollChangedListener(OnScrollChangedListener listener) {
        mOnScrollChangedListener = listener;
    }

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);
        if (mOnScrollChangedListener != null) {
            mOnScrollChangedListener.onScrollChanged(this, l, t, oldl, oldt);
        }
    }

}

然后看我们是如何设置图片的translationY的:

  private NotifyingScrollView.OnScrollChangedListener mOnScrollChangedListener = new NotifyingScrollView.OnScrollChangedListener() {
        public void onScrollChanged(ScrollView who, int l, int t, int oldl, int oldt) {
            if (l < mCoverImageHeight){
                final float ratio = (float) Math.min(Math.max(t, 0), mCoverImageHeight) / mCoverImageHeight;
                mCoverImageView.setTranslationY(ratio* mCoverImageHeight * verticalParalllaxSpeed);
            }
        }
    };

OK!结束了! Demo源代码 在github上:https://github.com/qianlvable/ParallaxEffectDemo

发表评论

电子邮件地址不会被公开。 必填项已用*标注

您可以使用这些HTML标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>