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>
显示效果是,竖向排列的一个按钮,一个文本框,一个按钮。
不过里面的逻辑倒是很复杂。没法细说,因为我还没弄懂。后续更新。
目前疑点:
ImageButton
的内容,貌似没有看到。NumberPicker
的内容是循环显示的,如何让其不循环显示。就像音乐播放器有循环播放,顺序播放这样。能不能不循环?要怎么操作?NumberPicker
的布局是三个子控件组成的,也就是一次只能显示3行,能不能改成5行,或者7行?要怎么改。除了改布局文件,还要改什么?希望有大神可以解答,我也会继续去研究这个源码。
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,value
的map
。
所以这里的逻辑就简单了,核心代码是两句:
canvas.drawText(scrollSelectorValue, x, y, mSelectorWheelPaint);
y += mSelectorElementHeight;
也就是一行一行的调用 canvas.drawText()
把之前存储的数据刷新到UI 上面去。
—————— update ———————————
尴尬了,实际上NumberPicker
的布局可能并没有ImageButton
。而是只有一个EditText
。
———-update—————-
特别注意一个变量:mHasSelectorWheel
,以及一句代码:setWillNotDraw(!mHasSelectorWheel);
这个方法的含义 setWillNotDraw();方法的使用 表明了,如果 mHasSelectorWheel == false
, 则NumberPicker
的 onDraw()
方法不会被执行。不过,既然是有个控件,肯定是要显示的,所以这个变量的取值正常情况下肯定是 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 的宽高都是100。
final 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文字基线(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 -->