android 使用Scroller实现美团悬浮框,网易左右滑动菜单效果

Scroller类其实就是对View的scrollTo()以及ScrollBy()方法封装以及优化,比如我们熟悉的网易客户端主界面的侧滑菜单,如果要实现当滑动超过一半的时候回弹的效果,而不是一下子就回到了最终点,view的scrollTo(),scrollBy()方法是没有做这个功能的,而Scroller类就做了这些优化,使用起来更方便,如果对View的那个二个方法很真的懂了的话,讲Scroller就很容易懂,不懂的话可以去看下我另外一篇关于View的scrollTo()和scrollBy()方法,自认为讲的比较详细,讲Scroller类首先从它的构造花函数讲起,在API 11之前就2个构造函数,API 11后添加了一个新的构造函数,

/**
 * Create a Scroller with the default duration and interpolator.
 */
public Scroller(Context context) {
    this(context, null);
}

/**
 * Create a Scroller with the specified interpolator. If the interpolator is
 * null, the default (viscous) interpolator will be used. "Flywheel" behavior will
 * be in effect for apps targeting Honeycomb or newer.
 */
public Scroller(Context context, Interpolator interpolator) {
    this(context, interpolator,
            context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB);
}

/**
 * Create a Scroller with the specified interpolator. If the interpolator is
 * null, the default (viscous) interpolator will be used. Specify whether or
 * not to support progressive "flywheel" behavior in flinging.
 */
public Scroller(Context context, Interpolator interpolator, boolean flywheel) {
    mFinished = true;
    if (interpolator == null) {
        mInterpolator = new ViscousFluidInterpolator();
    } else {
        mInterpolator = interpolator;
    }
    mPpi = context.getResources().getDisplayMetrics().density * 160.0f;
    mDeceleration = computeDeceleration(ViewConfiguration.getScrollFriction());
    mFlywheel = flywheel;

    mPhysicalCoeff = computeDeceleration(0.84f); // look and feel tuning
}
第一个构造函数很简单,就一个Context形参,无需做过多的介绍

第二个构造函数,第一个形参还是Context,第二个形参是插补器,里面封装了很多动画效果,

第三个构造函数有三个形参,最后一个形参是boolean值,是API 11后有的,什么意思先留在这里

今天就不讲Interpolator 这个类的使用,因为它会涉及很多子类,每个子类对应的动画效果不一样,到时候会讲动画系列会好好讲下,

现在看下Scroller类都给我们提供了哪些方法可以给我们调用,

 public final boolean isFinished() {} 判断滑动是否结束
public final int getDuration() {} 返回滑动所需的时间(单位为毫秒)
public final int getCurrX() {} 返回当前x轴偏移量
public final int getCurrY() {} 返回当前y轴偏移量
public float getCurrVelocity(){}返回当前滚动的速度
public final void forceFinished(boolean finished) {}强制终止滚动
public final int getStartX() {} 返回滚动起始点x轴偏移量
public final int getStartY() {} 返回滚动起始点y轴偏移量
public final int getFinalX() {}返回滚动结束x轴的偏移量
public final int getFinalY() {}返回滚动结束y轴的偏移量
 public boolean computeScrollOffset() {} 判断是否滑动结束,
public void setFinalX(int newX) {} 设置x轴方向终止偏移量
public void setFinalY(int newY) {} 设置y轴方向终止偏移量
public int timePassed() {}返回开始滑动经过的时间
public void extendDuration(int extend) {}延迟extend 秒再滑动
public void abortAnimation() {}终止动画执行
public void fling(int startX, int startY, int velocityX, int velocityY,int minX, int maxX, int minY, int maxY) {}  快速滑动松开手势惯性滑动

使用Scroller类实现view的滑动一般就三个步骤

第一步:创建Scroller类的对象

第二步:调用Scroller类的startScroll()方法,先看下它的源码:

