美文网首页Android知识Android技术知识手机移动程序开发
Android的边缘效应的相关类EdgeEffectCompat

Android的边缘效应的相关类EdgeEffectCompat

作者: 逝_思 | 来源:发表于2017-01-23 14:28 被阅读1241次

    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学习就到这里。

    相关文章

      网友评论

        本文标题:Android的边缘效应的相关类EdgeEffectCompat

        本文链接:https://www.haomeiwen.com/subject/uveabttx.html