乐虎游戏|乐虎国际登录|欢迎你

Android 滚轮选择器的实现详解

日期:2020-04-15编辑作者:计算机资讯

之前项目中需要实现这样一个功能,效果如图所示:

简介

最近用一个日期选择控件,感觉官方的DatePicker操作有点复杂,而且不同的Android版本样式也都不一样。后来发现小米日历的日期选择控件蛮好看的,于是自己尝试仿写一个,感觉效果还不错。GitHub: https://github.com/ycuwq/DatePicker

效果图:

图片 1

预览1

图片 2

预览2

(1) 构造方法

图片 3FJTimeSelectorView.gif

功能分析

  • 滚轮:首先绘制一列文本,然后添加一个偏移量,在onDraw中根据手指滑动,改变偏移量并重新绘制这一列文本,这样就实现了滑动的效果。
  • Fling:这个应该很常见了,用VelocityTrackerScroller来实现。
  • 循环滚动:当滚动超过数据集的大小后,从头继续获取数据即可。
  • 幕布效果:在中心区域绘制一个矩形。
  • 字体颜色渐变:
    • 从中心到两边,逐渐将Paint的透明度变小。
    • 从中心相邻项到中心,字体颜色渐变。
    • 中心选项文字变大: 从中心相邻项到中心,字体大小渐变。
  • 指示器文字,在中间的Item后边绘制一个文字。

到这里,所有的功能点的思路大概就清晰了。

a. 要创建一个日期对象,使用new操作符和Date构造函数。
b. 在调用Date构造函数而不传递参数的情况下,新创建的对象自动获得当前日期和时间。
c. 如果想根据特定的日期和时间创建日期对象,必须传入表示该日期的毫秒数(即从UTC时间1970年1月1日午夜起至该日期止经过的毫秒数)。
d. 为了简化这一计算过程,ECMAScript提供了两个方法:Date.parse()和Date.UTC()。

这是一个出生年月的时间选择器,当时看到这个需求第一反应就是想看下有没有好用的第三方,但是没找到因此就自己写了一个,然后封装了下,希望能帮到需要的人。

实现方法

(2) Date.parse()方法
a. Date.parse()方法接收一个表示日期的字符串参数,然后尝试根据这个字符串返回相应日期的毫秒数。
b. ECMA-262没有定义Date.parse()应该支持哪种日期格式,因此这个方法的行为因实现而异,而且通常是因地区而异。
c. 如果传入Date.parse()方法的字符串不能表示日期,那么它会返回NaN。
e. 实际上,如果直接将表示日期的字符串传递给Date构造函数,也会在后台调用Date.parse()。

gitHub 链接:FJTimeSelectorView

测量控件大小

这里主要是测量wrap_content模式的大小。
首先,要确定单个item的文字的宽高。代码如下:

public void computeTextSize() {
    mTextMaxWidth = mTextMaxHeight = 0;
    if (mDataList.size() == 0) {    
        return;
    }

    //这里使用最大的,防止文字大小超过布局大小。
    mPaint.setTextSize(mSelectedItemTextSize > mTextSize ? mSelectedItemTextSize : mTextSize);

    if (!TextUtils.isEmpty(mItemMaximumWidthText)) {
        mTextMaxWidth = (int) mPaint.measureText(mItemMaximumWidthText);
    } else {
        mTextMaxWidth = (int) mPaint.measureText(mDataList.get(0).toString());
    }
    Paint.FontMetrics metrics = mPaint.getFontMetrics();
    mTextMaxHeight = (int) (metrics.bottom - metrics.top);
}

然后确定布局的大小,布局的宽度就等于测量的mTextMaxWidth,高度为测量的mTextMaxHeight * itemCount。这里宽高中可以加入一个额外的Space,要不然文字就会挤到一起,比较难看。

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int specWidthSize = MeasureSpec.getSize(widthMeasureSpec);
        int specWidthMode = MeasureSpec.getMode(widthMeasureSpec);
        int specHeightSize = MeasureSpec.getSize(heightMeasureSpec);
        int specHeightMode = MeasureSpec.getMode(heightMeasureSpec);

        int width = mTextMaxWidth + mItemWidthSpace;
        int height = (mTextMaxHeight + mItemHeightSpace) * getVisibleItemCount();

        width += getPaddingLeft() + getPaddingRight();
        height += getPaddingTop() + getPaddingBottom();
        setMeasuredDimension(measureSize(specWidthMode, specWidthSize, width),
                measureSize(specHeightMode, specHeightSize, height));
    }

    private int measureSize(int specMode, int specSize, int size) {
        if (specMode == MeasureSpec.EXACTLY) {
            return specSize;
        } else {
            return Math.min(specSize, size);
        }
    }

