android NumberPicker 浅析

一个参考对象:Android NumberPicker探析

一个参考对象:NumberPicker 文档

NumberPicker是一个继承自LinearLayout的系统控件,布局很简单.




<merge xmlns:android="http://schemas.android.com/apk/res/android">

    <ImageButton android:id="@+id/increment"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:background="@android:drawable/numberpicker_up_btn"
        android:paddingTop="22dip"
        android:paddingBottom="22dip"
        android:contentDescription="@string/number_picker_increment_button" />

    <EditText
        android:id="@+id/numberpicker_input"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:textAppearance="@style/TextAppearance.Large.Inverse.NumberPickerInputText"
        android:gravity="center"
        android:singleLine="true"
        android:background="@drawable/numberpicker_input" />

    <ImageButton android:id="@+id/decrement"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:background="@android:drawable/numberpicker_down_btn"
        android:paddingTop="22dip"
        android:paddingBottom="22dip"
        android:contentDescription="@string/number_picker_decrement_button" />

merge>

显示效果是,竖向排列的一个按钮,一个文本框,一个按钮。
不过里面的逻辑倒是很复杂。没法细说,因为我还没弄懂。后续更新。

目前疑点:

  1. 怎么更新ImageButton的内容,貌似没有看到。
  2. NumberPicker的内容是循环显示的,如何让其不循环显示。就像音乐播放器有循环播放,顺序播放这样。能不能不循环?要怎么操作?
  3. NumberPicker的布局是三个子控件组成的,也就是一次只能显示3行,能不能改成5行,或者7行?要怎么改。除了改布局文件,还要改什么?
  4. 更多定制如何下手?

希望有大神可以解答,我也会继续去研究这个源码。


update 2018-08-07

发现ImageButton是一个ImageView,本身并没有setText()这样的方法,所以,它的内容更新肯定是在NumberPicker里面了。

貌似是通过更新mVirtualButtonPressedDrawable这个Drawable来实现的。那就要看这个对象是怎么更新的了。

—- update —–

不对,Drawable也没有去更新文本内容,似乎是onDraw()里面的canvas.drawText(scrollSelectorValue, x, y, mSelectorWheelPaint);执行了跟新ImageButton上面的文字内容的。

—— 今天先到这,这是一个长期的分析。—- 不一定要一次搞定,也不一定要一天内搞定,可以是一周,也可以是一个月,不急。2018-08-07 22:40:06

——— update —–

调用NumberPicker.setWrapSelectorWheel(false);可以让NumberPicker并不循环显示。

——- update ————
mSelectorIndices是一个数组,长度是3.

/**
 * The number of items show in the selector wheel. | 跟显示行数有关,指定是3
 */ 
private static final int SELECTOR_WHEEL_ITEM_COUNT = 3;

/**
 * The selector indices whose value are show by the selector.| 跟显示行数有关,指定是3
 */
private final int[] mSelectorIndices = new int[SELECTOR_WHEEL_ITEM_COUNT];

/**
 * The {@link Drawable} for pressed virtual (increment/decrement) buttons. | 跟 上下两个 ImageButton 有关
 */
private final Drawable mVirtualButtonPressedDrawable;

/**
 * The back ground color used to optimize scroller fading.|肯定跟什么背景颜色有关系
 */
private final int mSolidColor;
/**
 * Divider for showing item to be selected while scrolling | 根据最前面的引用博客,这个是用来显示分割线背景颜色的。[一共两条分割线]
 */
private final Drawable mSelectionDivider;

哈哈哈:

// 修改分割线高度成功
void setDividerHeight(NumberPicker np, int height) {
    try {
        Field field = NumberPicker.class.getDeclaredField("mSelectionDividerHeight");
        field.setAccessible(true);
        field.set(np, height);
    } catch (NoSuchFieldException | IllegalAccessException e) {
        e.printStackTrace();
    }
}

