如何流畅移动view

前几天接到的任务中涉及到控件移动的问题,以前都是通过更新控件内容+补间动画来做的,但发觉应付连续多次请求时,有时刷新不及时导致延迟,画面出现重叠的情况。苦思许久,最后在同事的帮助下参考源码中Launcher应用的方法完美搞定。在这里必须分享一下……

 

在一切开始之前有必要先说说三个知识点:

1. scrollTo()scrollBy()方法

如何移动控件,Android Framework中已经给我们留了接口。源码中控件基类View.java类的定义中有如下片段:

   /**

     * The offset, in pixels, by which the content of this view is scrolled

     * horizontally.

     * {@hide}

     */

    @ViewDebug.ExportedProperty

    protected int mScrollX;

    /**

     * The offset, in pixels, by which the content of this view is scrolled

     * vertically.

     * {@hide}

     */

    @ViewDebug.ExportedProperty

    protected int mScrollY;

 

    /**

     * Set the scrolled position of your view. This will cause a call to

     * {@link #onScrollChanged(int, int, int, int)} and the view will be

     * invalidated.

     * @param x the x position to scroll to

     * @param y the y position to scroll to

     */

    public void scrollTo(int x, int y) {

        if (mScrollX != x || mScrollY != y) {

            int oldX = mScrollX;

            int oldY = mScrollY;

            mScrollX = x;

            mScrollY = y;

            onScrollChanged(mScrollX, mScrollY, oldX, oldY);

            if (!awakenScrollBars()) {

                invalidate();

            }

        }

    }

 

    /**

     * Move the scrolled position of your view. This will cause a call to

     * {@link #onScrollChanged(int, int, int, int)} and the view will be

     * invalidated.

     * @param x the amount of pixels to scroll by horizontally

     * @param y the amount of pixels to scroll by vertically

     */

    public void scrollBy(int x, int y) {

        scrollTo(mScrollX + x, mScrollY + y);

    }

 

mScrollX ,“mScrollY”可以记录当前控件在父窗体中的偏移量,而调用scrollTo()scrollBy()你就可以轻松的移动你的控件了。

2. Scroller 

尝试上面的方法,你很快就会发现它的用户体验非常差了,每次你的view会瞬间移动到指定的位置,给人一顿一顿的感觉。怎样才能控制view的偏移过程,从而使偏移更流畅,更完美呢?这一点并不需要我们做太多工作,因为Android Framework已经为我们提供了Scroller.java类,将一个迁移的动作变成了一个可控的过程,代码片段如下:

 

    private int mCurrX;

    private int mCurrY;

  ……

 

    /**

     * Call this when you want to know the new location.  If it returns true,

     * the animation is not yet finished.  loc will be altered to provide the

     * new location.

     */ 

    public boolean computeScrollOffset() {

        if (mFinished) {

            return false;

        }

 

        int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);

    

        if (timePassed < mDuration) {

            switch (mMode) {

            case SCROLL_MODE:

                float x = (float)timePassed * mDurationReciprocal;

    

                if (mInterpolator == null)

                    x = viscousFluid(x); 

                else

                    x = mInterpolator.getInterpolation(x);

    

                mCurrX = mStartX + Math.round(x * mDeltaX);

                mCurrY = mStartY + Math.round(x * mDeltaY);

                break;

            case FLING_MODE:

                float timePassedSeconds = timePassed / 1000.0f;

                float distance = (mVelocity * timePassedSeconds)

                        - (mDeceleration * timePassedSeconds * timePassedSeconds / 2.0f);

                

                mCurrX = mStartX + Math.round(distance * mCoeffX);

                // Pin to mMinX <= mCurrX <= mMaxX

                mCurrX = Math.min(mCurrX, mMaxX);

                mCurrX = Math.max(mCurrX, mMinX);

                

                mCurrY = mStartY + Math.round(distance * mCoeffY);

                // Pin to mMinY <= mCurrY <= mMaxY

                mCurrY = Math.min(mCurrY, mMaxY);

                mCurrY = Math.max(mCurrY, mMinY);

                

                break;

            }

        }

        else {

            mCurrX = mFinalX;

            mCurrY = mFinalY;

            mFinished = true;

        }

        return true;

    }

 

    /**

     * Start scrolling by providing a starting point and the distance to travel.

     * 

     * @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;

        // This controls the viscous fluid effect (how much of it)

        mViscousFluidScale = 8.0f;

        // must be set to 1.0 (used in viscousFluid())

        mViscousFluidNormalize = 1.0f;

        mViscousFluidNormalize = 1.0f / viscousFluid(1.0f);

}

 

忽略细节,重点关注其中比较重要的两个方法的作用:

public boolean computeScrollOffset()

根据当前已经消逝的时间计算当前的坐标点,保存在mCurrXmCurrY值中

 

public void startScroll(int startX, int startY, int dx, int dy, int duration)

开始一个动画控制,由(startX , startY)duration时间内前进(dx,dy)个单位,到达坐标为(startX+dx , startY+dy)处。

 

3. computeScroll()方法

如何去控制这个流程,Android Framework提供了computeScroll()方法,方法位于ViewGroup.java类中。为了实现偏移控制,一般自定义控件都需要重载该方法 。其调用过程位于View绘制流程draw()过程中,用于由父View调用用来请求子View根据偏移值mScrollX,mScrollY重新绘制

 

了解了以上的知识点,相信实现这个功能已经不是难事了,大概的思路如下;

首先,自定义一个控件继承于View,添加一个Scroller类型的成员变量mScroller

其次,调用mScrollerstartScroll()去产生一个偏移控制,手动调用invalid()方法去重新绘制控件;

最后,重写的computeScroll()方法,通过mScrollercomputeScrollOffset()方法,根据当前已经逝去的时间,获取当前应该偏移的坐标;调用scrollBy()方法去逐步偏移,调用postInvalidate()方法刷新控件;  

 

大致的代码如下,仅供参考:

 

public void nextMenuItem(){  

   ……// 一系列更新子View的逻辑操作

   // 更新结束后,使用动画控制偏移过程, 3s内到位

   mScroller.startScroll(0, 0, menuItemWidth, 0,3000);  

   // 重绘控件

   invalidate();  

}  

 

@Override  

public void computeScroll() {  

    if (mScroller.computeScrollOffset()) { // 如果返回true,表示动画还没有结束

        // 产生平滑的动画效果,根据当前偏移量,每次滚动一点

        scrollTo(mScroller.getCurrX(), mScroller.getCurrY());  

        // 此时同样也需要刷新View ,否则效果可能有误差

        postInvalidate();  

    } else { //如果返回false,表示startScroll完成  

        Log.i(tag, " scoller has finished -----");  

}  


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值