(3) Date.UTC()方法
a. Date.UTC()方法同样也返回表示日期的毫秒数,但它与Date.parse()在构建值时使用不同的信息。
b. Date.UTC()的参数分别是年份、基于0的月份(一月是0,二月是1,以此类推)、月中的哪一天(1到31)、小时数(0到23)、分钟、秒以及毫秒数。
c. 在这些参数中,只有前两个参数(年和月)是必需的。如果没有提供月中的天数,则假设天数为1;如果省略其他参数,则统统假设为0。
d. Date构造函数也会模仿Date.UTC(),但有一点明显不同:日期和时间都基于本地时区而非GMT来创建。不过,Date构造函数接收的参数仍然与Date.UTC()相同。

集成方法:

滚轮的绘制

一般滚轮被选中的位置都是在中间,所以显示的设置为奇数比较合适。为了方便,定义mHalfVisibleItemCount作为显示的个数的一半,总显示个数为mHalfVisibleItemCount * 2 + 1,这样就可以保证选中的item在正中间

首先最简单的,不考虑滚动的情况,直接绘制一列文字,这里为了方便计算,设置中间的为数据集的第0个,第一个item就为0-mHalfVisibleItemCount,最后一个则为mHalfVisibleItemCount,代码如下:

@Override
protected void onDraw(Canvas canvas) {
    for (int drawDataPos = -mHalfVisibleItemCount; drawDataPos <= mHalfVisibleItemCount; drawDataPos++) {
        if (pos < 0 || pos > mDataList.size() - 1) {
            continue;
        }
        int itemDrawY = mFirstItemDrawY + (drawDataPos + mHalfVisibleItemCount) * mItemHeight;
        T data = mDataList.get(pos);
        canvas.drawText(data.toString(), mFirstItemDrawX, itemDrawY, mPaint);
    }
}

接下来加入滚动,获取手指滑动的值, 然后绘制的时候添加偏移量就好了。

手指滑动,这个应该都懂,这里就不废话了,直接上代码:

@Override
public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            mLastDownY = (int) event.getY();
            break;
        case MotionEvent.ACTION_MOVE:
            float move = event.getY() - mLastDownY;
            mScrollOffsetY += move;     //滑动的偏移量
            mLastDownY = (int) event.getY();
            invalidate();
            break;
        case MotionEvent.ACTION_UP:
            break;
    }

更改onDraw()的代码。这边有两个问题:

  1. 上下两边要多绘制一个出来,因为在滚动的时候,实际在容器内的item要比原定的item要多一个。
  2. 定位中间向位于数据集的位置,用偏移量 / item的高度即可。由于手指的坐标是以左上角为原点的,这里要注意坐标正负问题。
    代码如下:
@Override
protected void onDraw(Canvas canvas) {
    int drawnSelectedPos = - mScrollOffsetY / mItemHeight;
    for (int drawDataPos = drawnSelectedPos - mHalfVisibleItemCount - 1;drawDataPos <= drawnSelectedPos + mHalfVisibleItemCount + 1; drawDataPos++) {
        if (drawDataPos < 0 || drawDataPos > mDataList.size() - 1) {
            continue;
        }
        int itemDrawY = mFirstItemDrawY + (drawDataPos + mHalfVisibleItemCount) * mItemHeight + mScrollOffsetY;
        T data = mDataList.get(drawDataPos);
        canvas.drawText(data.toString(), mFirstItemDrawX, itemDrawY, mPaint);
    }
}

