Android的边缘效应的相关类EdgeEffectCompat学习

Android的边缘效应的相关类EdgeEffectCompat学习


Android中可以的ListView,ScrollView,RecyclerView等滑动到界面的边界的时候会出现一个半透明的颜色边
框。这个边框就是Android的边缘效果。主要是类EdgeEffect,EdgeEffectCompat管理。效果如下图

边缘效应效果图

1,EdgeEffectCompat类的学习
  1. 源码学习:
/**
     * 构造一个新的对象
     *
     * <p>Note: 对于不支持的版本,将会没有效果</p>
     *
     * @param 上下文
     */
    public EdgeEffectCompat(Context context) {
        mEdgeEffect = IMPL.newEdgeEffect(context);
    }
    
    /**
     * Set the size of this edge effect in pixels.
     * 设置边框的大小像素
     *
     * @param width Effect width in pixels
     * @param height Effect height in pixels
     */
    public void setSize(int width, int height) {
        IMPL.setSize(mEdgeEffect, width, height);
    }

    /**
     * Reports if this EdgeEffectCompat's animation is finished. If this method returns false
     * after a call to {@link #draw(Canvas)} the host widget should schedule another
     * drawing pass to continue the animation.
     *
     * 边缘的显示动画是否结束
     *
     * @return true if animation is finished, false if drawing should continue on the next frame. 
     *
     */
    public boolean isFinished() {
        return IMPL.isFinished(mEdgeEffect);
    }

    /**
     * Immediately finish the current animation.
     * After this call {@link #isFinished()} will return true.
     * 立刻结束显示动画
     */
    public void finish() {
        IMPL.finish(mEdgeEffect);
    }

    /**
     * 在处理滑动的时候调用
     * A view should call this when content is pulled away from an edge by the user.
     * This will update the state of the current visual effect and its associated animation.
     * The host view should always {@link android.view.View#invalidate()} if this method
     * returns true and draw the results accordingly.
     *
     * @param deltaDistance Change in distance since the last call. Values may be 0 (no change) to
     *                      1.f (full length of the view) or negative values to express change
     *                      back toward the edge reached to initiate the effect.
     *                      范围:[-1,1],是要移动的距里在View的边长的占比
     * @param displacement  The displacement from the starting side of the effect of the point
     *                      initiating the pull. In the case of touch this is the finger position.
     *                      Values may be from 0-1.
     *                      范围[0,1],手指所在的位置在非移动边的位置占比
     * @return true if the host view should call invalidate, false if it should not.
     * 如果返回为true就表示会重新刷新View。
     */
    public boolean onPull(float deltaDistance, float displacement) {
        return IMPL.onPull(mEdgeEffect, deltaDistance, displacement);
    }

   /**
     * Call when the object is released after being pulled.
     * This will begin the "decay" phase of the effect. After calling this method
     * the host view should {@link android.view.View#invalidate()} if this method
     * returns true and thereby draw the results accordingly.
     * 是否被释放
     * @return true if the host view should invalidate, false if it should not.
     */
    public boolean onRelease() {
        return IMPL.onRelease(mEdgeEffect);
    }

    /**
     * Call when the effect absorbs an impact at the given velocity.
     * Used when a fling reaches the scroll boundary.
     * 
     * 吸收一个速度,当到View的边界的时候会显示相应的动画
     * <p>When using a {@link android.widget.Scroller} or {@link android.widget.OverScroller},
     * the method <code>getCurrVelocity</code> will provide a reasonable approximation
     * to use here.</p>
     *
     * @param velocity Velocity at impact in pixels per second.
     * @return true if the host view should invalidate, false if it should not.
     */
    public boolean onAbsorb(int velocity) {
        return IMPL.onAbsorb(mEdgeEffect, velocity);
    }

    /**
     *  关键方法,在View的onDraw方法中调用会显示相应的动画,在调用这个方法之前要计算相应的
     * 平移,旋转的量。
     * Draw into the provided canvas. Assumes that the canvas has been rotated
     * accordingly and the size has been set. The effect will be drawn the full
     * width of X=0 to X=width, beginning from Y=0 and extending to some factor 小于
     * 1.f of height.
     *  
     * @param canvas Canvas to draw into
     * @return true if drawing should continue beyond this frame to continue the
     *         animation
     */
    public boolean draw(Canvas canvas) {
        return IMPL.draw(mEdgeEffect, canvas);
    }
  1. EdgeEffectCompat实现原理:

    EdgeEffcetCompat的实现就是在滑动控件滑动到边界的时候在边界画一个圆弧,然后根据
    滑动的位置(滑动方向的偏移量,非滑动方向坐标的位置)进行缩放平移,同时进行动画的播放。
    具体如下图(蓝色部分):

    原理图
