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 A
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();
}
}
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.
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
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.