Android Scroller与computeScroll的调用机制关系

 

 

Android ViewGroup中的Scroller与computeScroll的有什么关系?

答:没有直接的关系

知道了答案,是不是意味着下文就没必要看了,如果说对ViewGroup自定义控件不感兴趣,可以不用看了。

1.Scroller到底是什么?

答:Scroller只是个计算器,提供插值计算,让滚动过程具有动画属性,但它并不是UI,也不是辅助UI滑动,反而是单纯地为滑动提供计算。

无论从构造方法还是其他方法,以及Scroller的属性可知,其并不会持有View,辅助ViewGroup滑动

2.Scroller只是提供计算,那谁来调用computeScroll使得ViewGroup滑动

答:computeScroll也不是来让ViewGroup滑动的,真正让ViewGroup滑动的是scrollTo,scrollBy。computeScroll的作用是计算ViewGroup如何滑动。而computeScroll是通过draw来调用的。

3.computeScroll和Scroller都是计算,两者有啥关系?

答:文章开始已作答,没有直接的关系。computeScroll和Scroller要是飞得拉关系的话,那就是computeScroll可以参考Scroller计算结果来影响scrollTo,scrollBy,从而使得滑动发生改变。也就是Scroller不会调用computeScroll,反而是computeScroll调用Scroller。

4.滑动时连续的,如何让Scroller的计算也是连续的?

这个就问到了什么时候调用computeScroll了,如上所说computeScroll调用Scroller,只要computeScroll调用连续,Scroller也会连续,实质上computeScroll的连续性又invalidate方法控制,scrollTo,scrollBy都会调用invalidate,而invalidate回去触发draw,从而computeScroll被连续调用,综上,Scroller也会被连续调用,除非invalidate停止调用。

5.computeScroll如何和Scroller的调用过程保持一致。

computeScroll参考Scroller影响scrollTo,scrollBy,实质上,为了不重复影响scrollTo,scrollBy,那么Scroller必须终止计算currX,currY。要知道计算有没有终止,需要通过mScroller.computeScrollOffset()

6.如上问题应该说的很清楚了吧,如果不明白,请留言。

7.通过一个SlidePanel的例子,我们来深刻的了解一下

注意:在移动平台中,要明确知道“滑动”与“滚动”的不同,具体来说,滑动和滚动的方向总是相反的。

package com.masskysraum.myscrollview;

import android.content.Context;
import android.graphics.Color;
import android.graphics.Point;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.animation.DecelerateInterpolator;
import android.widget.FrameLayout;
import android.widget.RelativeLayout;
import android.widget.Scroller;

import java.time.chrono.HijrahEra;

public class SlidingPanel extends RelativeLayout {
    private Context context;
    private FrameLayout leftMenu;
    private FrameLayout middleMenu;
    private FrameLayout rightMenu;
    private FrameLayout middleMask;
    private Scroller mScroller;
    public final int LEFT_ID = 0xaabbcc;
    public final int MIDEELE_ID = 0xaaccbb;
    public final int RIGHT_ID = 0xccbbaa;

    private boolean isSlideCompete;
    private boolean isHorizontalScroll;

    private Point point = new Point();
    private static final int SLIDE_SLOP = 20;

    public SlidingPanel(Context context) {
        super(context);
        initView(context);
    }

    public SlidingPanel(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView(context);
    }

    private void initView(Context context) {

        this.context = context;
        mScroller = new Scroller(context, new DecelerateInterpolator());
        leftMenu = new FrameLayout(context);//左抽屉
        middleMenu = new FrameLayout(context);//中间抽屉
        rightMenu = new FrameLayout(context);//右边菜单
        middleMask = new FrameLayout(context);
        leftMenu.setBackgroundColor(Color.RED);
        middleMenu.setBackgroundColor(Color.GREEN);
        rightMenu.setBackgroundColor(Color.RED);
        middleMask.setBackgroundColor(0x88000000);

        addView(leftMenu);
        addView(middleMenu);
        addView(rightMenu);
        addView(middleMask);

        middleMask.setAlpha(0);
    }

    public float onMiddleMask() {
        return middleMask.getAlpha();
    }

