Android Seekbar的onProgressChanged监听回调之fromUser参数解析(源码层次)


在Android开发中,Seekbar是我们高频率使用的几个控件之一,不管是使用原生系统的样式,还是根据各种五花八门的需求来定制自己项目中的SeekBar(在这里吐槽下项目经理),相信大家都做过,那么闲话不说进入正题。
SeekBar的继承关系为:
Android Seekbar的onProgressChanged监听回调之fromUser参数解析(源码层次)_第1张图片
有些相对比较麻烦的需求要重写SeekBar,甚至有时候要直接写个重写继承View的SeekBar,但又想保留SeekBar的一些特性,比如我们今天要讲的fromUser参数,这个参数的作用是触发SeekBar的onProgressChanged回调接口时,可以根据这个参数判断是手动滑动SeekBar还是其他的一些方式改变了SeekBar的值。如果我们重写的自定义View要做这个功能,就要去了解这个参数的调用流程是怎样的,我们先来看Seekbar里的onProgressChanged是怎样被调用的,

/ frameworks / base / refs/heads/master / . / core / java / android / widget / SeekBar.java

    public SeekBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }
    //回调的地方
    @Override
    void onProgressRefresh(float scale, boolean fromUser, int progress) {
        super.onProgressRefresh(scale, fromUser, progress);
        if (mOnSeekBarChangeListener != null) {
            mOnSeekBarChangeListener.onProgressChanged(this, progress, fromUser);
        }
    }

可以看到onProgressChanged的fromUser参数是这个onProgressRefresh方法传过来的,那么这个onProgressRefresh是在哪里调用的呢?我们先去SeekBar的父类里看看,

 / frameworks / base / refs/heads/master / . / core / java / android / widget / AbsSeekBar.java

啊咧??找不到,在它的父类里完全找不到这个方法,那么我们再去AbsSeekBar的父类也就是ProgressBar里找找看,

....

    private synchronized void doRefreshProgress(int id, int progress, boolean fromUser,
            boolean callBackToApp, boolean animate) {
        final float scale = mMax > 0 ? progress / (float) mMax : 0;
        final boolean isPrimary = id == R.id.progress;
        if (isPrimary && animate) {
            final ObjectAnimator animator = ObjectAnimator.ofFloat(this, VISUAL_PROGRESS, scale);
            animator.setAutoCancel(true);
            animator.setDuration(PROGRESS_ANIM_DURATION);
            animator.setInterpolator(PROGRESS_ANIM_INTERPOLATOR);
            animator.start();
        } else {
            setVisualProgress(id, scale);
        }
        if (isPrimary && callBackToApp) {
        //注意这里
            onProgressRefresh(scale, fromUser, progress);
        }
    }
    ....

哈哈,找到了吧,原来这个onProgressRefresh是在SeekBar的祖父类里的doRefreshProgress被调用的,这个fromUser也是被这个方法传过来的,可以看到这个方法是private且是加了锁的,我们再往上追溯,还是在ProgressBar里,

....
    private synchronized void refreshProgress(int id, int progress, boolean fromUser,
            boolean animate) {
        if (mUiThreadId == Thread.currentThread().getId()) {
        //注意这里
            doRefreshProgress(id, progress, fromUser, true, animate);
        } else {
            if (mRefreshProgressRunnable == null) {
                mRefreshProgressRunnable = new RefreshProgressRunnable();
            }
            final RefreshData rd = RefreshData.obtain(id, progress, fromUser, animate);
            mRefreshData.add(rd);
            if (mAttached && !mRefreshIsPosted) {
                post(mRefreshProgressRunnable);
                mRefreshIsPosted = true;
            }
        }
    }
    ....

还是在ProgressBar里,再次向上追溯,

....
    @android.view.RemotableViewMethod
    synchronized boolean setProgressInternal(int progress, boolean fromUser, boolean animate) {
        if (mIndeterminate) {
            // Not applicable.
            return false;
        }
        progress = MathUtils.constrain(progress, 0, mMax);
        if (progress == mProgress) {
            // No change from current.
            return false;
        }
        mProgress = progress;
        //注意这里
        refreshProgress(R.id.progress, mProgress, fromUser, animate);
        return true;
    }
    ....

原来是在setProgressInternal这里被调用的,看方法名字就知道,意思是内部设置进度值,我们再看看这个方法是在哪里被调用的,