/**
 * Start scrolling by providing a starting point, the distance to travel,
 * and the duration of the scroll.
 * 
 * @param startX Starting horizontal scroll offset in pixels. Positive
 *        numbers will scroll the content to the left.
 * @param startY Starting vertical scroll offset in pixels. Positive numbers
 *        will scroll the content up.
 * @param dx Horizontal distance to travel. Positive numbers will scroll the
 *        content to the left.
 * @param dy Vertical distance to travel. Positive numbers will scroll the
 *        content up.
 * @param duration Duration of the scroll in milliseconds.
 */
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
    mMode = SCROLL_MODE;
    mFinished = false;
    mDuration = duration;
    mStartTime = AnimationUtils.currentAnimationTimeMillis();
    mStartX = startX;
    mStartY = startY;
    mFinalX = startX + dx;
    mFinalY = startY + dy;
    mDeltaX = dx;
    mDeltaY = dy;
    mDurationReciprocal = 1.0f / (float) mDuration;
}
你会发现这个方法其实一堆赋值操作,并没有帮你实现滑动的逻辑,滑动的逻辑其实还是调用View的scrollTo()方法,startScroll()方法就是初始化了一些滑动的数据

第三步:调用Scroller类的computeScrollOffset()方法判断是否滑动到了终点,如果没有滑动到终点的话就重写View的computeScroll()方法,这个方法在View中是空实现,所以重写这个方法实现你继续滑动的逻辑,为什么会调用这个方法呢?结合源码分析:

首先会调用ViewGroup中的invalidate()方法,因为ViewGroup是继承自View的,而ViewGroup并没有重写invalidate()方法,所以调用的是View中的invalidate()方法:

/**
 * Invalidate the whole view. If the view is visible,
 * {
   @link #onDraw(android.graphics.Canvas)} will be called at some point in
 * the future. This must be called from a UI thread. To call from a non-UI thread,
 * call {
   @link #postInvalidate()}.
 */
public void invalidate() {
    invalidate(true);
}
调用如下方法:

/**
 * This is where the invalidate() work actually happens. A full invalidate()
 * causes the drawing cache to be invalidated, but this function can be called with
 * invalidateCache set to false to skip that invalidation step for cases that do not
 * need it (for example, a component that remains at the same dimensions with the same
 * content).
 *
 * @param invalidateCache Whether the drawing cache for this view should be invalidated as
 * well. This is usually true for a full invalidate, but may be set to false if the
 * View's contents or dimensions have not changed.
 */
void invalidate(boolean invalidateCache) {
    if (skipInvalidate()) {
        return;
    }
    if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS) ||
            (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) ||
            (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED || isOpaque() != mLastIsOpaque) {
        mLastIsOpaque = isOpaque();
        mPrivateFlags &= ~PFLAG_DRAWN;
        mPrivateFlags |= PFLAG_DIRTY;
        if (invalidateCache) {
            mPrivateFlags |= PFLAG_INVALIDATED;
            mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
        }
        final AttachInfo ai = mAttachInfo;
        final ViewParent p = mParent;
        //noinspection PointlessBooleanExpression,ConstantConditions
        if (!HardwareRenderer.RENDER_DIRTY_REGIONS) {
            if (p != null && ai != null && ai.mHardwareAccelerated) {
                // fast-track for GL-enabled applications; just invalidate the whole hierarchy
                // with a null dirty rect, which tells the ViewAncestor to redraw everything
                p.invalidateChild(this, null);
                return;
            }
        }

        if (p != null && ai != null) {
            final Rect r = ai.mTmpInvalRect;//把ai定义的矩形赋值给r
            r.set(0, 0, mRight - mLeft, mBottom - mTop);//设置矩形的区域 也就是2个坐标点
            // Don't call invalidate -- we don't want to internally scroll
            // our own bounds
            p.invalidateChild(this, r);//调用ViewGroup中的重新绘制子view的方法,因为ViewGroup是实现了ViewParent()接口
        }
    }
}
ViewGroup中的invalidateChild(View view,Rect rect)

