【问题标题】:Android: Scroller Animation?Android:滚动动画?
【发布时间】:2011-03-31 06:02:11
【问题描述】:

我是 Android 开发的新手,我想了解一些有关 Scroller 小部件 (android.widget.Scroller) 的信息。它如何为视图设置动画?如果 Animation 对象存在,是否可以访问?如果是这样,怎么做?看了源码,没找到线索,还是我太新了?

我只是想在 Scroller 完成滚动后做一些操作,比如

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

【问题讨论】:

    标签: android animation scroll scroller


    【解决方案1】:

    Scroller 小部件实际上并没有为您完成很多工作。它不会触发任何回调,它不会为任何东西设置动画,它只是响应各种方法调用。

    那有什么好处呢?好吧,它完成了所有的计算,例如为你一扔,这很方便。所以你通常会创建一个 Runnable,它反复询问 Scroller,“我现在的滚动位置应该是什么?我们完成了吗?”然后你在处理程序上(通常在视图上)重新发布该可运行文件,直到完成。

    这是我现在正在处理的片段中的一个示例:

    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 实现了一种侦听器线程。好的。谢谢! :)
    • 你的isFlinging()方法不应该返回!scroller.isFinished()吗?要么,要么将其重命名为isFinished()。不过很好的例子:)
    • 是的,应该。固定的。谢谢!
    • 希望有人能给我解释一下。我看不出它会在哪里重复以保持每 t 时间移动视图 x 量
    • 在我问这个问题差不多两年后,我想你也可以创建一个子类,它接受一个委托并定期调用该委托的某些方法,即 onScrollFinished() 或 onOverscroll() 等。这比一直轮询滚动条要好。 @BillPhillips 不过,您确实在这方面帮助了我。 :)
    【解决方案2】:

    就像 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 repository

    【讨论】:

      【解决方案3】:

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

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

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

      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) 设置了一个类字段,该字段用于在诸如调用 Scroller#scrollTo(int, int, int, int) 的情况下使用。

      bringPointIntoView() 调整 mScrollX 和 mScrollY(带有一些 SDK 碎片代码),然后调用 invalidate()。这一切的重点是 mScrollX 和 mScrollY 用于 onPreDraw(...) 之类的方法中,以影响视图绘制内容的位置。

      【讨论】:

        【解决方案4】:

        我们可以扩展Scroller类,然后拦截相应的动画开始方法来标记已经开始,在computeScrollOffset()返回false后,表示动画完成的值,我们通过一个Listener通知调用者:

        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 的滚动位置。在扩展 Scroller 的实现中,如何从视图中轮询 ScrollerImpl 以获取滚动位置的变化?
        • 见谅,我不是很清楚你的问题,你想知道ScrollerImpl怎么用吗?
        • @carignan.boy 在这种情况下您不进行投票。您所做的是设置 ScrollerImpl OnFinishListener,然后等待调用 onFinish() 方法。这样做比轮询要好得多;这正是我在接受的答案上写的评论中的意思。
        猜你喜欢
        • 2011-09-12
        • 1970-01-01
        • 1970-01-01
        • 2011-04-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多