(4) ECMAScript 5添加了Data.now()方法
a. 返回表示调用这个方法时的日期和时间的毫秒数。
b. 支持Data.now()方法的浏览器包括IE9+、Firefox 3+、Safari 3+、Opera 10.5和Chrome。在不支持它的浏览器中,使用+操作符把Data对象转换成字符串,也可以达到同样的目的。

静态:手动将FJTimeSelectorView文件夹拖入到工程中。动态:CocoaPods:pod 'FJTimeSelectorView'

Fling的效果

先上主要代码:

@Override
public boolean onTouchEvent(MotionEvent event) {
    if (mTracker == null) {
        mTracker = VelocityTracker.obtain();
    }
    mTracker.addMovement(event);
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            mTracker.clear();
            mTouchDownY = mLastDownY = (int) event.getY();
            break;
        case MotionEvent.ACTION_MOVE:
            mTouchSlopFlag = false;
            float move = event.getY() - mLastDownY;
            mScrollOffsetY += move;
            mLastDownY = (int) event.getY();
            invalidate();
            break;
        case MotionEvent.ACTION_UP:
            mTracker.computeCurrentVelocity(1000, mMaximumVelocity);
            int velocity = (int) mTracker.getYVelocity();
            mScroller.fling(0, mScrollOffsetY, 0, velocity,
                    0, 0, mMinFlingY, mMaxFlingY);
            mScroller.setFinalY(mScroller.getFinalY() +
                    computeDistanceToEndPoint(mScroller.getFinalY() % mItemHeight));
            mHandler.post(mScrollerRunnable);
            mTracker.recycle();
            mTracker = null;
            break;
    }
    return true;
}

private int computeDistanceToEndPoint(int remainder) {
    if (Math.abs(remainder) > mItemHeight / 2) {
        if (mScrollOffsetY < 0) {
            return -mItemHeight - remainder;
        } else {
            return mItemHeight - remainder;
        }
    } else {
        return -remainder;
    }
}

private Runnable mScrollerRunnable = new Runnable() {
    @Override
    public void run() {
        if (mScroller.computeScrollOffset()) {
            int scrollerCurrY = mScroller.getCurrY();
            mScrollOffsetY = scrollerCurrY;
            postInvalidate();
            mHandler.postDelayed(this, 16);
        }
    }
}

Fling效果的实现主要是用的VelocityTrackerScroller,网上已经有很多资料了,这里就不再说明了。这里主要就是获取Scroller当前滚动的值,然后加入到偏移量mScrollOffsetY后请求重新绘制。

这里有一个finalY的修正计算.当动画停止的时候,停止的位置如果随缘的话,就会经常在停在这种位置:

图片 4

因为要保证当滑动停止的时候,要保证item正好在中间。也就是这样:

图片 5

修正的方法就是:滚动的值只能为item高度的整数。这样就能保证,滚动结束中间的item只能在正中间。利用这个原理,上边的手指滑动,也能在手指离开屏幕后来修正位置。

这里还要考虑一个问题,滚动的时候可能会超过给定的数据集的大小,就是当滚动的值超过最后一个数据后,当手指松开后返回到最后一个数据的位置,即下图的效果:

图片 6

161098a4ab2fdbe1.gif

方法还是在ACTION_UP的时候判断是否超过数据集的大小即可。代码如下:

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    ...
    mMinFlingY = - mItemHeight * (mDataList.size() - 1);
    mMaxFlingY = 0;
}


@Override
public boolean onTouchEvent(MotionEvent event) {
    ...

    case MotionEvent.ACTION_UP:
        mTracker.computeCurrentVelocity(1000, mMaximumVelocity);
        int velocity = (int) mTracker.getYVelocity();
        mScroller.fling(0, mScrollOffsetY, 0, velocity,
                0, 0, mMinFlingY, mMaxFlingY);
        mScroller.setFinalY(mScroller.getFinalY() +
                computeDistanceToEndPoint(mScroller.getFinalY() % mItemHeight));

        if (mScroller.getFinalY() > mMaxFlingY) {
            mScroller.setFinalY(mMaxFlingY);
        } else if (mScroller.getFinalY() < mMinFlingY) {
            mScroller.setFinalY(mMinFlingY);
        }

    ...
}