2,EdgeEffectCompat类的应用

这里我用ScrollView的相关源码进行学习,展示EdgeEffectCompat是如何应用到View中的。

  1. 在View的绘制过程中,通过EdgeEffectCompat.setSize(width,height)EdgeEffectCompat.draw(canvas)两个方法来绘制效果图。相关的详细解释看注释:
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (mTopEdgeEffect != null) {
            final int width = getWidth() - getPaddingRight() - getPaddingLeft();//内容的宽度
            final int scrollY = getScrollY();//当前滑动的量
            if (!mTopEdgeEffect.isFinished()) {//动画是否已经结束
                int restoreCount = canvas.save();
                canvas.translate(getPaddingLeft(), Math.min(0, scrollY));//画布向右平移,如果View有向下超过0的偏移量就要再向上偏移,超过上边界的平移量
                mTopEdgeEffect.setSize(width , getHeight());//设置效果的展示范围(内容的宽度,和View的高度)
                if (mTopEdgeEffect.draw(canvas)) {//绘制边缘效果图,如果绘制需要进行动画效果返回true
                    ViewCompat.postInvalidateOnAnimation(this);//进行动画
                }
                canvas.restoreToCount(restoreCount);
            }

            if (!mBottomEdgeEffect.isFinished()) {
                int restoreCount = canvas.save();
                //下面两行代码的作用就是把画布平移旋转到底部展示,并让效果向上显示
                canvas.translate(getPaddingLeft() - width, Math.max(getScrollRange(), scrollY) + getHeight());
                canvas.rotate(180, width, 0);
                
                mBottomEdgeEffect.setSize(width, getHeight());
                if (mBottomEdgeEffect.draw(canvas)) {
                    ViewCompat.postInvalidateOnAnimation(this);
                }
                canvas.restoreToCount(restoreCount);
            }
        }
    }
  1. View.onTouchEvent()方法中调用EdgeEffectCompat.onPull方法详细见源码:
    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        final int actionMark = MotionEventCompat.getActionMasked(ev);
        switch (actionMark) {
            
            case MotionEvent.ACTION_MOVE:
                final int pointIndex = ev.findPointerIndex(mActionPointerId);
                if (pointIndex == -1) {
                    break;
                }

                final int y = (int) ev.getY(pointIndex);
                int deltaY = mLastPointY - y;
                if (!mIsBeingDragged && Math.abs(deltaY) > mTouchSlop) {
                    ViewParent parent = getParent();
                    if (parent != null) {
                        parent.requestDisallowInterceptTouchEvent(true);
                    }
                    if (deltaY > 0) {//减去积累的量
                        deltaY -= mTouchSlop;
                    } else {
                        deltaY += mTouchSlop;
                    }
                    mIsBeingDragged = true;
                }

                if (mIsBeingDragged) {
                    final int oldY = getScrollY();
                    final int range = getScrollRange();
                    final int overMode = getOverScrollMode();
                    boolean canOverScroll = overMode == View.OVER_SCROLL_ALWAYS
                            || (overMode == View.OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0);
                   //主要的代码
                    if (canOverScroll) {
                        // 边缘效果
                        ensureGlows();
                        final int pullToY = oldY + deltaY;
                        if (pullToY < 0) {//在顶部
                            mTopEdgeEffect.onPull((float) deltaY / getHeight(), ev.getX(pointIndex) / getWidth());
                            if (!mBottomEdgeEffect.isFinished()) {
                                mBottomEdgeEffect.onRelease();
                            }
                        } else if (pullToY > range) {//在底部
                            mBottomEdgeEffect.onPull((float) deltaY / getHeight(), 1.0f - ev.getX(pointIndex) / getWidth());
                            if (!mTopEdgeEffect.isFinished()) {
                                mTopEdgeEffect.onRelease();
                            }
                        }
                        if (mTopEdgeEffect != null && (!mTopEdgeEffect.isFinished() || !mBottomEdgeEffect.isFinished())) {
                            ViewCompat.postInvalidateOnAnimation(this);
                        }
                    }
                    mLastPointY = y;
                }

                break;
            case MotionEvent.ACTION_CANCEL:
                if (mTopEdgeEffect != null) {
                    mTopEdgeEffect.onRelease();
                    mBottomEdgeEffect.onRelease();
                }
                break;
            case MotionEvent.ACTION_UP:
                if (mTopEdgeEffect != null) {
                    mTopEdgeEffect.onRelease();
                    mBottomEdgeEffect.onRelease();
                }
                break;
          
        }
        
        return true;
    }
    
    private void ensureGlows() {
        if (getOverScrollMode() != OVER_SCROLL_NEVER) {
            Context context = getContext();
            if (context != null && mTopEdgeEffect == null) {
                mTopEdgeEffect = new EdgeEffectCompat(context);
                mBottomEdgeEffect = new EdgeEffectCompat(context);
            }
        } else {
            mTopEdgeEffect = null;
            mBottomEdgeEffect = null;
        }
    }
  1. 在快速滑动View的时候,View会有一定的速度到达边界,这时候就要根据到达边界的速度进行显示。
    一般View是用Scroller来进行Fling动画效果的。这时候就要在View.computeScroll方法中设置。详细如下:
    @Override
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            int oldX = getScrollX();
            int oldY = getScrollY();
            int newX = mScroller.getCurrX();
            int newY = mScroller.getCurrY();