/**
 * Don't call or override this method. It is used for the implementation of
 * the view hierarchy.
 */
public final void invalidateChild(View child, final Rect dirty) {
    ViewParent parent = this;

    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
        // If the child is drawing an animation, we want to copy this flag onto
        // ourselves and the parent to make sure the invalidate request goes
        // through
        final boolean drawAnimation = (child.mPrivateFlags & PFLAG_DRAW_ANIMATION)
                == PFLAG_DRAW_ANIMATION;

        // Check whether the child that requests the invalidate is fully opaque
        // Views being animated or transformed are not considered opaque because we may
        // be invalidating their old position and need the parent to paint behind them.
        Matrix childMatrix = child.getMatrix();
        final boolean isOpaque = child.isOpaque() && !drawAnimation &&
                child.getAnimation() == null && childMatrix.isIdentity();
        // Mark the child as dirty, using the appropriate flag
        // Make sure we do not set both flags at the same time
        int opaqueFlag = isOpaque ? PFLAG_DIRTY_OPAQUE : PFLAG_DIRTY;

        if (child.mLayerType != LAYER_TYPE_NONE) {
            mPrivateFlags |= PFLAG_INVALIDATED;
            mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
            child.mLocalDirtyRect.union(dirty);
        }

        final int[] location = attachInfo.mInvalidateChildLocation;
        location[CHILD_LEFT_INDEX] = child.mLeft;
        location[CHILD_TOP_INDEX] = child.mTop;
        if (!childMatrix.isIdentity() ||
                (mGroupFlags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) {
            RectF boundingRect = attachInfo.mTmpTransformRect;
            boundingRect.set(dirty);
            Matrix transformMatrix;
            if ((mGroupFlags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) {
                Transformation t = attachInfo.mTmpTransformation;
                boolean transformed = getChildStaticTransformation(child, t);
                if (transformed) {
                    transformMatrix = attachInfo.mTmpMatrix;
                    transformMatrix.set(t.getMatrix());
                    if (!childMatrix.isIdentity()) {
                        transformMatrix.preConcat(childMatrix);
                    }
                } else {
                    transformMatrix = childMatrix;
                }
            } else {
                transformMatrix = childMatrix;
            }
            transformMatrix.mapRect(boundingRect);
            dirty.set((int) (boundingRect.left - 0.5f),
                    (int) (boundingRect.top - 0.5f),
                    (int) (boundingRect.right + 0.5f),
                    (int) (boundingRect.bottom + 0.5f));
        }

        do {
  
            View view = null;
            if (parent instanceof View) {
                view = (View) parent;
            }

            if (drawAnimation) {
                if (view != null) {
                    view.mPrivateFlags |= PFLAG_DRAW_ANIMATION;
                } else if (parent instanceof ViewRootImpl) {
                    ((ViewRootImpl) parent).mIsAnimating = true;
                }
            }

            // If the parent is dirty opaque or not dirty, mark it dirty with the opaque
            // flag coming from the child that initiated the invalidate
            if (view != null) {
                if ((view.mViewFlags & FADING_EDGE_MASK) != 0 &&
                        view.getSolidColor() == 0) {
                    opaqueFlag = PFLAG_DIRTY;
                }
                if ((view.mPrivateFlags & PFLAG_DIRTY_MASK) != PFLAG_DIRTY) {
                    view.mPrivateFlags = (view.mPrivateFlags & ~PFLAG_DIRTY_MASK) | opaqueFlag;
                }
            }

            parent = parent.invalidateChildInParent(location, dirty);
            if (view != null) {
                // Account for transform on current parent
                Matrix m = view.getMatrix();
                if (!m.isIdentity()) {
                    RectF boundingRect = attachInfo.mTmpTransformRect;
                    boundingRect.set(dirty);
                    m.mapRect(boundingRect);
                    dirty.set((int) (boundingRect.left - 0.5f),
                            (int) (boundingRect.top - 0.5f),
                            
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值