(5) 继承的方法
a. Date类型也重写了toLocaleString()、toString()和valueOf()方法。
b. Date类型的toLocaleString()方法会按照与浏览器设置的地区相适应的格式返回日期和时间。
c. Date类型的toString()方法则通常返回带有时区信息的日期和时间,其中时间一般以军用时间(即小时的范围是0到23)表示。
d. 事实上,toLocaleString()和toString()的这一差别仅在调试代码时比较有用,而在显示日期和时间时没有什么价值。
e. Date类型的valueOf()方法,则根本不返回字符串,而是返回日期的毫秒表示。因此,可以方便使用比较操作符(小于或大于)来比较日期值。

一.使用方法:

循环滚动

接下来要考虑循环滚动的问题。

首先 上边的mMinFlingYmMaxFlingY在循环滚动的时候就要设置成Integer的极限值,如下

mMinFlingY = mIsCyclic ? Integer.MIN_VALUE : - mItemHeight * (mDataList.size() - 1);
mMaxFlingY = mIsCyclic ? Integer.MAX_VALUE : 0;

然后考虑绘制的时候取值问题,在上边onDraw()方法的里面有一个安全值判断,如果超过数据集的大小就跳过此条item绘制,代码如下:

@Override
protected void onDraw(Canvas canvas) {
    int drawnSelectedPos = - mScrollOffsetY / mItemHeight;
    for (int drawDataPos = drawnSelectedPos - mHalfVisibleItemCount - 1;drawDataPos <= drawnSelectedPos + mHalfVisibleItemCount + 1; drawDataPos++) {
        if (drawDataPos < 0 || drawDataPos > mDataList.size() - 1) {
            continue;
        }
        ...
    }
}

我们要改动的就是这里。当循环滚动的时候上下滚动的极限都为无穷,所以- mScrollOffsetY / mItemHeight;得到的值应该也在正负无穷之间,我们要把值都映射到数据集中。
假设数据集有10条数据,当前滚动的位置为pos:

  • 当pos>10时,假设当pos = 11时,我们想让其回到第一个数据,最后化简的公示为:pos % 10
  • 当pos<0时,假设当pos = -1时,应该要展示最后一个数据,最后化简的公式为:mDataList.size() + (pos % 10)
    所以上边的代码就可以改变成:
@Override
protected void onDraw(Canvas canvas) {
    int drawnSelectedPos = - mScrollOffsetY / mItemHeight;
    for (int drawDataPos = drawnSelectedPos - mHalfVisibleItemCount - 1;drawDataPos <= drawnSelectedPos + mHalfVisibleItemCount + 1; drawDataPos++) {
        int pos = drawDataPos;
        if  (mIsCyclic) {
            if (pos < 0) {
                pos = mDataList.size() + (pos % 10);
            } else {
                pos = pos % mDataList.size();
            }
        } else {
            if (drawDataPos < 0 || drawDataPos > mDataList.size() - 1) {
                continue;
            }
        }
        ...
    }
}

(6) 日期格式化方法
 toDateString()——以特定于实现的格式显示星期几、月、日和年;
 toTimeString()——以特定于实现的格式显示时、分、秒和时区;
 toLocaleDateString()——以特定于地区的格式显示星期几、月、日和年;
 toLocaleTimeString()——以特定于实现的格式显示时、分、秒;
 toUTCString()——以特定于实现的格式完整的UTC日期。

先生成FJTimeSelectorView,然后设置timeScaleStyle属性。

幕布

这个就比较简单了,在中间画一个矩形就好了,不过要在绘制滚轮之前绘制,否则会遮盖住文字,代码如下:

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    ...
    mDrawnRect.set(getPaddingLeft(), getPaddingTop(),
        getWidth() - getPaddingRight(), getHeight() - getPaddingBottom());
    mSelectedItemRect.set(getPaddingLeft(), mItemHeight * mHalfVisibleItemCount,getWidth() - getPaddingRight(), mItemHeight + mItemHeight * mHalfVisibleItemCount);
}

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    mPaint.setTextAlign(Paint.Align.CENTER);
    //是否绘制幕布
    if (mIsShowCurtain) {
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setColor(mCurtainColor);
        canvas.drawRect(mSelectedItemRect, mPaint);
    }
    //是否绘制幕布边框
    if (mIsShowCurtainBorder) {
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setColor(mCurtainBorderColor);
        canvas.drawRect(mSelectedItemRect, mPaint);
        canvas.drawRect(mDrawnRect, mPaint);
    }
    ...
}