//            Log.i(TAG, "computeScroll: oldY : " + oldY +" newY : "+newY);
            if (oldX != newX || oldY != newY) {
                final int range = getScrollRange();
                final int overMode = getOverScrollMode();
                boolean canOverScroll = overMode == View.OVER_SCROLL_ALWAYS
                        || (overMode == View.OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0);
                overScrollByCompat(newX - oldX, newY - oldY, oldX, oldY, 0, range, 0, 0);
                if (canOverScroll) {//这部分是吓死边缘效果的
                    ensureGlows();
                    if (newY < 0 && oldY > 0) {//到达顶部,吸收速度
                        mTopEdgeEffect.onAbsorb((int) mScroller.getCurrVelocity());
                    } else if (newY > range && oldY < range) {//到达底部,吸收速度
                        mBottomEdgeEffect.onAbsorb((int) mScroller.getCurrVelocity());
                    }
                }
            }
        }
    }

Android的边缘效应的相关类EdgeEffectCompat学习就到这里。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,401评论 25 707
  • 我觉得我应该就处在这样的阶段:什么都不用去想,不用去规划和展望,就是立即行动!非常赞成那个观点:也许在某个阶段,你...
    蛀牙哼阅读 139评论 0 0
  • 静静地坐在山头 看流星悄悄划过 晚风轻拂 一如你惯有的温柔 在霓虹街道漫步 驻足于喧嚣街口 人潮汹涌 却再没有你的温度
    木芽儿阅读 180评论 0 0