android RadialTimePickerView 浅析

本文深入剖析了RadialTimePickerView的工作原理,包括角度与弧度的转换、静态代码块的功能、数组SNAP_PREFER_30S_MAP的含义及其在时钟选择中的作用,同时介绍了setCurrentHourInternal和setCurrentMinuteInternal方法的具体实现。

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对应的是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

尴尬。。。。

PickerView (2.x系列) 精仿iOS的PickerView控件,有时间选择和选项选择并支持一二三级联动效果 ——TimePickerView 时间选择器,支持年月日时分,年月日,年月,时分等格式 ——OptionsPickerView 选项选择器,支持一,二,三级选项选择,并且可以设置是否联动 2.x是全新的3D效果,比1.x版本更加贴近iOS的效果,从外观细节上也得到了改善。api兼容1.x版本,只需要把依赖的版本号升级即可,几乎不用修改代码即可完成升级。 使用gradle 依赖: compile 'com.bigkoo:pickerview:2.0.8' Demo 图片 demo代码请看戳这里 更新说明 v2.0.0 不需修改任何代码就可以兼容1.x 外观大整改 支持反射获取getPickerViewText()来获取要展示数据,以前只能传String的对象,现在可以传任意对象只要有getPickerViewText()函数即可显示对应的字符串,如果没有getPickerViewText()函数则使用对象toString作为显示 加入setTitle v2.0.1 去掉popupWindow,改用View,类名也对应修改为TimePickerView和 OptionsPickerView 加入遮罩效果 v2.0.2 修复不循环模式下点击空白item处出现数组越界问题 修复循环模式下只有一条数据的时候只显示三条而不是填充满高度问题 v2.0.3 修复时间选择的时候部分数字选不到直接跳到下一个数字的问题 v2.0.4 修复不循环模式下顶部超出范围问题 wheel view文字颜色通过xml配置 v2.0.5 修复不循环模式下底部超出范围问题 v2.0.6 修复不循环模式下点击超出范围问题,修复后点击空白的地方,只能滚到最顶或最底,不会滚出数据范围。 v2.0.7 修复设置初始化position ,第三级数据不对的BUG。 v2.0.8 修复#41 未选中项有错乱数据问题。 加入pickerview_customTextSize 和 pickerview_textsize 到 xml 中 来控制自定义文字大小 ---------------------华丽丽的分割线-------------------------- PickerView1.x (我已经把1.0.3版本分到v1.x的分支去了,停止维护1.x的分支) 使用gradle 依赖: compile 'com.bigkoo:pickerview:1.0.3' Demo 图片(招行信用卡的“掌上生活”里面条件选择器他们用的就是我这个库,大家可以当实际项目参考) Thanks WheelView androidWheelView
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值