    /**
     * *invalidate->computeScroll->scrollTo->invalidate->draw->Scroller->invalidate
     * *getScrollX():当前视图相对于屏幕原点在x轴上的偏移量
     * *getScrollY():当前视图相对于屏幕原点在y轴上的偏移量
     *
     * @param x
     * @param y
     */
    @Override
    public void scrollTo(int x, int y) {
        super.scrollTo(x, y);
        onMiddleMask();
        // Log.e("getScrollX","getScrollX="+getScrollX());//可以是负值
        int curX = Math.abs(getScrollX());
        float scale = curX / (float) leftMenu.getMeasuredWidth();
        middleMask.setAlpha(scale);
    }

    /**
     * onMeasure->setMeasuredDimension
     *
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        middleMenu.measure(widthMeasureSpec, heightMeasureSpec);
        middleMask.measure(widthMeasureSpec, heightMeasureSpec);
        int realWidth = MeasureSpec.getSize(widthMeasureSpec);
        int tempWidthMeasure = MeasureSpec.makeMeasureSpec((int) (realWidth * 0.8f), MeasureSpec.EXACTLY);
        leftMenu.measure(tempWidthMeasure, heightMeasureSpec);
        rightMenu.measure(tempWidthMeasure, heightMeasureSpec);
    }

    /**
     * @param changed
     * @param l
     * @param t
     * @param r
     * @param b
     */
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {//0,0,720,1118
        super.onLayout(changed, l, t, r, b);
        middleMenu.layout(l, t, r, b);
        middleMask.layout(l, t, r, b);

        int left_menu_left = leftMenu.getMeasuredWidth();
        leftMenu.layout(l - left_menu_left, t, r, b);


        rightMenu.layout(
                l + middleMenu.getMeasuredWidth(),
                t,
                l + middleMenu.getMeasuredWidth()
                        + rightMenu.getMeasuredWidth(), b
        );
    }


    /*   *getScrollX():当前视图相对于屏幕原点在x轴上的偏移量
     *   *getX():触摸点相对于其所在视图原点在x轴上的偏移量
     *   *getX() + getScrollX()-> 相当于View上一个触摸点相对于屏幕原点在x轴上的偏移量,
     */
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (!isSlideCompete) {
            handleSlideEvent(ev);
            return true;
        }
        if (isHorizontalScroll) {
            switch (ev.getActionMasked()) {
                case MotionEvent.ACTION_MOVE:
                    int curScrollX = getScrollX();
                    int dis_x = (int) (ev.getX() - point.x);
                    //滑动方向和滚动滚动条方向相反,因此dis_x必须取负值
                    int expectX = -dis_x + curScrollX;

                    if (dis_x > 0) {
                        Log.e("I", "Right-Slide,Left-Scroll");//向右滑动,向左滚动
                    } else {
                        Log.e("I", "Left-Slide,Right-Scroll");
                    }

                    Log.e("I", "ScrollX=" + curScrollX + " , X=" + ev.getX() + " , dis_x=" + dis_x);
                    //规定expectX的变化范围
                    Log.e("I", "leftMenu.getMeasuredWidth()=" + leftMenu.getMeasuredWidth()
                            + " , rightMenu.getMeasuredWidth()=" + rightMenu.getMeasuredWidth());

                    int finalX = Math.max(-leftMenu.getMeasuredWidth(), Math.min(expectX, rightMenu.getMeasuredWidth()));
                    //这句话让滑动最多滑动leftMenu.getMeasuredWidth()大小的距离;防止出现白屏;

                    scrollTo(finalX, 0);
                    point.x = (int) ev.getX();//更新,保证滑动平滑
                    break;

                case MotionEvent.ACTION_UP:
                case MotionEvent.ACTION_CANCEL:
                    curScrollX = getScrollX();
                    int half_left = leftMenu.getMeasuredWidth() >> 1;
                    Log.e("I", "leftMenu.getMeasuredWidth() >> 1=" +half_left + "curScrollX = " + curScrollX);


                    if (Math.abs(curScrollX) > half_left) {
                        if (curScrollX < 0) {//滑满leftMenu.getMeasuredWidth()
                            mScroller.startScroll(curScrollX, 0,
                                    -leftMenu.getMeasuredWidth() - curScrollX, 0,
                                    200);
                        } else {
                            mScroller.startScroll(curScrollX, 0,
                                    leftMenu.getMeasuredWidth() - curScrollX, 0,
                                    200);
                        }
                    } else {
                        mScroller.startScroll(curScrollX, 0, -curScrollX, 0, 200);
                    }
                    invalidate();
                    isHorizontalScroll = false;
                    isSlideCompete = false;
                    break;
            }
        } else {
            switch (ev.getActionMasked()) {
                case MotionEvent.ACTION_UP:
                    isHorizontalScroll = false;
                    isSlideCompete = false;
                    break;
                default:
                    break;
            }
        }