——- update ————-

/**
 * Changes the current value by one which is increment or
 * decrement based on the passes argument.
 * decrement the current value.
 *
 * @param increment True to increment, false to decrement.
 */
 private void changeValueByOne(boolean increment) {
    if (mHasSelectorWheel) {
        mInputText.setVisibility(View.INVISIBLE);
        if (!moveToFinalScrollerPosition(mFlingScroller)) {
            moveToFinalScrollerPosition(mAdjustScroller);
        }
        mPreviousScrollerY = 0;
        if (increment) {
            mFlingScroller.startScroll(0, 0, 0, -mSelectorElementHeight, SNAP_SCROLL_DURATION);
        } else {
            mFlingScroller.startScroll(0, 0, 0, mSelectorElementHeight, SNAP_SCROLL_DURATION);
        }
        invalidate();
    } else {
        if (increment) {
            setValueInternal(mValue + 1, true);
        } else {
            setValueInternal(mValue - 1, true);
        }
    }
}

看一下这个方法:changeValueByOne(bool) ,首先根据一个变量判断是走if还是else.然后,看一下这两个的逻辑。走if的话,就是根据点击的按钮,然后滑动一个元素的高度;走else的话,就是直接改变元素的值,没有滑动动画了。

所以,这个方法的逻辑和名字是一样的,就是将控件的当前数值显示+1/-1

    /**
     * Sets the current value of this NumberPicker.
     *
     * @param current The new value of the NumberPicker.
     * @param notifyChange Whether to notify if the current value changed.
     */
    private void setValueInternal(int current, boolean notifyChange) {
        if (mValue == current) {
            return;
        }
        // Wrap around the values if we go past the start or end
        if (mWrapSelectorWheel) {
            current = getWrappedSelectorIndex(current);
        } else {
            current = Math.max(current, mMinValue);
            current = Math.min(current, mMaxValue);
        }
        int previous = mValue;
        mValue = current;
        updateInputTextView();
        if (notifyChange) {
            notifyChange(previous, current);
        }
        initializeSelectorWheelIndices();
        invalidate();
    }

这里的setValueInternal()就是在不滑动的情况下,点击+/-的时候,改变显示数值的逻辑。

这里根据 mWrapSelectorWheel 判断数值是不是循环显示的。它标明了一句注释:// Wrap around the values if we go past the start or end,感觉词不达意的。逻辑上,这句话的意思就是【如果到了起始值的时候,数值会循环显示。感觉表达能力越来越差了。我以前表达能力很好的,缺乏训练。。。

先看一下循环显示的话,会怎么样。

/**
 * @return The wrapped index selectorIndex value.
 * 假装翻译:如果当前值大于最大值,就返回最小值;如果当前值小于最小值,就返回最大值。
 */
private int getWrappedSelectorIndex(int selectorIndex) {
    if (selectorIndex > mMaxValue) {
        return mMinValue + (selectorIndex - mMaxValue) % (mMaxValue - mMinValue) - 1;
    } else if (selectorIndex < mMinValue) {
        return mMaxValue - (mMinValue - selectorIndex) % (mMaxValue - mMinValue) + 1;
    }
    return selectorIndex;
}

它这个表达式写的让人看不懂,不清楚它的数学意义。但是,把数字代入进去算一下就明白了,它的意思就是,如果当前值大于最大值,就返回最小值;如果当前值小于最小值,就返回最大值。

比如 min = 3, max = 6, 那第一行的逻辑就是 return 3 + (7-6)%(6-3)-1; ===> return 3; 

备注一下为什么是 7-6而不是100-6,因为它里面的计算每次都是+1/-1,所以只能是比最大值 大1,不会大超过1.

如果不循环显示,逻辑更简单了:

current = Math.max(current, mMinValue);
current = Math.min(current, mMaxValue);

这两个表达式一起使用的话,意思就很明显了,current最大等于最大值,最小等于最小值,不会超出这个范围。这个就是不循环的效果。

