1. 程式人生 > >Android的邊緣效應的相關類EdgeEffectCompat學習

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學習就到這裡。