ViewPager切换时粘滞视差的效果--模仿美丽说HIGO全球买手圈的滑动效果

先来看看美丽说HIGO的全球买手圈滑动效果

观察效果发现

每次向左滑动时,右边的页4个View中,右下角的View速度最慢,视差最明显。
每次向右滑动时,左边的页4个View中,左上角的View速度最慢,视差最明显。
每次滑动时,View中的标题和View右上角的红色标注,都是随着滑动有属于自己的纬度的视差跟透明度的变化。

原理

需要获取到ViewPager中手势滑动的距离,根据具体,根据子View的位置做变化,子View中的成员也要以子View为标准再次变化,产生两层错落的视觉效果。

API

ViewPager.PageTransformer

A PageTransformer is invoked whenever a visible/attached page is scrolled. This offers an opportunity for the application to apply a custom transformation to the page views using animation properties.

As property animation is only supported as of Android 3.0 and forward, setting a PageTransformer on a ViewPager on earlier platform versions will be ignored.

public abstract void transformPage (View page, float position)

Apply a property transformation to the given page.

Parameters
page    Apply the transformation to this page
position    Position of page relative to the current front-and-center position of the pager. 0 is front and center. 1 is one full page position to the right, and -1 is one page position to the left.

要展示不同于默认滑屏效果的动画,实现 ViewPager.PageTransformer 接口,然后把它补充到 view pager 里就行了。这个接口只暴露了一个方法, transformPage() 。每次界面切换,这个方法都会为每个可见page(通常只有一个页面可见)和其即将消失的相邻page面调用一次。例如,第三页可见而且用户向第四页拖动, transformPage() 在操作的各个阶段为第二,三,四页分别调用。

在你 transformPage() 的实现中,基于当前界面上page的 position(position 由 transformPage() 方法的参数给出)决定哪些page需要被动画转换,这样你就能创建自己的动画。

position 参数表示特定page相对于屏幕中的page的位置。它的值在用户滑动页面过程中动态变化。当page填充屏幕,它的值为 0。当page刚从屏幕右边拖走,它的值为 1。如果用户在page 1和page 2间滑动到一半,那么page 1的 position 为 -0.5 并且page 2的 position 为 0.5。根据屏幕上页面的 position,你可以通过 setAlpha() , setTranslationX() 或 setScaleY() 这些方法设定页面属性来自定义滑动动画。

实现

有了符合的API,那剩下的就是实现了。
省略了ViewPager跟Indicator的代码部分了,专注来看下我们的PageTransformer部分跟自定义的VIew部分。

PageTransformer代码


public class StickyPagerTransformer implements PageTransformer {

    /*
    * 继承PageTransformer
    * 重写transformPage方法
    * 通过transformPage方法中的v参数,得到滑动的变化值
    * 获取transformPage方法中view的childView,设置变化
    * */

    private RecyclerView recyclerView;
    private float speed = 0.3f;


    /*在一次手势滑动中,关系到两页的变化
  * 向左滑动,关系到未滑动时处于左边的那一页和未滑动时显示的当前页
  * 向右滑动,关系到未滑动时处于右边的那一页和未滑动时显示的当前页
  * 两个页的变化都会回调到transformPage方法中
  * */
    @SuppressLint("NewApi")
    @Override
    public void transformPage(View pager, float v) {
        /*
         * -1
        /*position = 0,pager处于正中心,没有位移*/
        recyclerView = (RecyclerView) pager.findViewById(R.id.my_recycler_view);
        for (int i = 0; i < recyclerView.getChildCount(); i++) {
            ViewTestItem itemView = (ViewTestItem) recyclerView.getChildAt(i);
            float weight;
            if (v > 0) {
                weight = speed * i;
            } else {
                weight = speed * Math.abs(4 - i);
            }
            trans(itemView, v, weight);
        }
    }