比如 min=3 , max=6 , 如果 current=2 ,则让current=3。*而不是6* ,这就不会循环了。

然后updateInputTextView()是更新中间的EditText内容的逻辑。

    /**
     * Updates the view of this NumberPicker. If displayValues were specified in
     * the string corresponding to the index specified by the current value will
     * be returned. Otherwise, the formatter specified in {@link #setFormatter}
     * will be used to format the number.
     *
     * @return Whether the text was updated.
     */
    private boolean updateInputTextView() {
        /*
         * If we don't have displayed values then use the current number else
         * find the correct value in the displayed values for the current
         * number.
         */
        String text = (mDisplayedValues == null) ? formatNumber(mValue)
                : mDisplayedValues[mValue - mMinValue];
        if (!TextUtils.isEmpty(text) && !text.equals(mInputText.getText().toString())) {
            mInputText.setText(text);
            return true;
        }

        return false;
    }

这个就比较简单了,就看一种,text = mDisplayedValues[mValue - mMinValue];

比如:min=3 , current = 5,则 text = mDisplayedValues[5-3];也就是说,无论min的值是多少,反正得从0开始计算,按照数组的索引来。

在更新了EditText内容之后,是

    /**
     * Resets the selector indices and clear the cached string representation of
     * these indices.
假装翻译:设置将要显示的3行的 index,values的值。【是不是循环显示的,都考虑了】,
把要显示的3行的 index, 存到 mSelectorIndices 数组里面
     */
    private void initializeSelectorWheelIndices() {
        mSelectorIndexToStringCache.clear();
        int[] selectorIndices = mSelectorIndices;
        int current = getValue();
        for (int i = 0; i < mSelectorIndices.length; i++) {
            int selectorIndex = current + (i - SELECTOR_MIDDLE_ITEM_INDEX);
            if (mWrapSelectorWheel) {
                selectorIndex = getWrappedSelectorIndex(selectorIndex);
            }
            selectorIndices[i] = selectorIndex;
            ensureCachedScrollSelectorValue(selectorIndices[i]);
        }
    }

这个逻辑写的也让人看不懂。还是根据数字代入的方式去理解。(手动滑稽,确实无法理解它这个表达式的数学含义)

for循环里面,先假设min=3 , max=6 , current = 6.

i=0时,selectorIndex = 6+ 0 -1 == 5
i=1时,selectorIndex = 6+ 1 -1 == 6
i=1时,selectorIndex = 6+ 1 -1 == 7

能大致看出来什么了吗? 如果界面上,中间值显示是6, 那第一行肯定是5, 第三行是7(假设最大值大于7)。所有这个逻辑就是更新将要刷新到界面上面的数据信息。

然后下以及是:

    /* Ensures we have a cached string representation of the given 
     * selectorIndex to avoid multiple instantiations of the same string.
     * 假装翻译:把要显示的3行的 index,value 存储到 Map 里面。
     */
    private void ensureCachedScrollSelectorValue(int selectorIndex) {
        SparseArray<String> cache = mSelectorIndexToStringCache;
        String scrollSelectorValue = cache.get(selectorIndex);
        if (scrollSelectorValue != null) {
            return;
        }
        if (selectorIndex < mMinValue || selectorIndex > mMaxValue) {
            scrollSelectorValue = "";
        } else {
            if (mDisplayedValues != null) {
                int displayedValueIndex = selectorIndex - mMinValue;
                scrollSelectorValue = mDisplayedValues[displayedValueIndex];
            } else {
                scrollSelectorValue = formatNumber(selectorIndex);
            }
        }
        cache.put(selectorIndex, scrollSelectorValue);
    }

这里的逻辑就是:把index,value映射关联起来,并存到一个map里面。这里的index就是这3行对应的数字索引,value就是设置的要显示的字符串值。如果index > max或者 current < min, 则value="",也就是界面那一行显示空字符串,不显示文本内容。

