一起Talk Android吧(第四百六十回:SeekBar源代码分析)

Posted talk_8

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一起Talk Android吧(第四百六十回:SeekBar源代码分析)相关的知识,希望对你有一定的参考价值。

文章目录


各位看官们大家好,上一回中咱们说的例子是"SeekBar补充用法",这一回中咱们说的例子是"SeekBar源代码分析"。闲话休提,言归正转,让我们一起Talk android吧!

使用背景

我们在前面章回中介绍了SeekBar的使用方法,不过感觉还是使用不方便,于是便想分析它的源代码,通过源代码来了解它的功能。前面两篇关于它补充用法的博客就是在分析源代码的基础上得出来的。本章回将直接使用源代码进行分析,这样可以让大家对知识的了解更加深入一些。

分析结论

SeekBar本质上是进度条

我们从SeekBar类的继承体系上可以得出:SeekBar是进度条(ProgressBar)的子类,因此它本质上也是进度条。它的的继承体系如下:

SeekBar extends AbsSeekBar , AbsSeekBar extends ProgressBar .

此外,它只在父类的基础上添加了监听器功能,其它的事情都是父类完成的。

不推荐修改SeekBar的步进值

这个结论是我们分析它的父类(AbsSeekBar)后得出的,它的父类中有一个变量:

private int mKeyProgressIncrement = 1;

这个变量用来控制SeekBar的步进值,它默认值为1。这就是SeekBar的步进值默认为1的原因。该类提供了一个公有方法用来修改此变量,进而可以修改SeekBar的步进值,公有方法如下:

public void setKeyProgressIncrement(int increment) 
    mKeyProgressIncrement = increment < 0 ? -increment : increment;

不过在实际代码调用此方法后没有效果,原因是该类没有响应SeekBar的滑动事件,该类重写了onTouchEvent()方法和onKeyDown()方法,但是没有在在滑动事件中处理与步进有关的任何操作。这也是我在定义SeekBar的子类时重写onTouchEvent()方法的原因,我重写此方法时在滑动事件中修改了步进值。这种修改是有效果的,我们在前面博客中介绍过。

修改时使用的是incrementProgressBy()方法,该方法是从父类(ProgressBar)继承来的,也可以使用setKeyProgressIncrement()方法来实现相同的效果。下面是该类重写onTouchEvent()方法的源代码:

@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;

该类还重写了onKeyDown方法,并且在此方法中修改了步进值。下面是重写该方法的代码。

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) 
    if (isEnabled()) 
        int increment = mKeyProgressIncrement;
        switch (keyCode) 
            case KeyEvent.KEYCODE_DPAD_LEFT:
            case KeyEvent.KEYCODE_MINUS:
                increment = -increment;
                // fallthrough
            case KeyEvent.KEYCODE_DPAD_RIGHT:
            case KeyEvent.KEYCODE_PLUS:
            case KeyEvent.KEYCODE_EQUALS:
                increment = isLayoutRtl() ? -increment : increment;
                if (setProgressInternal(getProgress() + increment, true, true)) 
                    onKeyChange();
                    return true;
                
                break;
        
    
    return super.onKeyDown(keyCode, event);

我们拖动SeekBar时看不到这种效果,因为方法方法响应的是按下事件,而不是滑动事件。我们启动模拟器运行程序,按下键盘上的上下键进就会有效果。这时SeekBar的步进值就是setKeyProgressIncrement()方法的参数值。

AbsSeekBar类中还有一个地方修改了步进值,源代码如下:

@Override
public boolean performAccessibilityActionInternal(int action, Bundle arguments) 
    if (super.performAccessibilityActionInternal(action, arguments)) 
        return true;
    
    if (!isEnabled()) 
        return false;
    
    switch (action) 
        case R.id.accessibilityActionSetProgress: 
            if (!canUserSetProgress()) 
                return false;
            
            if (arguments == null || !arguments.containsKey(
                    AccessibilityNodeInfo.ACTION_ARGUMENT_PROGRESS_VALUE)) 
                return false;
            
            float value = arguments.getFloat(
                    AccessibilityNodeInfo.ACTION_ARGUMENT_PROGRESS_VALUE);
            return setProgressInternal((int) value, true, true);
        
        case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD:
        case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: 
            if (!canUserSetProgress()) 
                return false;
            
            int range = getMax() - getMin();
            int increment = Math.max(1, Math.round((float) range / 20));
            if (action == AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD) 
                increment = -increment;
            
            // Let progress bar handle clamping values.
            if (setProgressInternal(getProgress() + increment, true, true)) 
                onKeyChange();
                return true;
            
            return false;
        
    
    return false;

