打造丝滑的滑动视差控件(ScrollParallaxView)

1、前言

Wishing you peace, joy and happiness through the coming year.

提交祝贺大家新春快乐,狗年行大运。

2、正文

在年前跟大家分享一款滑动视差控件,先看效果图:

由于录制的不是很清晰,请下载源码运行看效果。ScrollParallaxView 控件支持以下个性化定制,并支持链式的调用:

  1. 支持滑动是否具有滑动视差效果
  2. 支持圆角,圆
  3. 支持调节视差比例
  4. 支持非图片区域的背景颜色设置

    链式调用的方式如下:

    ((ScrollParallaxView) helper.getView(R.id.spv))
            .setEnableCircle(false)
            .setRoundWidth(16)
            .setEmptyAreaColor(Color.parseColor("#FFFFFF"));

2.1、实现原理

就用语言描述吧,原理图我画的实在太丑了,都不好意思放上来了。

一句话概括:先将图片放大,然后根据滑动的偏移量来对图片进行平移处理。

原理就是这么简单,这么任性,接下来看看具体的实现。

2.2、具体实现

首先需要针对滑动事件的监听,为了降低耦合,直接注册监听视图树的滑动事件:

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        getViewTreeObserver().addOnScrollChangedListener(this);
    }

既然有添加,那么就有移除:

    @Override
    protected void onDetachedFromWindow() {
        getViewTreeObserver().removeOnScrollChangedListener(this);
        super.onDetachedFromWindow();
    }

ScrollParallaxView 视图在滚动的时候进行重绘:

    @Override
    public void onScrollChanged() {
        //重绘
        invalidate();
    }

invalidate() 方法会调用 onDraw() 方法,接着看看 onDraw() 方法进行了哪些操作:

    @Override
    protected void onDraw(Canvas canvas) {
        if (enableScrollParallax) {
            int saveCount = canvas.save();
            getLocationInWindow(mViewLocation);

            mActualRect = getDrawable().getBounds();
            //视图宽度
            float vw = getWidth();
            //视图高度
            float vh = getHeight();
            //图片宽度
            float bw = mActualRect.width();
            //图片高度
            float bh = mActualRect.height();
            //视图宽高比
            float vratio = vw / vh;
            //图片宽高比
            float bratio = bw / bh;
            //最大滑动高度
            float ph = (1 + parallaxMultiplier) * vh;

            if (bratio > vratio) {
                parallaxScale = ph / (vw / bratio);
            } else {
                float _scale = vw / (vh * bratio);
                float _ph = vw / bratio;
                if (_ph < ph) {
                    _scale = ph / vh;
                }
                parallaxScale = _scale;
            }

            //重置矩阵
            mParallaxMatrix.reset();
            mParallaxMatrix.mapRect(new RectF(mActualRect));

            //居中缩放
            mParallaxMatrix.postScale(parallaxScale, parallaxScale, vw / 2, vh / 2);

            //增加滑动偏移量,让滑动从图片顶部开始
            float translationY = parallaxMultiplier / 2f * vh;
            mParallaxMatrix.postTranslate(0, translationY);

            //滚动偏移
            int dHeight = getResources().getDisplayMetrics().heightPixels;
            mParallaxMatrix.postTranslate(0, -(parallaxMultiplier * vh) * ((float) mViewLocation[1] / dHeight));

            canvas.concat(mParallaxMatrix);

            super.onDraw(canvas);

            canvas.restoreToCount(saveCount);
        } else {
            super.onDraw(canvas);
        }
        canvas.drawPath(mRoundPath, mRoundPaint);
    }

首先是获取到视图在屏幕当中的坐标:

 getLocationInWindow(mViewLocation);

然后获取到图片的矩形区域:

 mActualRect = getDrawable().getBounds();

接着是获取到图片的缩放比例:

 //视图宽度
 float vw = getWidth();
 //视图高度
 float vh = getHeight();
 //图片宽度
 float bw = mActualRect.width();
 //图片高度
 float bh = mActualRect.height();
 //视图宽高比
 float vratio = vw / vh;
 //图片宽高比
 float bratio = bw / bh;
 //最大滑动高度
 float ph = (1 + parallaxMultiplier) * vh;
 //等比缩放,防止变形
 if (bratio > vratio) {
     parallaxScale = ph / (vw / bratio);
 } else {
     float _scale = vw / (vh * bratio);
     float _ph = vw / bratio;
     if (_ph < ph) {
         _scale = ph / vh;
     }
     parallaxScale = _scale;
 }

最后对图片进行缩放,平移的操作:

//重置矩阵
mParallaxMatrix.reset();
mParallaxMatrix.mapRect(new RectF(mActualRect));
//居中缩放
mParallaxMatrix.postScale(parallaxScale, parallaxScale, vw / 2, vh / 2);
//增加滑动偏移量,让滑动从图片顶部开始
float translationY = parallaxMultiplier / 2f * vh;
mParallaxMatrix.postTranslate(0, translationY);
//滚动偏移
int dHeight = getResources().getDisplayMetrics().heightPixels;

mParallaxMatrix.postTranslate(0, -(parallaxMultiplier * vh) * ((float) mViewLocation[1] / dHeight));
canvas.concat(mParallaxMatrix);

代码我并没有细讲,请原谅我的懒惰。如果有不懂的地方请留言,如果你有更好的方式也请留言讨论。

接下来,我跟大家分享一个跟本文无关的故事。

在面试过程中,怎么去实现以下的控件效果:

打造丝滑的滑动视差控件(ScrollParallaxView)_第1张图片

来源:MEIOS4.0 手机设置页面顶部效果。

以上效果图是我个人实现的效果,有一些细节需要调整,因为我没有参数。只能实现了一个类似的效果。

实现的过程当中遇到的第一个难题就是,怎么实现多边形的圆角问题?四边形系统直接提供了方法,那么三边形,五边形,多边形呢?如果我每个角去画,那么真的会很酸爽。

画笔 Paint 有个方法 setPathEffect 可以设置画笔的路径样式。PathEffect 有个子类 CornerPathEffect 把拐角绘制成圆角效果。接着你会发现,始终有一个角是直角,怎么也绘制不成圆角的效果,那么又怎么解决呢?欢迎留言给出你的方案,透密一下很简单哟。

本篇到此就差不多结束了,谢谢大家的关注。也希望能推送一些对大家有帮助的文章。

你可能感兴趣的:(Android)