记录使用Scroller实现平滑滚动,效果图如下:
一、自定义View中实现View的平滑滚动
public class ScrollerView extends View { private Scroller mScroller; private Paint mPaint; /** * 屏幕拖动最小像素 */ private int mTouchSlop; /** * View宽度 */ private int width; /** * View高度 */ private int height; /** * MotionEvent.getX() */ private int mEventX; /** * MotionEvent.getY() */ private int mEventY; private Bitmap mBitmap; /** * View到屏幕左边距离 */ private int mStartX; /** * View到屏幕顶部距离 */ private int mStartY; /** * View默认大小 */ private static int DEFAULT_SIZE = 200; public ScrollerView(Context context) { this(context, null); } public ScrollerView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); mPaint = new Paint(); mScroller = new Scroller(context); ViewConfiguration configuration = ViewConfiguration.get(context); mTouchSlop = ViewConfigurationCompat.getScaledHoverSlop(configuration); mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); if (widthMode == MeasureSpec.EXACTLY) { width = MeasureSpec.getSize(widthMeasureSpec); } else { if (heightMode == MeasureSpec.EXACTLY) { width = MeasureSpec.getSize(heightMeasureSpec); } else { width = DEFAULT_SIZE; } } if (heightMode == MeasureSpec.EXACTLY) { height = MeasureSpec.getSize(heightMeasureSpec); } else { height = width; } setMeasuredDimension(width, height); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (null != mBitmap) { Rect src = new Rect(0, 0, mBitmap.getWidth(), mBitmap.getHeight()); Rect dst = new Rect(0, 0, width, height); canvas.drawBitmap(mBitmap, src, dst, mPaint); } else { Log.e("zzy", "Bitmap is null"); } } @Override public boolean onTouchEvent(MotionEvent event) { int action = event.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: mEventX = (int) event.getX(); mEventY = (int) event.getY(); break; case MotionEvent.ACTION_MOVE: mStartX = (int) event.getRawX() - mEventX; mStartY = (int) event.getRawY() - mEventY; layout(mStartX,mStartY,mStartX+width,mStartY+height); break; case MotionEvent.ACTION_UP: startScroller(); break; } return true; } @Override public void computeScroll() { if (mScroller.computeScrollOffset()){ int l = mScroller.getCurrX(); layout(l,mStartY,l+width,mStartY+height); invalidate(); } } /** * 开始Scroller动画 */ private void startScroller(){ mScroller.forceFinished(true); mScroller.startScroll(mStartX, mStartY,-mStartX,0); int screenWidth = getScreenWidth(); // Scroller动画默认250ms,超过屏幕一半时设置为500ms if (mStartX > screenWidth / 2){ mScroller.extendDuration(500); } invalidate(); } private int getScreenWidth(){ return getResources().getDisplayMetrics().widthPixels; } }
Scroller其实是个辅助类,本身并不能完成动画的执行。而是帮我们计算随着时间的流逝,动画应该执行的位置值,我们需要获得当前时间的位置,然后调用View位置移动方法,将View移动到该位置,完成动画。
所以,在自定义View中。我们需要调用invalidate()
触发View的重绘,并覆写重绘会执行的方法computeScroll()
。
在computeScroll()
方法中调用Scroller
的computeScrollOffset()
计算当前时间动画应该移动的位置,返回值是动画是否在执行。
通过mScroller.getCurrX()
和mScroller.getCurrY()
获得当前时间的位置。手动调用View位置移动的方法将View的位置移动到当前时间的位置,实现View的滚动。
然后再次调用invalidate()
触发刷新。直到computeScrollOffset()
返回false,动画执行完成,滚动完成。
二、直接使用Scroller实现View的平滑滚动
我们知道,Scroller会帮我们计算当前时间,插值器返回的值。
而如果直接使用Scroller实现平滑滚动的话,也需要借助带时间的监听器。
这里借助ValueAnimator
来实现Scroller平滑滚动
private Scroller mScroller; private ImageView mImage; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mImage = findViewById(R.id.image); mScroller =new Scroller(this); } public void btnStart(View view){ start(); } private void start(){ mScroller.forceFinished(false); mScroller.extendDuration(500); mScroller.startScroll(0,0,400,400); ValueAnimator valueAnimator = ValueAnimator.ofFloat(0,1); valueAnimator.setDuration(500); valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { if (mScroller.computeScrollOffset()){ ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) mImage.getLayoutParams(); params.leftMargin = mScroller.getCurrX(); params.topMargin = mScroller.getCurrY(); mImage.setLayoutParams(params); } } }); valueAnimator.start(); }
在ValueAnimator的addUpdateListener
中刷新Scroller当前值。并移动位置。效果如下: