RadialTimePickerView
浅析关于角度与弧度:看这个链接
.
从这里我们至少知道了一点,就是360° == 2*PI
,180° == PI
.
static {
// Prepare mapping to snap touchable degrees to selectable degrees.
preparePrefer30sMap();
final double increment = 2.0 * Math.PI / NUM_POSITIONS;
// NUM_POSITIONS == 12;
// increment = 2*PI/12; --> 钟表上时钟一共12格,则 increment 表示是两格之间的弧度 ==> 对应的角度就是 360/12 = 30°
double angle = Math.PI / 2.0; // --> 90° 对应的弧度
for (int i = 0; i < NUM_POSITIONS; i++) {
COS_30[i] = (float) Math.cos(angle);
SIN_30[i] = (float) Math.sin(angle);
angle += increment; // 换成角度就是 angle += 30 , angle 起始角度是 90
// angle --> [90,120,150,180,210,240,270,300,330,360,390(30),420(60)]
}
}
这里先调用了一个方法:preparePrefer30sMap();
preparePrefer30sMap();
这个方法就是给一个数组赋值,这个数组就是:SNAP_PREFER_30S_MAP
,赋值之后的数组内容是:(我打印出来的[只打印了不重复的元素])
SNAP_PREFER_30S_MAP=
[0,
6, 12, 18, 24, 30, 36,
42, 48, 54, 60, 66, 72,
114, 120, 126, 132, 138, 144,
150, 156, 162, 168, 174, 180,
186, 192, 198, 204, 210, 216,
222, 228, 234, 240, 246, 252,
258, 264, 270, 276, 282, 288,
294, 300, 306, 312, 318, 324,
330, 336, 342, 348, 354, 360]
// length == 61
看到这个数组, 原本长度是
361
,现在是61
(去重之后的)。然后值好像有一种规律,但是目前还看不出是什么规律。
我们知道,钟表的分钟一共是60
个刻度,但是一个圆,可以划分成360
个度数。那么,常理来讲,肯定是,6
个度数转成一个分钟的刻度。那么这里大胆猜测一下,这个数组的作用就是记录每个角度对应的分钟的。怎么理解呢?比如:0-6
对应的是第一分钟,6-12
对应的是第二分钟。不过它这里不是这么表示的。它的表示方式是0-7
对应的是0
,8-11
对应的是6
。然后0
表示第一分钟,6
表示第二分钟。。。。直到354-360
对应的是360
,表示的是第60
分钟。
然后下面还有一个方法,就是用来根据索引取这个数组的值的:
/**
* Returns mapping of any input degrees (0 to 360) to one of 60 selectable output degrees,
* where the degrees corresponding to visible numbers (i.e. those divisible by 30) will be
* weighted heavier than the degrees corresponding to non-visible numbers.
* See {@link #preparePrefer30sMap()} documentation for the rationale and generation of the
* mapping.
*/
private static int snapPrefer30s(int degrees) {
if (SNAP_PREFER_30S_MAP == null) {
return -1;
}
return SNAP_PREFER_30S_MAP[degrees];
}
这个数组做的事情应该就是上面的猜测。返回值的不重复结果的长度为61
,所以这个方法(snapPrefer30s()
)大概是给分钟用的。
snapOnly30s();
/**
* Returns mapping of any input degrees (0 to 360) to one of 12 visible output degrees (all
* multiples of 30), where the input will be "snapped" to the closest visible degrees.
* @param degrees The input degrees
* @param forceHigherOrLower The output may be forced to either the higher or lower step, or may
* be allowed to snap to whichever is closer. Use 1 to force strictly higher, -1 to force
* strictly lower, and 0 to snap to the closer one.
* @return output degrees, will be a multiple of 30
*/
private static int snapOnly30s(int degrees, int forceHigherOrLower) {
final int stepSize = DEGREES_FOR_ONE_HOUR; // == 360/12 == 30
int floor = (degrees / stepSize) * stepSize;
final int ceiling = floor + stepSize;
if (forceHigherOrLower == 1) {
degrees = ceiling;
} else if (forceHigherOrLower == -1) {
if (degrees == floor) {
floor -= stepSize;
}
degrees = floor;
} else {
if ((degrees - floor) < (ceiling - degrees)) {
degrees = floor;
} else {
degrees = ceiling;
}
}
return degrees;
}
这个函数的意思不明确,代入数字计算一下,degrees
取值范围肯定是0-360
了,forceHigherOrLower
看它的逻辑,只有3
种可能,1 , -1
或者一个其他的数字,比如:0 , 100,-23
这样的。
然后看一下输出:
forceHigherOrLower == 0
, 函数返回值的为0 ,30 ,60 ,90 ,120, 150, 180, 210, 240, 270, 300, 330, 360
[共13个]forceHigherOrLower == 1
, 函数返回值的为30 , 60 ,...,390
forceHigherOrLower == 1
, 函数返回值的为-30 , 60 ,...,330
无论哪种情况,返回的值只有
13
种。对应的时钟,一共是12格。所有这个方法(snapOnly30s()
)大概是给时钟用的。
然后看两个方法:
setCurrentHourInternal()
/**
* Sets the current hour.
*
* @param hour The current hour
* @param callback Whether the value listener should be invoked
* @param autoAdvance Whether the listener should auto-advance to the next
* selection mode, e.g. hour to minutes
*/
private void setCurrentHourInternal(int hour, boolean callback, boolean autoAdvance) {
final int degrees = (hour % 12) * DEGREES_FOR_ONE_HOUR;
// degress = (hour%12)* 30 --> 11*30 ==330
mSelectionDegrees[HOURS] = degrees;
// 0 is 12 AM (midnight) and 12 is 12 PM (noon).
final int amOrPm = (hour == 0 || (hour % 24) < 12) ? AM : PM;
final boolean isOnInnerCircle = getInnerCircleForHour(hour);
if (mAmOrPm != amOrPm || mIsOnInnerCircle != isOnInnerCircle) {
mAmOrPm = amOrPm;
mIsOnInnerCircle = isOnInnerCircle;
initData();
mTouchHelper.invalidateRoot();
}
invalidate();
if (callback && mListener != null) {
mListener.onValueSelected(HOURS, hour, autoAdvance);
}
}
setCurrentMinuteInternal()
private void setCurrentMinuteInternal(int minute, boolean callback) {
mSelectionDegrees[MINUTES] = (minute % MINUTES_IN_CIRCLE) * DEGREES_FOR_ONE_MINUTE;
invalidate();
if (callback && mListener != null) {
mListener.onValueSelected(MINUTES, minute, false);
}
}
可以看到,这两个方法分别是设置时钟要显示的数字,位置,以及分钟要显示的数字,位置。然后是刷新。
里面的代码不打算分析了,因为这个控件定制化太严重的,几乎没有扩展性。虽然写的肯定很好,但是不适合用来继承或者做二次定制。除非有类似的需求。
另外,这个类是@hide
的,也就是不让app
开发者使用的。这么做确实是有道理的。因为很难重用。
最后,说明一下,这个类是TimePicker
的 clock
模式下会用到的核心类。TimerPicker
之前说过,是一个组合控件,本身没有什么内容。主要是封装其他的View
来实现的。在clock
模式下,封装的就是RadialTimePickerView
这个类。这里所说的封装,可以理解成“代理”。~
end
尴尬。。。。