这个方法响应是的SeekBar的拖动事件,不过它走到canUserSetProgress()方法中时返回false,然后retun了,并没有运行下面的代码,而下面的代码才是真正修改步进值的代码。因此虽然在这里响应了滑动事件,但是并没有修改步进值。

我们再来看看canUserSetProgress()方法的源代码。

boolean canUserSetProgress() 
    return !isIndeterminate() && isEnabled();

上面的代码中isEnabled表示View是否处于Enable状态,该值默认为true.而isIndeterminate()方法是父类(ProgressBar)的方法,其源代码如下:

private void initProgressBar() 
    mMin = 0;
    mMax = 100;
    mProgress = 0;
    mSecondaryProgress = 0;
    mIndeterminate = false;
    mOnlyIndeterminate = false;
    mDuration = 4000;
    mBehavior = AlphaAnimation.RESTART;
    mMinWidth = 24;
    mMaxWidth = 48;
    mMinHeight = 24;
    mMaxHeight = 48;

/**
 * <p>Indicate whether this progress bar is in indeterminate mode.</p>
 *
 * @return true if the progress bar is in indeterminate mode
 */
@ViewDebug.ExportedProperty(category = "progress")
public synchronized boolean isIndeterminate() 
    return mIndeterminate;

从源代码中可以看出来该方法默认为返回false.这是因为构造方法默认将其设置为false.

分析了这么多源代码,核心结论只有一个:

canUserSetProgress()方法默认返回true,但是AbsSeekBar在滑动事件是将其进行了取反操作,因此AbsSeekBar类默认不能修改SeekBar的步进值。

如果能让canUserSetProgress()方法返回false是不是就可以修改步进值了?没错。我们可以通过SeekBar的indeterminate属性来让它返回false.该属性的默认值为true,可以在xml文件中将其设置为false.不过此时的SeekBar会自动从开始滑动到结束,而且是反复进行此操作,我们无法拖动它,这样的SeekBar也不是我们想要的。因此我们还是无法修改步进值。

修改步进值的方法在父类中

从众多的源代码中可以看到真正修改步进值的方法是setProgressInternal()方法,它是父类(ProgressBar)的方法,它的源代码如下:

synchronized boolean setProgressInternal(int progress, boolean fromUser, boolean animate) 
if (mIndeterminate) 
        // Not applicable.
        return false;
    
    progress = MathUtils.constrain(progress, mMin, mMax);
    if (progress == mProgress) 
        // No change from current.
        return false;
    
    mProgress = progress;
    refreshProgress(R.id.progress, mProgress, fromUser, animate);
    return true;

续续分析源代码就会发现ProgressBar类使用了动画来实现进度条的滑动效果。这里的方法调用比较多,不过调用关系很清楚,而且所有方法都在一个类中,我就不一一列出了,大家可以自己去分析。我们可以借鉴这个思路:

遇到滑动的效果可以使用动画来实现。

在分析源代码时我还发现setProgress()方法也调用了setProgressInternal(),它的源代码如下:

@android.view.RemotableViewMethod
public synchronized void setProgress(int progress) 
    setProgressInternal(progress, false, false);

那么我们可以使用setProgress()方法修改步进值。现在一共有三个方法可以修改走进值了。

关于SeekBar的源代码分析就到此为止吧,下面是分析过程中涉及的文件,请大家参考:

/frameworks/base/core/java/android/widget/SeekBar.java
/frameworks/base/core/java/android/widget/AbsSeekBar.java
/frameworks/base/core/java/android/widget/ProgressBar.java

看官们,关于"SeekBar源代码分析"的例子咱们就介绍到这里,欲知后面还有什么例子,且听下回分解!

以上是关于一起Talk Android吧(第四百六十回:SeekBar源代码分析)的主要内容,如果未能解决你的问题,请参考以下文章

一起Talk Android吧(第四百六十五回:自定义View的思路)

一起Talk Android吧(第四百六十一回:再谈从drawable中获取Bitmap)

一起Talk Android吧(第四百六十八回:实现自定义View中的布局功能)

一起Talk Android吧(第四百六十九回:实现自定义View中的绘制功能)

一起Talk Android吧(第四百六十七回:实现自定义ViewGroup中的测量功能)

一起Talk Android吧(第四百六十六回:实现自定义View中的测量功能)