安卓:滚动动画?

12

我是Android开发的新手,我想了解一下Scroller小部件(android.widget.Scroller)。它如何使视图动画化?如果存在Animation对象,是否可以访问该对象?如果可以,如何访问?我已经阅读了源代码,但没有找到任何线索,或者可能是因为我太新了?

我只是想在Scroller完成滚动后执行一些操作,类似于

m_scroller.getAnimation().setAnimationListener(...);
4个回答

40

Scroller小部件实际上并不为您完成太多工作。 它不会触发任何回调,也不会执行任何动画,它只会响应各种方法调用。

那么它有什么用呢?好吧,它为您执行例如抛掷的所有计算,这很方便。 因此,通常您要做的是创建一个Runnable,反复询问Scroller:“现在我的滚动位置应该是什么?我们完成抛掷了吗?” 然后将该Runnable重新发布到Handler上(通常在View上),直到完成抛掷为止。

这是我正在处理的Fragment中的示例:

private class Flinger implements Runnable {
    private final Scroller scroller;

    private int lastX = 0;

    Flinger() {
        scroller = new Scroller(getActivity());
    }

    void start(int initialVelocity) {
        int initialX = scrollingView.getScrollX();
        int maxX = Integer.MAX_VALUE; // or some appropriate max value in your code
        scroller.fling(initialX, 0, initialVelocity, 0, 0, maxX, 0, 10);
        Log.i(TAG, "starting fling at " + initialX + ", velocity is " + initialVelocity + "");

        lastX = initialX;
        getView().post(this);
    }

    public void run() {
        if (scroller.isFinished()) {
            Log.i(TAG, "scroller is finished, done with fling");
            return;
        }

        boolean more = scroller.computeScrollOffset();
        int x = scroller.getCurrX();
        int diff = lastX - x;
        if (diff != 0) {
            scrollingView.scrollBy(diff, 0);
            lastX = x;
        }

        if (more) {
            getView().post(this);
        }
    }

    boolean isFlinging() {
        return !scroller.isFinished();
    }

    void forceFinished() {
        if (!scroller.isFinished()) {
            scroller.forceFinished(true);
        }
    }
}

使用Scroller.startScroll的细节应该是类似的。


我明白了。所以你为Scroller实现了一种监听线程。很好。谢谢! :) - Kenn Cal
1
你的 isFlinging() 方法不应该返回 !scroller.isFinished() 吗?要么这样,要么它应该被重命名为 isFinished()。不过这是一个很好的例子 :) - ashughes
getView() 方法所指的是哪个视图? - Deepak Senapati
没关系。View.post(Runnable) 是将事物发布到 UI 线程的便捷快捷方式。 - Bill Phillips
2
在我提出这个问题将近两年后,我发现你也可以创建一个子类,它接受一个委托并定期调用该委托的某些方法,例如onScrollFinished()或onOverscroll()等。在我看来,这比一直轮询滚动器要好。@BillPhillips,你确实帮了我大忙。 :) - Kenn Cal
显示剩余3条评论

4

就像Bill Phillips所说,Scroller只是一个Android SDK类,帮助计算滚动位置。我在这里有一个完整的工作示例:

public class SimpleScrollableView extends TextView {
    private Scroller mScrollEventChecker;

    private int mLastFlingY;
    private float mLastY;
    private float mDeltaY;

    public SimpleScrollableView(Context context) {
        this(context, null, 0);
    }

    public SimpleScrollableView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public SimpleScrollableView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (mScrollEventChecker != null && !mScrollEventChecker.isFinished()) {
            return super.onTouchEvent(event);
        }

        final int action = event.getAction();

        switch (action) {
            case MotionEvent.ACTION_DOWN:
                mLastY = event.getY();
                return true;

            case MotionEvent.ACTION_MOVE:
                int movingDelta = (int) (event.getY() - mLastY);
                mDeltaY += movingDelta;
                offsetTopAndBottom(movingDelta);
                invalidate();
                return true;

            case MotionEvent.ACTION_UP:
                mScrollEventChecker = new Scroller(getContext());
                mScrollEventChecker.startScroll(0, 0, 0, (int) -mDeltaY, 1000);
                post(new Runnable() {
                    @Override
                    public void run() {
                        if (mScrollEventChecker.computeScrollOffset()) {
                            int curY = mScrollEventChecker.getCurrY();
                            int delta = curY - mLastFlingY;
                            offsetTopAndBottom(delta); // this is the method make this view move
                            invalidate();
                            mLastFlingY = curY;
                            post(this);
                        } else {
                            mLastFlingY = 0;
                            mDeltaY = 0;
                        }
                    }
                });
                return super.onTouchEvent(event);
        }

        return super.onTouchEvent(event);
    }
}

上面的演示自定义视图将在用户释放视图后滚动回原始位置。当用户释放视图时,将调用startScroll()方法,我们可以知道每个单独消息帖子的距离值应该是多少。
完整的工作示例:Github存储库

2
我们可以扩展Scroller类,然后拦截相应的动画开始方法来标记已启动,在computeScrollOffset()返回false,也就是动画完成的值后,我们通过监听器通知调用者:
public class ScrollerImpl extends Scroller {
    ...Constructor...

    private boolean mIsStarted;
    private OnFinishListener mOnFinishListener;

    @Override
    public boolean computeScrollOffset() {
        boolean result = super.computeScrollOffset();
        if (!result && mIsStarted) {
            try { // Don't let any exception impact the scroll animation.
                mOnFinishListener.onFinish();
            } catch (Exception e) {}
            mIsStarted = false;
        }
        return result;
    }

    @Override
    public void startScroll(int startX, int startY, int dx, int dy) {
        super.startScroll(startX, startY, dx, dy);
        mIsStarted = true;
    }

    @Override
    public void startScroll(int startX, int startY, int dx, int dy, int duration) {
        super.startScroll(startX, startY, dx, dy, duration);
        mIsStarted = true;
    }

    @Override
    public void fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY) {
        super.fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY);
        mIsStarted = true;
    }

    public void setOnFinishListener(OnFinishListener onFinishListener) {
        mOnFinishListener = onFinishListener;
    }

    public static interface OnFinishListener {
        void onFinish();
    }
}

之前的实现使用线程来轮询滚动器的滚动位置。在您扩展Scroller的实现中,您如何从您的View中轮询ScrollerImpl以获取滚动位置的更改? - alexismorin
抱歉,我没有很清楚地理解你的问题,你是想知道如何使用 ScrollerImpl 吗? - VinceStyling
@carignan.boy 在这种情况下,你不需要轮询。你需要做的是设置ScrollerImpl OnFinishListener并等待onFinish()方法被调用。这样做比轮询要好得多;这正是我在接受的答案中所写评论的意思。 - Kenn Cal

2

上面的答案很好。Scroller#startScroll(...)的确是以相同的方式工作。

例如,自定义滚动TextView的源码在此处: http://bear-polka.blogspot.com/2009/01/scrolltextview-scrolling-textview-for.html

使用TextView#setScroller(Scroller)将Scroller设置到TextView上。

SDK的TextView源代码在此处: http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/2.2_r1.1/android/widget/TextView.java#TextView.0mScroller

显示TextView#setScroller(Scroller)设置了一个类字段,该字段在像bringPointIntoView(int)这样的情况下被使用,其中会调用Scroller#scrollTo(int, int, int, int)。

bringPointIntoView()调整mScrollX和mScrollY(带有一些SDK碎片化代码),然后调用invalidate()。所有这些的目的是mScrollX和mScrollY在onPreDraw(...)等方法中被使用,从而影响视图中绘制内容的位置。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接