RadialTimePickerView 浅析


  • RadialTimePickerView 浅析
  • 上来就一个静态代码块
  • preparePrefer30sMap();
  • snapOnly30s();
  • setCurrentHourInternal()
  • setCurrentMinuteInternal()


上来就一个静态代码块

关于角度与弧度:看这个链接
.
从这里我们至少知道了一点,就是 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对应的是08-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开发者使用的。这么做确实是有道理的。因为很难重用。

最后,说明一下,这个类是TimePickerclock模式下会用到的核心类。TimerPicker之前说过,是一个组合控件,本身没有什么内容。主要是封装其他的View来实现的。在clock模式下,封装的就是RadialTimePickerView这个类。这里所说的封装,可以理解成“代理”。~


end

尴尬。。。。