• 【android】View的事件体系3-弹性滑动


    3、弹性滑动

    View 的滑动如果过于生硬的话,用户体验会很差劲,所以需要实现渐进式滑动。这种滑动的基本思想是将一次大的滑动拆分成若干次小的滑动。以下是几种实现方法。

    3.1、使用Scroller

    这个在View的事件体系1里面有,现在看它为什么能够实现view的弹性滑动。

    1. Scroller scroller = new Scroller(mContext);
    2. //缓慢滚动到指定位置
    3. private void smoothScrollTo(int destX, int destY){
    4. int scrollX = getScroller();
    5. int deltaX = destX - scrollX;
    6. //1000ms内滑向destX,效果就是慢慢滑动
    7. mScroller.startScroll(scrollX, 0, deltaX, 0, 1000);
    8. invalidate();
    9. }
    10. @override
    11. public void computeScroll(){
    12. if (mScroller.computeScrollOffset()){
    13. scrollTo(mScrollrt.getCurrX(),mScroller.getCurrY());
    14. postInvalidate();
    15. }
    16. }

    上面是 Scroller 典型的使用方法,它的工作原理是:当我们构造一个 Scroller 对象并且调用它的 startScroll 方法时,Scroller 内部其实什么也没做,它只是保存了我们传递的几个参数,这几个参数从 startScroll的原型上就可以看出来,如下所示。

    1. public void startScroll(int startX, int startY, int dx, int dy, int duration) {
    2. mMode = SCROLL_MODE;
    3. mFinished = false;
    4. mDuration = duration;
    5. mStartTime = AnimationUtils.currentAnimationTimeMillis();
    6. mStartX = startX;
    7. mStartY = startY;
    8. mFinalX = startX + dx;
    9. mFinalY = startY + dy;
    10. mDeltaX = dx;
    11. mDeltaY = dy;
    12. mDurationReciprocal = 1.0f / (float) mDuration;
    13. }
    • startX 和 startY 表示的是滑动的起点。
    • dx 和 dy 表示的是滑动的距离。
    • duration 表示的是滑动时间。

    到此为止,都没有看到 Scroller 是如何实现弹性滑动的,其实是在 startScroll() 方法下面的 invalidate()方法。invalidate()方法会导致 View 重绘,在 View 的 draw()方法中又会去调用computeScroll()方法,computeScroll()方法在 View 中是一个空实现,因此需要我们自己去实现,上面的代码已经实现了这个方法。正是因为这个方法 View 才能实现弹性滑动。

    再看一下 computeScroll()方法中写的 Scroller 的 computeScrollOffset()方法的实现,如下:

    1. /**
    2. * Call this when you want to know the new location. If it returns true,
    3. * the animation is not yet finished.
    4. */
    5. public boolean computeScrollOffset() {
    6. ...
    7. int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
    8. if (timePassed < mDuration) {//过去的时间 < 动画规定总时长
    9. switch (mMode) {
    10. case SCROLL_MODE:
    11. final float x = mInterpolator.getInterpolation(timePassed * mDurationReciprocal);
    12. mCurrX = mStartX + Math.round(x * mDeltaX);
    13. mCurrY = mStartY + Math.round(x * mDeltaY);
    14. break;
    15. ...
    16. }
    17. }
    18. return true;
    19. }

    这个方法会根据时间流逝来计算当前的 scrollX 和 scrollY 的值。如果这个方法返回 true,表示动画过程至今还没有完成。

    概括 Scroller 的工作原理: Scroller 本身并不能完成 View 的滑动,需要配合 View 的一个计算方法才能完成弹性滑动的效果,它不断让 View 重绘,每一次重绘距离滑动开始时间有一个时间间隔,通过这个时间间隔得出 View 当前的滑动位置,然后就通过 scrollTo 可劲滑。每次重绘都滑动一点,摩擦摩擦在光滑的地面上…仿佛在滑动,就成弹性滑动了。

    3.2、通过动画

    之前写过,动画本身就是一种渐进的过程,因此通过它来实现的滑动天然就具有弹性滑动的效果。

    但是我们可以用动画的特性来实现一些动画不能实现效果,比如我们通过 Scroller 来实现 View 的弹性滑动:

    1. final int startX = 0;
    2. final int deltaX = 100;
    3. ValueAnimator animator = ValueAnimator.ofInt(0,1).setDuration(1000);
    4. animator.addUpdateListener(new AnimatorUpdateListener(){
    5. @override
    6. public void onAnimationUpdate(ValueAnimator animator){
    7. float fraction = animator.getAnimatedFraction();
    8. btn.scrollTo(startX + (int)(deltaX * fraction), 0);
    9. }
    10. });
    11. animator.start();

    上述代码中,动画只是在 1000ms 内完成了。利用这个特性,我们就可以在动画的每一帧到来时获取动画完成的比例,然后再根据这个比例计算出当前 View 所要滑动的距离,然后通过 scrollTo() 滑动。除此之外,我们完全可以在 onAnimationUpdate()方法中加上想要的任何操作。

    3.3、使用延时策略

    延时策略的核心思想是:通过发送一系列延时消息从而达到一种渐进式的效果,具体来说可以使用 Handler 或 View 的 postDelayed()方法,也可以使用线程的 sleep()方法。

    Handler示例:

    1. private static final int MESSAGE_SCROLL_TO = 1;
    2. private static final int FRAME_COUNT = 30;
    3. private static final int DELAYED_TIME = 33;
    4. private int mCount = 0;
    5. @SuppressLint("HandlerLeak")
    6. private Handler mHandler = new Handler(){
    7. public void handleMessage(Message msg){
    8. switch(msg.what){
    9. case MESSAGE_SCROLL_TO:{
    10. mCount++;
    11. if(mCount <= FRAME_COUNT){
    12. float fraction = mCount / (float)FRAME_COUNT;
    13. int scrollX = (int)(fraction * 100);
    14. btn.scrollTo(scrollX, 0);
    15. mHandler.sendEmptyMessageDelayed(MESSAGE_SCROLL_TO, DELAYED_TIME);
    16. }
    17. break;
    18. }
    19. default:
    20. break;
    21. }
    22. }
    23. };

    上面代码是在大约1000ms内将 View 的内容向左移动 100px。之所以是大约,是因为采用这种方式无法精确地定时,原因是系统的消息调度也是需要时间的,并且所需的时间不定。

  • 相关阅读:
    【图像分类】Swin Transformer理论解读+实践测试
    前端(五)
    OSPF的LSA优化
    GeoPandas安装
    MySQL的锁这么多,不知从何学起,看完这篇文章就够了
    P3177 [HAOI2015] 树上染色
    点餐小程序实战教程03-用户注册
    Linux 系统服务
    基于ssm二手车交易管理系统毕业设计-附源码151159
    多模态交互式 AI 代理的兴起:探索 Google 的 Astra 和 OpenAI 的 ChatGPT-4o应用
  • 原文地址:https://blog.csdn.net/eeeeety6208/article/details/125401980