a. 与toLocaleString()和toString()方法一样,以上这些字符串格式方法的输出也是因浏览器而异的,因此没有哪一个方法能够用来在用户界面中显示一致的日期信息。
b. 除了前面介绍的方法之外,还有一个名叫toGMTString()的方法,这是一个与toUTCString()等价的方法,其存在目的在于确保向后兼容。不过,ECMAScript推荐现在编写的代码一律使用toUTCString()方法。

// timeSelectorView- (FJTimeSelectorView *)timeSelectorView { if(!_timeSelectorView){ CGFloat timeSelectorViewX = 12.0f; CGFloat timeSelectorWidth = self.view.frame.size.width - 2 * timeSelectorViewX; _timeSelectorView = [[FJTimeSelectorView alloc] initWithFrame:CGRectMake(timeSelectorViewX, CGRectGetMaxY(self.setChildrenAgeView.frame) + 20, timeSelectorWidth, 60)]; _timeSelectorView.timeScaleStyle = [[FJTimeScaleStyle alloc] init]; } return _timeSelectorView;}

字体颜色渐变

这个分三个部分:

(7) 日期/时间组件方法
UTC日期指的是在没有时区偏差的情况下(将日期转换为GMT时间)的日期值。

具体参数可以通过FJTimeScaleStyle类配置:

1. 透明度渐变:

Paint在绘制文字的时候可以设置Alpha来设置透明度,Alpha的比例计算方法:

$$ frac{绘制点到端点距离}{中心绘制点到端点距离} $$

如下图所示,计算“03”的比例:

图片 7

主要代码如下:

@Override
protected void onDraw(Canvas canvas) {
    ...
    int drawnSelectedPos = - mScrollOffsetY / mItemHeight;
    for (int drawDataPos = drawnSelectedPos - mHalfVisibleItemCount - 1;drawDataPos <= drawnSelectedPos + mHalfVisibleItemCount + 1; drawDataPos++) {
        int pos = drawDataPos;
        if  (mIsCyclic) {
            if (pos < 0) {
                pos = mDataList.size() + (pos % 10);
            } else {
                pos = pos % mDataList.size();
            }
        } else {
            if (drawDataPos < 0 || drawDataPos > mDataList.size() - 1) {
                continue;
            }
        }

        T data = mDataList.get(pos);
        int itemDrawY = mFirstItemDrawY + (drawDataPos + mHalfVisibleItemCount) * mItemHeight + mScrollOffsetY;
        //距离中心的Y轴距离
        int distanceY = Math.abs(mCenterItemDrawnY - itemDrawY);

        float alphaRatio;
        if (itemDrawY > mCenterItemDrawnY) {
            alphaRatio = (mDrawnRect.height() - itemDrawY) /
                    (float) (mDrawnRect.height() - (mCenterItemDrawnY));
        } else {
            alphaRatio = itemDrawY / (float) mCenterItemDrawnY;
        }
        mPaint.setAlpha((int) (alphaRatio * 255));
        canvas.drawText(data.toString(), mFirstItemDrawX, itemDrawY, mPaint);
        ...
    }
}