既然这里只是更新数据,并没有做到刷新UI,那么刷新UI肯定是在onDraw()之类的方法里面咯,那就去看一下:

onDraw(){
// .... 其他逻辑

// draw the selector wheel
        int[] selectorIndices = mSelectorIndices;
        for (int i = 0; i < selectorIndices.length; i++) {
            int selectorIndex = selectorIndices[i];
            String scrollSelectorValue = mSelectorIndexToStringCache.get(selectorIndex);
            // Do not draw the middle item if input is visible since the input
            // is shown only if the wheel is static and it covers the middle
            // item. Otherwise, if the user starts editing the text via the
            // IME he may see a dimmed version of the old value intermixed
            // with the new one.
            if ((showSelectorWheel && i != SELECTOR_MIDDLE_ITEM_INDEX) ||
                (i == SELECTOR_MIDDLE_ITEM_INDEX && mInputText.getVisibility() != VISIBLE)) {
                canvas.drawText(scrollSelectorValue, x, y, mSelectorWheelPaint);
            }
            y += mSelectorElementHeight;
        }

// .....
// .... 其他逻辑

}

这里代码不多,mSelectorIndices就是前面在initializeSelectorWheelIndices()存放了将要显示的3行的 index 的数组,mSelectorIndexToStringCache就是在前面ensureCachedScrollSelectorValue(index)存储了index,valuemap

所以这里的逻辑就简单了,核心代码是两句:

canvas.drawText(scrollSelectorValue, x, y, mSelectorWheelPaint);

y += mSelectorElementHeight;

也就是一行一行的调用 canvas.drawText() 把之前存储的数据刷新到UI 上面去。

—————— update ———————————

尴尬了,实际上NumberPicker的布局可能并没有ImageButton。而是只有一个EditText

———-update—————-

特别注意一个变量:mHasSelectorWheel,以及一句代码:setWillNotDraw(!mHasSelectorWheel);

这个方法的含义 setWillNotDraw();方法的使用 表明了,如果 mHasSelectorWheel == false, 则NumberPickeronDraw()方法不会被执行。不过,既然是有个控件,肯定是要显示的,所以这个变量的取值正常情况下肯定是 true

看一下onLayout()方法,这个方法很有意思的。

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    // 其他逻辑。。。。
    final int msrdWdth = getMeasuredWidth();
    final int msrdHght = getMeasuredHeight();

    // Input text centered horizontally.
    final int inptTxtMsrdWdth = mInputText.getMeasuredWidth();
    final int inptTxtMsrdHght = mInputText.getMeasuredHeight();
    final int inptTxtLeft = (msrdWdth - inptTxtMsrdWdth) / 2;
    final int inptTxtTop = (msrdHght - inptTxtMsrdHght) / 2;
    final int inptTxtRight = inptTxtLeft + inptTxtMsrdWdth;
    final int inptTxtBottom = inptTxtTop + inptTxtMsrdHght;
    mInputText.layout(inptTxtLeft, inptTxtTop, inptTxtRight, inptTxtBottom);
 // 其他逻辑....
}

看看这段代码,首先获取自己的测量宽高,然后是获取里面的EditText的测量宽高。这样就得到了4个宽高变量。
然后下面一段计算让人看不懂,不知道到底在做什么。进行数值代入计算。

假设 editText 宽高都是 10 , NumberPicker 的宽高都是100final int inptTxtLeft = (100 - 10)/2 = 45;
final int inptTxtTop = (100 -10)/2 = 45;
final int inptTxtRight = 45 + 10 = 55
final int inptTxtBottom = 45 + 10 = 55;
mInputText.layout(45, 45, 55, 55); // 没有改变EditText 大小,但是让它居中了。

所以这里可以看到,是对EditText进行居中显示了。不过好像并没有对上下两个ImageButton做任何操作。

还是在onLayout()里面,在给EditText居中之后,看又做了什么?

if (changed) {
    // need to do all this when we know our size
    initializeSelectorWheel();
    initializeFadingEdges();
    mTopSelectionDividerTop = (getHeight() - mSelectionDividersDistance) / 2
            - mSelectionDividerHeight;
    mBottomSelectionDividerBottom = mTopSelectionDividerTop + 2 * mSelectionDividerHeight
            + mSelectionDividersDistance;
}

首先是调用了initializeSelectorWheel();这个首先调用了:initializeSelectorWheelIndices();不过这个方法前面已经分析过了,就是准备好要显示的3行的索引值,以及对应的字符串值,并存储在map里面。

接下来呢?

    private void initializeSelectorWheel() {
        initializeSelectorWheelIndices();
        int[] selectorIndices = mSelectorIndices;
        int totalTextHeight = selectorIndices.length * mTextSize; // 每行文字高度的总和
        float totalTextGapHeight = (mBottom - mTop) - totalTextHeight;// 控件高度 - 文字高度总和 = 文字间隙高度总和
        float textGapCount = selectorIndices.length; // 文字间隙高度总量
        mSelectorTextGapHeight = (int) (totalTextGapHeight / textGapCount + 0.5f); // 单行文字间隙高度 [间隙指的应该是“单行高度-单行文字高度”]
        mSelectorElementHeight = mTextSize + mSelectorTextGapHeight; // 得到单行的高度
        // Ensure that the middle item is positioned the same as the text in
        // mInputText
        int editTextTextPosition = mInputText.getBaseline() + mInputText.getTop(); // 得到 editText 的 baseline 距离 NumberPicker 的顶部距离。
        mInitialScrollOffset = editTextTextPosition
                - (mSelectorElementHeight * SELECTOR_MIDDLE_ITEM_INDEX);
        // 得到 去 et 的 baseline 需要执行的偏移量(表达的不好,这一句)
        mCurrentScrollOffset = mInitialScrollOffset;
        updateInputTextView(); // et.setText(); 刷新 et 的文字
    }

关于文字基线:Android文字基线(Baseline)算法

android NumberPicker 浅析_第1张图片

这张图片来自Android文字基线(Baseline)算法

另外:baseline 类似 height ,跟位置无关,top 跟位置有关

接下来调用了一个方法initializeFadingEdges();
不过这个没有什么用,是设置什么渐变效果的边缘的。这个暂时不管。好像是让NumberPicker的上下都是有一个透明度一样的效果的。

====================== 调用栈 ====== 放在最后面=============

然后后面还有两行分别是获取上分割线最上面的y坐标,与下分割线最下面的 y 坐标的。


            mTopSelectionDividerTop = (getHeight() - mSelectionDividersDistance) / 2
                    - mSelectionDividerHeight;
            // toph = (总高度 - 两个分割线之间的距离)/2 - 分割线的高度;
            mBottomSelectionDividerBottom = mTopSelectionDividerTop + 2 * mSelectionDividerHeight
                    + mSelectionDividersDistance;
             // bottomH 同理。

然后是看一下

@Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        if (!mHasSelectorWheel || !isEnabled()) {
            return false;
        }
        final int action = event.getActionMasked();
        switch (action) {
            case MotionEvent.ACTION_DOWN: {
                removeAllCallbacks();
                mInputText.setVisibility(View.INVISIBLE);
                mLastDownOrMoveEventY = mLastDownEventY = event.getY();
                mLastDownEventTime = event.getEventTime();
                mIgnoreMoveEvents = false;
                mPerformClickOnTap = false;
                // Handle pressed state before any state change.
                if (mLastDownEventY < mTopSelectionDividerTop) {
                // 如果点击区域在第一条分割线的上面,发送BUTTON_DECREMENT
                    if (mScrollState == OnScrollListener.SCROLL_STATE_IDLE) {
                        mPressedStateHelper.buttonPressDelayed(
                                PressedStateHelper.BUTTON_DECREMENT);
                    }
                } else if (mLastDownEventY > mBottomSelectionDividerBottom) {
                    if (mScrollState == OnScrollListener.SCROLL_STATE_IDLE) {
                        mPressedStateHelper.buttonPressDelayed(
                                PressedStateHelper.BUTTON_INCREMENT);
                    }
                }
                // more codes .....

这里首先隐藏了EditText,然后并且当前只是点击,并不是滑动的情况下,根据当前按下的位置,判断如果在第一条分割线的上面,就是按点击上面的区域处理,如果当前按下的位置低于 第二条分割线的下面,就按点击下面的区域处理。

看一个具体的代码:

mPressedStateHelper.buttonPressDelayed(PressedStateHelper.BUTTON_INCREMENT);

public void buttonPressDelayed(int button) {
            cancel();
            mMode = MODE_PRESS;
            mManagedButton = button;
            NumberPicker.this.postDelayed(this, ViewConfiguration.getTapTimeout());
        }

@Override
        public void run() {
            switch (mMode) {
                case MODE_PRESS: {
                    switch (mManagedButton) {
                        case BUTTON_INCREMENT: {
                            mIncrementVirtualButtonPressed = true;
                            invalidate(0, mBottomSelectionDividerBottom, mRight, mBottom);
                        } break;
                        case BUTTON_DECREMENT: {
                            mDecrementVirtualButtonPressed = true;
                            invalidate(0, 0, mRight, mTopSelectionDividerTop);
                        }
                                        // more code

可以看到 buttonPressDelayed()里面只是更新了几个变量,主要是最后的NumberPicker.this.postDelayed(this, ViewConfiguration.getTapTimeout());

这里的 this,当然就要看run()方法了,this是一个Runnable嘛。~

所有,可以看到,点击第一条分割线上面的部分实际执行的逻辑是

mDecrementVirtualButtonPressed = true;
invalidate(0, 0, mRight, mTopSelectionDividerTop); // 局部刷新,顶部到第一条分割线之间的区域。

对应到onDraw()里面,就是:

if (mDecrementVirtualButtonPressed) {
    mVirtualButtonPressedDrawable.setState(PRESSED_STATE_SET);
    mVirtualButtonPressedDrawable.setBounds(0, 0, mRight, mTopSelectionDividerTop);
    mVirtualButtonPressedDrawable.draw(canvas);
}

这个代码的意思肯定是,把上面的部分作为一个区域,然后设置状态是PRESSED_STATE_SET。然后绘制。{这个设计到Drawable了,不熟悉。反正大致的意思肯定就是让这边有一个点击的效果}

然后这里也能看出来mVirtualButtonPressedDrawable无所谓初始值是什么,只要不是null就可以了,然后在使用的时候,再指定区域,状态,就能用了。

顺便看一下onDraw()里面的最后一段逻辑:

// draw the selection dividers
        if (showSelectorWheel && mSelectionDivider != null) {
            // draw the top divider
            int topOfTopDivider = mTopSelectionDividerTop;
            int bottomOfTopDivider = topOfTopDivider + mSelectionDividerHeight;
            mSelectionDivider.setBounds(0, topOfTopDivider, mRight, bottomOfTopDivider);
            mSelectionDivider.draw(canvas);

            // draw the bottom divider
            int bottomOfBottomDivider = mBottomSelectionDividerBottom;
            int topOfBottomDivider = bottomOfBottomDivider - mSelectionDividerHeight;
            mSelectionDivider.setBounds(0, topOfBottomDivider, mRight, bottomOfBottomDivider);
            mSelectionDivider.draw(canvas);
        }

这段逻辑现在来看就很清楚了。mTopSelectionDividerTop是第一个分割线最上面的 y 坐标,mBottomSelectionDividerBottom是第二条分割线最下面的 y 坐标。
很明确,这段逻辑就是指定两条分割线所在区域,然后显示出来。

点击下面的部分逻辑肯定同理了。

再回到 onInterceptTouchEvent()方法里面,看剩下的逻辑:

                // Make sure we support flinging inside scrollables.
                getParent().requestDisallowInterceptTouchEvent(true);
                if (!mFlingScroller.isFinished()) {
                    mFlingScroller.forceFinished(true);
                    mAdjustScroller.forceFinished(true);
                    onScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
                } else if (!mAdjustScroller.isFinished()) {
                    mFlingScroller.forceFinished(true);
                    mAdjustScroller.forceFinished(true);
                } else if (mLastDownEventY < mTopSelectionDividerTop) {
                // 如果点击的是上方区域,发送消息,传送 false
                    hideSoftInput();
                    postChangeCurrentByOneFromLongPress(
                            false, ViewConfiguration.getLongPressTimeout());
                } else if (mLastDownEventY > mBottomSelectionDividerBottom) {
                    hideSoftInput();
                    postChangeCurrentByOneFromLongPress(
                            true, ViewConfiguration.getLongPressTimeout());
                } else {
                    mPerformClickOnTap = true;
                    postBeginSoftInputOnLongPressCommand();
                }

首先,确保自己可以滑动。
然后,如果当前在滑动,那么按下的时候就去停止滑动。
这里会去调用一个方法:

/**
     * Posts a command for changing the current value by one.
     *
     * @param increment Whether to increment or decrement the value.
     */
    private void postChangeCurrentByOneFromLongPress(boolean increment, long delayMillis) {
        if (mChangeCurrentByOneFromLongPressCommand == null) {
            mChangeCurrentByOneFromLongPressCommand = new ChangeCurrentByOneFromLongPressCommand();
        } else {
            removeCallbacks(mChangeCurrentByOneFromLongPressCommand);
        }
        mChangeCurrentByOneFromLongPressCommand.setStep(increment);
        postDelayed(mChangeCurrentByOneFromLongPressCommand, delayMillis);
    }

不过这个方法没有执行实际的逻辑,仅仅起到了消息传递的作用。
然后就看到:

class ChangeCurrentByOneFromLongPressCommand implements Runnable {
    private boolean mIncrement;

    private void setStep(boolean increment) {
        mIncrement = increment;
    }

    @Override
    public void run() {
        changeValueByOne(mIncrement);
        postDelayed(this, mLongPressUpdateInterval);
    }
}

不过这个类也只是对数据封装了一下,真正的逻辑在changeValueByOne(mIncrement);里面。

if (mHasSelectorWheel) {
   hideSoftInput();
    if (!moveToFinalScrollerPosition(mFlingScroller)) {
        moveToFinalScrollerPosition(mAdjustScroller);
    }
    mPreviousScrollerY = 0;
    if (increment) {
        mFlingScroller.startScroll(0, 0, 0, -mSelectorElementHeight, SNAP_SCROLL_DURATION);
    } else {
        mFlingScroller.startScroll(0, 0, 0, mSelectorElementHeight, SNAP_SCROLL_DURATION);
    }
    invalidate();
}

所有核心逻辑就是mFlingScroller.startScroll(0, 0, 0, mSelectorElementHeight, SNAP_SCROLL_DURATION);

也就是说,点击第一条分割线上方的区域,会让整个NumberPicker 向下滚动一个 item高度的距离。

连起来看,在onInterceptEvent()里面的逻辑就是,如果点击的是上方区域,那么,就去改变该区域的背景;并且,去向下滑动当前控件一个item高度的距离(持续300ms)。

onTouch里面主要是判断速度了,然后也是更新这些东西,这个下次再分析。

imageButton 点击 -> onClickListener -->

你可能感兴趣的:(android)