Android: Scroller Animation?

2019-01-31 04:56发布

I'm a newbie in Android development, and I would just like to know a little bit about the Scroller widget (android.widget.Scroller). How does it animate the view? Can the Animation object, if it exists, be accessed? If so, how? I've read the source code, but could find no clues, or maybe I'm too new?

I just wanted to do some operations after a Scroller finishes scrolling, something like

m_scroller.getAnimation().setAnimationListener(...);

4条回答
Emotional °昔
2楼-- · 2019-01-31 04:58

like Bill Phillips said, Scroller is just an Android SDK class helping with calculating scrolling positions. I have a full working example here:

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);
    }
}

The demo custom view above will scroll back to original position after the user release the view. When user release the view, then startScroll() method is invoked and we can know what the distance value should be for every single message post.

Full working example: Github repository

查看更多
甜甜的少女心
3楼-- · 2019-01-31 05:03

Great answer above. Scroller#startScroll(...) indeed works the same way.

For example, the source for a custom scrolling TextView at: http://bear-polka.blogspot.com/2009/01/scrolltextview-scrolling-textview-for.html

Sets a Scroller on a TextView using TextView#setScroller(Scroller).

The source for the SDK's TextView at: http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/2.2_r1.1/android/widget/TextView.java#TextView.0mScroller

Shows that TextView#setScroller(Scroller) sets a class field which is used in situations like bringPointIntoView(int) where Scroller#scrollTo(int, int, int, int) is called.

bringPointIntoView() adjusts mScrollX and mScrollY (with some SDK fragmentation code), then calls invalidate(). The point of all this is that mScrollX and mScrollY are used in methods like onPreDraw(...) to affect the position of the drawn contents of the view.

查看更多
老娘就宠你
4楼-- · 2019-01-31 05:09

We can extend the Scroller class then intercept corresponding animation start methods to mark that was started, after computeScrollOffset() return false which means animation finished's value, we inform by a Listener to caller :

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();
    }
}
查看更多
劳资没心,怎么记你
5楼-- · 2019-01-31 05:10

The Scroller widget doesn't actually do much of the work at all for you. It doesn't fire any callbacks, it doesn't animate anything, it just responds to various method calls.

So what good is it? Well, it does all of the calculation for e.g. a fling for you, which is handy. So what you'd generally do is create a Runnable that repeatedly asks the Scroller, "What should my scroll position be now? Are we done flinging yet?" Then you repost that runnable on a Handler (usually on the View) until the fling is done.

Here's an example from a Fragment I'm working on right now:

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);
        }
    }
}

The details of using Scroller.startScroll should be similar.

查看更多
登录 后发表回答