        return super.dispatchTouchEvent(ev);
    }

    /**
     *     * 通过invalidate操纵,此方法通过draw方法调用
     *     *invalidate->computeScroll->scrollTo->invalidate->draw->Scroller->invalidate
     *     *scrollTo(tempX, 0)->tempX > 0 向右滑动,
     */
    @Override
    public void computeScroll() {
        super.computeScroll();
        if (!mScroller.computeScrollOffset()) {
            //计算currX,currY,并检测是否已完成“滚动”
            return;
        }
        int tempX = mScroller.getCurrX();
        scrollTo(tempX, 0); //会重复调用invalidate
    }


    private void handleSlideEvent(MotionEvent ev) {
        switch (ev.getAction() & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_DOWN:
                point.x = (int) ev.getX();
                point.y = (int) ev.getY();
                super.dispatchTouchEvent(ev);
                break;

            case MotionEvent.ACTION_MOVE:
                int dX = Math.abs((int) ev.getX() - point.x);
                int dY = Math.abs((int) ev.getY() - point.y);
                if (dX >= SLIDE_SLOP && dX > dY) { // 左右滑动
                    isHorizontalScroll = true;
                    isSlideCompete = true;
                    point.x = (int) ev.getX();
                    point.y = (int) ev.getY();
                } else if (dY >= SLIDE_SLOP && dY > dX) { // 上下滑动
                    isHorizontalScroll = false;
                    isSlideCompete = true;
                    point.x = (int) ev.getX();
                    point.y = (int) ev.getY();
                }
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_OUTSIDE:
            case MotionEvent.ACTION_CANCEL:
                super.dispatchTouchEvent(ev);
                isHorizontalScroll = false;
                isSlideCompete = false;
                break;
        }
    }

}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android Scroller 是一个用于实现平滑滚动效果的工具类。它可以用于在 Android 应用中实现滑动的动画效果,如平滑滚动到指定位置或者平滑滚动到顶部。 使用 Android Scroller 需要以下步骤: 1. 创建一个 Scroller 实例:使用 `new Scroller(context)` 创建一个 Scroller 对象。 2. 在 View 的 `computeScroll()` 方法中更新滚动位置:在需要实现滑动效果的 View 类里重写 `computeScroll()` 方法,然后在该方法中调用 `scroller.computeScrollOffset()` 获取当前的滚动位置,并根据需要更新 View 的位置。 3. 处理触摸事件:在触摸事件的回调方法中调用 Scroller 的 `startScroll()` 方法来启动滚动效果。可以根据触摸事件的不同情况调用不同的方法,如 `startScroll(int startX, int startY, int dx, int dy)` 或者 `startScroll(int startX, int startY, int dx, int dy, int duration)` 来指定滚动的起点、偏移量和持续时间。 4. 在 View 的 `invalidate()` 方法中不断重绘:在 `computeScroll()` 方法中更新了 View 的位置后,需要在 View 的 `invalidate()` 方法中调用,以便触发 View 的重新绘制。 需要注意的是,尽管 Android Scroller 提供了平滑滚动的功能,但它仅仅是一个工具类,实际的滚动效果实现还需要结合其他相关的 API 和组件来完成,如使用 `ViewGroup.LayoutParams` 来设置 View 的位置和大小,或者使用 `ViewPropertyAnimator` 实现更复杂的动画效果。 希望这个回答对你有帮助!如果有更多问题,请继续提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值