登录 立即注册
安币:

安卓巴士 - 安卓开发 - Android开发 - 安卓 - 移动互联网门户

Android-PickerView系列之源码解析篇(二) [复制链接]

2018-6-13 10:23
liujh 阅读:761 评论:0 赞:0
Tag:  

前言

  WheelView想必大家或多或少都有一定了解, 它是一款3D滚轮控件,效果类似IOS 上面的UIpickerview 。按照国际惯例,先放一张效果图:

GIF效果图

  以上是Android-PickerView 的demo演示图,它有时间选择和选项选择,并支持一二三级联动,支持自定义样式。 
  由于saiwu-bigkoo(吴哥)已经转行不干编程了,项目现已转交由我更新维护。目前我更新了3.x的版本,修复了若干问题,并重构了项目,新增了许多可选参数,让开发者使用起来更加灵活方便,定制化更强。另外我还创建了一个组织,希望有兴趣的小伙伴也能加入进来为这个项目添加一份力量。
  关于它的介绍和使用详情,这里就不过多阐述,有兴趣请参考我的另外一篇文章:Android-PickerView系列之介绍与使用篇(一) 
  好了,闲话就说到这,开始进入正文,本篇文章的主要内容是讲解WheelView的实现原理以及源代码,大致分以下几个步骤:

一、实现原理 
二、自定义控件 
三、onMeasure 测量 
四、onDraw 绘制 
五、onTouchEvent监听

一、实现原理

  上面我们看到的GIF图中,控件中间滚轮部分的布局,有多个WheelView, 一个WheelView 就是一个3D滚轮,我画了一张图方便大家更为直观地理解:

WhelView绘制原理图

  从上图中我们可以看到,每一项Item都是在圆弧上面, 假设我们设置的WheelView它的可见Item数目为11,那么圆的半个周长就等于 10项Item的高度。我们看到的第一象限和第四象限,它是可见区域,即Item所显示的位置。其中,每项Item的高度 ItemHeight 等于两条分隔线的高度,具体如下图所示:

这里写图片描述
(为什么要画得那么详细,因为这些参数在绘制过程中需要用到)

因此,我们可得以下结论:

1.每项Item 的高度是由文字大小以及间距倍数控制的, itemHeight = lineSpacingMultiplier * maxTextHeight; 
2.圆周长 C = 2 (itemHeight *(itemsVisible - 1)) 
2.根据圆周长公式 C= 2πR, 可推导出圆半径R = C/2π ,圆直径 L = C/π;

二、自定义控件

  1. 创建一个WheelView 类继承自 View,覆盖onDraw、onMeasure、onTouchEvent方法.
  2. 在构造方法中初始化数据;
  3. 在构造方法中初始化三个画笔Paint,分别用于绘制选中项、未选中项、分隔线。
 private void initPaints() {
        paintOuterText = new Paint();
        paintOuterText.setColor(textColorOut);
        paintOuterText.setAntiAlias(true);
        paintOuterText.setTypeface(Typeface.MONOSPACE);
        paintOuterText.setTextSize(textSize);

        paintCenterText = new Paint();
        paintCenterText.setColor(textColorCenter);
        paintCenterText.setAntiAlias(true);
        paintCenterText.setTextScaleX(1.1F);
        paintCenterText.setTypeface(Typeface.MONOSPACE);
        paintCenterText.setTextSize(textSize);


        paintIndicator = new Paint();
        paintIndicator.setColor(dividerColor);
        paintIndicator.setAntiAlias(true);

        if (android.os.Build.VERSION.SDK_INT >= 11) {
            setLayerType(LAYER_TYPE_SOFTWARE, null);
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

三、onMeasure 测量

1.计算最大length的Text的宽高度

 private void measureTextWidthHeight() {
        Rect rect = new Rect();
        for (int i = 0; i < adapter.getItemsCount(); i++) {
            String s1 = getContentText(adapter.getItem(i));
            paintCenterText.getTextBounds(s1, 0, s1.length(), rect);
            int textWidth = rect.width();
            if (textWidth > maxTextWidth) {
                maxTextWidth = textWidth;
            }
            paintCenterText.getTextBounds("\u661F\u671F", 0, 2, rect); // "星期"的字符编码,用它作为标准高度
            int textHeight = rect.height();
            if (textHeight > maxTextHeight) {
                maxTextHeight = textHeight;
            }
        }
        itemHeight = lineSpacingMultiplier * maxTextHeight;//item的高度
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

2.计算圆的半径和直径,求出WheelView控件的宽高度


        //周长公式 C= 2πR
        //半圆的周长 = item高度乘以item数目-1
        halfCircumference = (int) (itemHeight * (itemsVisible - 1));
        //整个圆的周长除以PI得到直径,这个直径用作控件的总高度
        measuredHeight = (int) ((halfCircumference * 2) / Math.PI);
        //求出半径
        radius = (int) (halfCircumference / Math.PI);
        //计算控件宽度,这里支持weight
        measuredWidth = MeasureSpec.getSize(widthMeasureSpec);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

3.计算两条分隔线和Label文字的基线位置

        //计算两条横线 和 选中项Label的基线centerY 位置
        firstLineY = (measuredHeight - itemHeight) / 2.0F;
        secondLineY = (measuredHeight + itemHeight) / 2.0F;
        centerY = secondLineY - (itemHeight-maxTextHeight)/2.0f - CENTERCONTENTOFFSET;
  • 1
  • 2
  • 3
  • 4

对于centerY 为什么要减去CENTERCONTENTOFFSET(偏移量),因为Canvas.drawText方法中的坐标参数Y,并不是文字的底部位置,而是基线位置,所以我们要微调一下位置,让显示居中:

这里写图片描述