....
    /**
     * Sets the current progress to the specified value. Does not do anything
     * if the progress bar is in indeterminate mode.
     * 

* This method will immediately update the visual position of the progress * indicator. To animate the visual position to the target value, use * {@link #setProgress(int, boolean)}}. * * @param progress the new progress, between 0 and {@link #getMax()} * * @see #setIndeterminate(boolean) * @see #isIndeterminate() * @see #getProgress() * @see #incrementProgressBy(int) */ @android.view.RemotableViewMethod public synchronized void setProgress(int progress) { //注意这里 setProgressInternal(progress, false, false); } ....

看到这个方法是不是很熟悉啊?我们再用SeekBar时是不是经常调这个方法,这个方法实际上就调用了setProgressInternal方法,这个方法的第二个参数就是我们今天要说的fromUser参数,而且这个参数的源头就是由setProgressInternal来最终分发的,一层层一直传到我们的onProgressChanged里,看到这里是不是已经有点想法了呢?是不是直接设置值的时候穿false,那什么时候传true呢?答案就是我们直接点击Seekbar或者拖动的时候,只要有点击拖动那便会想到onTouchEvent吧,那么我们再去看看这个ProgressBar的onTouchEvent里,
…很可惜ProgressBar并没有重写这个方法,那我们再去SeekBar的父类AbsSeekBar里找找看,

....
   @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (!mIsUserSeekable || !isEnabled()) {
            return false;
        }
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                if (isInScrollingContainer()) {
                    mTouchDownX = event.getX();
                } else {
                    startDrag(event);
                }
                break;
            case MotionEvent.ACTION_MOVE:
                if (mIsDragging) {
                //注意这里
                    trackTouchEvent(event);
                } else {
                    final float x = event.getX();
                    if (Math.abs(x - mTouchDownX) > mScaledTouchSlop) {
                        startDrag(event);
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
                if (mIsDragging) {
                //注意这里
                    trackTouchEvent(event);
                    onStopTrackingTouch();
                    setPressed(false);
                } else {
                    // Touch up when we never crossed the touch slop threshold should
                    // be interpreted as a tap-seek to that location.
                    onStartTrackingTouch();
                    //注意这里
                    trackTouchEvent(event);
                    onStopTrackingTouch();
                }
                // ProgressBar doesn't know to repaint the thumb drawable
                // in its inactive state when the touch stops (because the
                // value has not apparently changed)
                invalidate();
                break;
            case MotionEvent.ACTION_CANCEL:
                if (mIsDragging) {
                    onStopTrackingTouch();
                    setPressed(false);
                }
                invalidate(); // see above explanation
                break;
        }
        return true;
    }
    private void startDrag(MotionEvent event) {
        setPressed(true);
        if (mThumb != null) {
            // This may be within the padding region.
            invalidate(mThumb.getBounds());
        }
        onStartTrackingTouch();
        //注意这里
        trackTouchEvent(event);
        attemptClaimDrag();
    }
    ....

果不其然找到了,注意这个类重写的onTouchEvent方法,很多地方都调用了这个trackTouchEvent(event)方法,我们再去看看这个方法干了什么事,

....
  private void trackTouchEvent(MotionEvent event) {
        final int x = Math.round(event.getX());
        final int y = Math.round(event.getY());
        final int width = getWidth();
        final int availableWidth = width - mPaddingLeft - mPaddingRight;
        final float scale;
        float progress = 0.0f;
        if (isLayoutRtl() && mMirrorForRtl) {
            if (x > width - mPaddingRight) {
                scale = 0.0f;
            } else if (x < mPaddingLeft) {
                scale = 1.0f;
            } else {
                scale = (availableWidth - x + mPaddingLeft) / (float) availableWidth;
                progress = mTouchProgressOffset;
            }
        } else {
            if (x < mPaddingLeft) {
                scale = 0.0f;
            } else if (x > width - mPaddingRight) {
                scale = 1.0f;
            } else {
                scale = (x - mPaddingLeft) / (float) availableWidth;
                progress = mTouchProgressOffset;
            }
        }
        final int max = getMax();
        progress += scale * max;
        setHotspot(x, y);
        //注意这里
        setProgressInternal(Math.round(progress), true, false);
    }
....

答案已经揭晓,这个trackTouchEvent最后就直接调用了我们ProgressBar里的setProgressInternal方法,而且注意第二个参数也就是我们的fromUser参数,传的是true!一切已然揭晓,在onTouchEvent的话说明是手动调节,理应传入true,在其他地方改变的话直接传false就好了,系统就是这么玩的,同学们明白了么?

你可能感兴趣的:(Android)