getTime() 返回表示日期的毫秒数;与valueOf()方法返回的值相同
setTime(毫秒) 以毫秒数设置日期,会改变整个日期
getFullYear() 取得4位数的年份(如2007而非仅07)
getUTCFullYear() 返回UTC日期的4位数年份
setFullYear(年) 设置日期的年份。传入的年份值必须是4位数字(如2007而非仅07)
setUTCFullYear(年) 设置UTC日期的年份。传入的年份值必须是4位数字(如2007而非仅07)
getMonth() 返回日期中的月份,其中0表示一月,11表示十二月
getUTCMonth() 返回UTC日期中的月份,其中0表示一月,11表示十二月
setMonth(月) 设置日期的月份。传入的月份值必须大于0,超过11则增加年份
setUTCMonth(月) 设置UTC日期的月份。传入的月份值必须大于0,超过11则增加年份
getDate() 返回日期月份中的天数(1到31)
getUTCDate() 返回UTC日期月份中的天数(1到31)
setDate(日) 设置日期月份中的天数。如果传入的值超过了该月中应有的天数,则增加月份
setUTCDate(日) 设置UTC日期月份中的天数。如果传入的值超过了该月中应有的天数,则增加月份
getDay() 返回日期中星期的星期几(其中0表示星期日,6表示星期六)
getUTCDay() 返回UTC日期中星期的星期几(其中0表示星期日,6表示星期六)
getHours() 返回日期中的小时数(0到23)
getUTCHours() 返回UTC日期中的小时数(0到23)
setHours(时) 设置日期中的小时数。传入的值超过了23则增加月份中的天数
setUTCHours(时) 设置UTC日期中的小时数。传入的值超过了23则增加月份中的天数
getMinutes() 返回日期中的分钟数(0到59)
getUTCMinutes() 返回UTC日期中的分钟数(0到59)
setMinutes(分) 设置日期中的分钟数。传入的值超过59则增加小时数
setUTCMinutes(分) 设置UTC日期中的分钟数。传入的值超过59则增加小时数
getSeconds() 返回日期中的秒数(0到59)
getUTCSeconds() 返回UTC日期中的秒数(0到59)
setSeconds(秒) 设置日期中的秒数。传入的值超过了59会增加分钟数
setUTCSeconds(秒) 设置UTC日期中的秒数。传入的值超过了59会增加分钟数
getMilliseconds() 返回日期中的毫秒数
getUTCMilliseconds() 返回UTC日期中的毫秒数
setMilliseconds(毫秒) 设置日期中的毫秒数
setUTCMilliseconds(毫秒) 设置UTC日期中的毫秒数
getTimezoneOffset() 返回本地时间与UTC时间相差的分钟数。例如,美国东部标准时间返回300。在某地进入夏令时的情况下,这个值会有所变化

// 刻度线 正常 高度@property (nonatomic, assign) CGFloat timeScaleLineNormalHeight;// 刻度线 最大 高度@property (nonatomic, assign) CGFloat timeScaleLineMaxHeight;// 刻度cell size@property (nonatomic, assign) CGSize timeScaleItemSize;// 刻度线 数量@property (nonatomic, assign) NSInteger timeScaleCount;// 刻度线 宽度@property (nonatomic, assign) CGFloat timeScaleLineWidth;// 刻度单位 @property (nonatomic, assign) NSInteger timeScaleUnit;// 刻度线 开始 值@property (nonatomic, assign) NSInteger timeScaleStartValue;// 刻度线 结束 值@property (nonatomic, assign) NSInteger timeScaleEndValue;// 刻度线 默认值@property (nonatomic, assign) NSInteger timeScaleDefaultValue;// 刻度线 颜色@property (nonatomic, strong) UIColor *timeScaleLineColor;// 刻度 label 字体 颜色@property (nonatomic, strong) UIColor *timeScaleLabelTextColor;// 刻度 label 字体 大小@property (nonatomic, strong) UIFont *timeScaleLabelFont;// collectionView 边框 宽度@property (nonatomic, assign) CGFloat collectionViewBorderWidth;// collectionView 边框 颜色@property (nonatomic, strong) UIColor *collectionViewBorderColor;// indicatorLineWidth@property (nonatomic, assign) CGFloat indicatorViewLineWidth;// indicatorLayerWidth@property (nonatomic, assign) CGFloat indicatorViewLayerWidth;// indicatorLayerHeight@property (nonatomic, assign) CGFloat indicatorViewLayerHeight;// indicatorHeaderHeight@property (nonatomic, assign) CGFloat indicatorViewHeaderHeight;// 指示器 颜色@property (nonatomic, strong) UIColor *indicatorViewBackgroundColor;

2. 文字颜色渐变

当距离中心绘制点的距离小于一个ItemHeight时,进行文字颜色渐变。
首先需要一个线性颜色渐变的工具,思路就是指定一个开始颜色和结束颜色,传入比例获取颜色。比较简单,直接看代码:

public class LinearGradient {

    private int mStartColor;
    private int mEndColor;
    private int mRedStart;
    private int mBlueStart;
    private int mGreenStart;
    private int mRedEnd;
    private int mBlueEnd;
    private int mGreenEnd;

    public LinearGradient(@ColorInt int startColor, @ColorInt int endColor) {
        mStartColor = startColor;
        mEndColor = endColor;
        updateColor();
    }


    public void setStartColor(@ColorInt int startColor) {
        mStartColor = startColor;
        updateColor();
    }

    public void setEndColor(@ColorInt int endColor) {
        mEndColor = endColor;
        updateColor();
    }

    private void updateColor() {
        mRedStart = Color.red(mStartColor);
        mBlueStart = Color.blue(mStartColor);
        mGreenStart = Color.green(mStartColor);
        mRedEnd = Color.red(mEndColor);
        mBlueEnd = Color.blue(mEndColor);
        mGreenEnd = Color.green(mEndColor);
    }

    public int getColor(float ratio) {
        int red = (int) (mRedStart + ((mRedEnd - mRedStart) * ratio + 0.5));
        int greed = (int) (mGreenStart + ((mGreenEnd - mGreenStart) * ratio + 0.5));
        int blue = (int) (mBlueStart + ((mBlueEnd - mBlueStart) * ratio + 0.5));
        return Color.rgb(red, greed, blue);
    }
}

接下来就是计算文字颜色渐变了,和上边的计算透明度的类似,代码如下:

@Override
protected void onDraw(Canvas canvas) {
    ...
    int drawnSelectedPos = - mScrollOffsetY / mItemHeight;
    for (int drawDataPos = drawnSelectedPos - mHalfVisibleItemCount - 1;drawDataPos <= drawnSelectedPos + mHalfVisibleItemCount + 1; drawDataPos++) {
        ...

        T data = mDataList.get(pos);
        int itemDrawY = mFirstItemDrawY + (drawDataPos + mHalfVisibleItemCount) * mItemHeight + mScrollOffsetY;
        //距离中心的Y轴距离
        int distanceY = Math.abs(mCenterItemDrawnY - itemDrawY);

        //计算文字颜色渐变
        //文字颜色渐变要在设置透明度上边,否则透明度会被覆盖
        if (distanceY < mItemHeight) {
            float colorRatio = 1 - (distanceY / (float) mItemHeight);
            mPaint.setColor(mLinearGradient.getColor(colorRatio));
        } else {
            mPaint.setColor(mTextColor);
        }

        //计算透明度渐变
        float alphaRatio;
        if (itemDrawY > mCenterItemDrawnY) {
            alphaRatio = (mDrawnRect.height() - itemDrawY) /
                    (float) (mDrawnRect.height() - (mCenterItemDrawnY));
        } else {
            alphaRatio = itemDrawY / (float) mCenterItemDrawnY;
        }
        mPaint.setAlpha((int) (alphaRatio * 255));
        canvas.drawText(data.toString(), mFirstItemDrawX, itemDrawY, mPaint);
        ...
    }
}

原:javascript高级程序设计(第三版)

本文由乐虎游戏发布于计算机资讯,转载请注明出处:Android 滚轮选择器的实现详解

关键词:

计算机在iOS开垦中应用Protobuf

Protobuf简介 protocolbuffer 是google的一种数据交换的格式,它独立于语言,独立于平台。google提供了多种语言的实现:j...

详细>>

深入剖析Objective-C中的Swizzle

method_name是函数的选择器,method_type是参数和返回值类型编码的c字符串,method_imp是指向实际函数的函数指针。可以通过下...

详细>>

Atitit.收银系统pos 以及打印功能的行业标准

OG视讯直播,Atitit.收银系统pos以及打印功能的行业标准 之前公司有个面向商户的项目,需要连接商户打印机打印小票...

详细>>

计算机iOS-ReactiveCocoa学习笔记1

①. 简化响应式函数的模式 在Swift中,我们有几种响应式的开发模式:target-action、代理、通知中心、KVO等。以上每个...

详细>>