    private void trans(ViewTestItem viewTestItem, float position, float offset) {
        float value = 0;
        if (viewTestItem != null) {
            float width = viewTestItem.getWidth();
            if (position != 0) {
                /*当position不为0时,不用区分position>0还是position<0
                * 因为每次滑动时,向右滑动位移为正,向左滑动位移为负
                * 想让item向右位移,
                * 那向左滑动时,偏移量就应该也为负值,只是比位移量绝对值小,才能出现粘滞视差的效果
                * 如果向左滑动,偏移量却为正值,那就不是粘滞的效果,而且相反方向的运动了
                * 那向右滑动时,偏移量就应该也为正值,只是比位移量绝对值小,才能出现粘滞视差的效果
                * 想让item向左位移
                * 那向左滑动时,偏移量就应该也为负值,只是比位移量绝对值大,才能出现粘滞视差的效果
                * 那向右滑动时,偏移量就应该也为正值,只是比位移量绝对值大,才能出现粘滞视差的效果
                * */
                value = (position * width * offset);
                /*设置item在X方向上位移*/
                viewTestItem.setTranslationX(value);
                viewTestItem.setOffset(position * offset);
            } else if (position == 0) {
                viewTestItem.setTranslationX(0);
                viewTestItem.setOffset(0);
            }
        }
    }
}

注释已经写的很清楚了,这里就不啰嗦了。

自定义View代码

public class ViewTestItem extends LinearLayout {


    private TextView textView;
    private View rootView;
    private TextView sub_title;
    private TextView title;
    public ViewTestItem(Context context) {
        super(context);
        initView(context);
    }


    public ViewTestItem(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView(context);
    }

    private void initView(Context context){
        LayoutInflater inflater = LayoutInflater.from(context);
        inflater.inflate(R.layout.view_text,this,true);
        textView = (TextView) findViewById(R.id.textView);
        rootView = findViewById(R.id.rootView);
        sub_title = (TextView) findViewById(R.id.sub_title);
        title = (TextView) findViewById(R.id.title);
    }

    public void setString(String sta){
        textView.setText(sta);
        title.setText(sta);
        sub_title.setText(sta+sta+sta);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        rootView.measure(widthMeasureSpec,widthMeasureSpec);
        textView.measure(widthMeasureSpec-200,widthMeasureSpec-200);
        setMeasuredDimension(widthMeasureSpec, widthMeasureSpec);

    }

    /*设置一定的范围值,超出范围后,使得标题彻底隐藏*/
    private float div = 0.2f;
    /*水平方向上标题位移的速度*/
    private float horiziSpeed = 1f;
    /*竖直方向上标题位移的速度*/
    private float verSpeed = 0.3f;

    public void setOffset(float position) {
        if (position > 0) {
            if (position > div) {
                /*超过范围值,彻底隐藏标题*/
                sub_title.setAlpha(0);
                title.setAlpha(0);
            } else if (position < div && position > 0) {
                title.setAlpha((div - position) / div);
                /*一级标题偏移速度比为1*/
                title.setTranslationX(position * title.getWidth() * horiziSpeed * 1);
                /*竖直方向上的偏移*/
                title.setTranslationY(-position * title.getHeight() * verSpeed);
                sub_title.setAlpha((div - position) / div);
                /*二级标题偏移速度比为2,与一级标题偏移量不同,造成错落的感觉*/
                sub_title.setTranslationX(position * sub_title.getWidth() * horiziSpeed * 2);
                /*竖直方向上的偏移*/
                sub_title.setTranslationY(-position * sub_title.getHeight() * verSpeed);
            }
        } else if (position < 0) {
            if (position < -div) {
                /*超过范围值,彻底隐藏标题*/
                sub_title.setAlpha(0);
                title.setAlpha(0);
            } else if (position > -div && position < 0) {
                title.setAlpha((-div - position) / -div);
                title.setTranslationX(position * title.getWidth() * horiziSpeed * 1);
                title.setTranslationY(position * title.getWidth() * verSpeed);
                sub_title.setAlpha((-div - position) / -div);
                sub_title.setTranslationX(position * sub_title.getWidth() * horiziSpeed * 2);
                sub_title.setTranslationY(position * sub_title.getWidth() * verSpeed);

            }
        } else if (position == 0) {
            /*当前页完全显示在中心,完全显示标题,偏移量为0*/
            sub_title.setAlpha(1);
            sub_title.setTranslationX(0);
            title.setAlpha(1);
            title.setTranslationX(0);
        }
    }

自定义View中,onMeasure部分代码先不做解释了,只是为了完成圆形的绘制而已,在不同分辨率下能够适配,下一篇文章会详细讲一个自定义ViewGroup的demo。

效果

源码

github地址

希望能帮到你。

你可能感兴趣的:(android)