时间选择器大多是年月日那种三级联动的,今天实现了一个小小的区别三级联动的时间选择器,先把效果图贴出来给大家展示一下下:
图片有点大,大家将就看一下,1 这个效果需要先自定义个控件如下LoopView:
public class LoopView extends View {
private static final String TAG = LoopView.class.getSimpleName();
public static final int MSG_INVALIDATE = 1000;
public static final int MSG_SCROLL_LOOP = 2000;
public static final int MSG_SELECTED_ITEM = 3000;
private ScheduledExecutorService mExecutor = Executors.newSingleThreadScheduledExecutor();
private ScheduledFuture<?> mScheduledFuture;
private int mTotalScrollY;
private LoopScrollListener mLoopListener;
private GestureDetector mGestureDetector;
private int mSelectedItem;
private GestureDetector.SimpleOnGestureListener mOnGestureListener;
private Context mContext;
private Paint mTopBottomTextPaint; //paint that draw top and bottom text
private Paint mCenterTextPaint; // paint that draw center text
private Paint mCenterLinePaint; // paint that draw line besides center text
private ArrayList mDataList;
private int mTextSize;
private int mMaxTextWidth;
private int mMaxTextHeight;
private int mTopBottomTextColor;
private int mCenterTextColor;
private int mCenterLineColor;
private float lineSpacingMultiplier;
private boolean mCanLoop;
private int mTopLineY;
private int mBottomLineY;
private int mCurrentIndex;
private int mInitPosition;
private int mPaddingLeftRight;
private int mPaddingTopBottom;
private float mItemHeight;
private int mDrawItemsCount;
private int mCircularDiameter;
private int mWidgetHeight;
private int mCircularRadius;
private int mWidgetWidth;
public Handler mHandler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
if (msg.what == MSG_INVALIDATE)
invalidate();
if (msg.what == MSG_SCROLL_LOOP)
startSmoothScrollTo();
else if (msg.what == MSG_SELECTED_ITEM)
itemSelected();
return false;
}
});
public LoopView(Context context) {
this(context, null);
}
public LoopView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public LoopView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context,attrs);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public LoopView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initView(context,attrs);
}
private void initView(Context context,AttributeSet attrs) {
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.LoopView);
if (array != null) {
mTopBottomTextColor = array.getColor(R.styleable.LoopView_topBottomTextColor, 0xffafafaf);
mCenterTextColor = array.getColor(R.styleable.LoopView_centerTextColor, 0xff313131);
mCenterLineColor = array.getColor(R.styleable.LoopView_lineColor, 0xffc5c5c5);
mCanLoop = array.getBoolean(R.styleable.LoopView_canLoop, true);
mInitPosition = array.getInt(R.styleable.LoopView_initPosition, -1);
mTextSize = array.getDimensionPixelSize(R.styleable.LoopView_textSize, sp2px(context, 16));
mDrawItemsCount = array.getInt(R.styleable.LoopView_drawItemCount, 7);
array.recycle();
}
lineSpacingMultiplier = 2.0F;
this.mContext = context;
mOnGestureListener = new LoopViewGestureListener();
mTopBottomTextPaint = new Paint();
mCenterTextPaint = new Paint();
mCenterLinePaint = new Paint();
if (android.os.Build.VERSION.SDK_INT >= 11) {
setLayerType(LAYER_TYPE_SOFTWARE, null);
}
mGestureDetector = new GestureDetector(context, mOnGestureListener);
mGestureDetector.setIsLongpressEnabled(false);
}
private void initData() {
if (mDataList == null) {
throw new IllegalArgumentException("data list must not be null!");
}
mTopBottomTextPaint.setColor(mTopBottomTextColor);
mTopBottomTextPaint.setAntiAlias(true);
mTopBottomTextPaint.setTypeface(Typeface.MONOSPACE);
mTopBottomTextPaint.setTextSize(mTextSize);
mCenterTextPaint.setColor(mCenterTextColor);
mCenterTextPaint.setAntiAlias(true);
mCenterTextPaint.setTextScaleX(1.05F);
mCenterTextPaint.setTypeface(Typeface.MONOSPACE);
mCenterTextPaint.setTextSize(mTextSize);
mCenterLinePaint.setColor(mCenterLineColor);
mCenterLinePaint.setAntiAlias(true);
mCenterLinePaint.setTypeface(Typeface.MONOSPACE);
mCenterLinePaint.setTextSize(mTextSize);
measureTextWidthHeight();
//计算半圆周 -- mMaxTextHeight * lineSpacingMultiplier 表示每个item的高度 mDrawItemsCount = 7
//实际显示5个,留两个是在圆周的上下面
//lineSpacingMultiplier是指text上下的距离的值和maxTextHeight一样的意思 所以 = 2
//mDrawItemsCount - 1 代表圆周的上下两面各被剪切了一半 相当于高度少了一个 mMaxTextHeight
int mHalfCircumference = (int) (mMaxTextHeight * lineSpacingMultiplier * (mDrawItemsCount - 1));
//the diameter of circular 2πr = cir, 2r = height
mCircularDiameter = (int) ((mHalfCircumference * 2) / Math.PI);
//the radius of circular
mCircularRadius = (int) (mHalfCircumference / Math.PI);
// FIXME: 7/8/16 通过控件的高度来计算圆弧的周长
if (mInitPosition == -1) {
if (mCanLoop) {
mInitPosition = (mDataList.size() + 1) / 2;
} else {
mInitPosition = 0;
}
}
mCurrentIndex = mInitPosition;
invalidate();
}
private void measureTextWidthHeight() {
Rect rect = new Rect();
for (int i = 0; i < mDataList.size(); i++) {
String s1 = (String) mDataList.get(i);
mCenterTextPaint.getTextBounds(s1, 0, s1.length(), rect);
int textWidth = rect.width();
if (textWidth > mMaxTextWidth) {
mMaxTextWidth = textWidth;
}
int textHeight = rect.height();
if (textHeight > mMaxTextHeight) {
mMaxTextHeight = textHeight;
}
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mWidgetWidth = getMeasuredWidth();
mWidgetHeight = MeasureSpec.getSize(heightMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
Log.i(TAG, "onMeasure -> heightMode:" + heightMode);
mItemHeight = lineSpacingMultiplier * mMaxTextHeight;
//auto calculate the text's left/right value when draw
mPaddingLeftRight = (mWidgetWidth - mMaxTextWidth) / 2;
mPaddingTopBottom = (mWidgetHeight - mCircularDiameter) / 2;
//topLineY = diameter/2 - itemHeight(mItemHeight)/2 + mPaddingTopBottom
mTopLineY = (int) ((mCircularDiameter - mItemHeight) / 2.0F) + mPaddingTopBottom;
mBottomLineY = (int) ((mCircularDiameter + mItemHeight) / 2.0F) + mPaddingTopBottom;
}
@Override
protected void onDraw(Canvas canvas) {
if (mDataList == null) {
super.onDraw(canvas);
return;
}
super.onDraw(canvas);
//the length of single item is mItemHeight
int mChangingItem = (int) (mTotalScrollY / (mItemHeight));
mCurrentIndex = mInitPosition + mChangingItem % mDataList.size();
if (!mCanLoop) { // can loop
if (mCurrentIndex < 0) {
mCurrentIndex = 0;
}
if (mCurrentIndex > mDataList.size() - 1) {
mCurrentIndex = mDataList.size() - 1;
}
} else { //can not loop
if (mCurrentIndex < 0) {
mCurrentIndex = mDataList.size() + mCurrentIndex;
}
if (mCurrentIndex > mDataList.size() - 1) {
mCurrentIndex = mCurrentIndex - mDataList.size();
}
}
int count = 0;
String itemCount[] = new String[mDrawItemsCount];
//reconfirm each item's value from dataList according to currentIndex,
while (count < mDrawItemsCount) {
int templateItem = mCurrentIndex - (mDrawItemsCount / 2 - count);
if (mCanLoop) {
if (templateItem < 0) {
templateItem = templateItem + mDataList.size();
}
if (templateItem > mDataList.size() - 1) {
templateItem = templateItem - mDataList.size();
}
itemCount[count] = (String) mDataList.get(templateItem);
} else if (templateItem < 0) {
itemCount[count] = "";
} else if (templateItem > mDataList.size() - 1) {
itemCount[count] = "";
} else {
itemCount[count] = (String) mDataList.get(templateItem);
}
count++;
}
//draw top and bottom line
canvas.drawLine(0.0F, mTopLineY, mWidgetWidth, mTopLineY, mCenterLinePaint);
canvas.drawLine(0.0F, mBottomLineY, mWidgetWidth, mBottomLineY, mCenterLinePaint);
count = 0;
int changingLeftY = (int) (mTotalScrollY % (mItemHeight));
while (count < mDrawItemsCount) {
canvas.save();
// L= å * r -> å = rad
float itemHeight = mMaxTextHeight * lineSpacingMultiplier;
//get radian L = (itemHeight * count - changingLeftY),r = mCircularRadius
double radian = (itemHeight * count - changingLeftY) / mCircularRadius;
// a = rad * 180 / π
//get angle
float angle = (float) (radian * 180 / Math.PI);
//when angle >= 180 || angle <= 0 don't draw
if (angle >= 180F || angle <= 0F) {
canvas.restore();
} else {
// translateY = r - r*cos(å) -
//(Math.sin(radian) * mMaxTextHeight) / 2 this is text offset
int translateY = (int) (mCircularRadius - Math.cos(radian) * mCircularRadius - (Math.sin(radian) * mMaxTextHeight) / 2) + mPaddingTopBottom;
canvas.translate(0.0F, translateY);
//scale offset = Math.sin(radian) -> 0 - 1
canvas.scale(1.0F, (float) Math.sin(radian));
if (translateY <= mTopLineY) {
//draw text y between 0 -> mTopLineY,include incomplete text
canvas.save();
canvas.clipRect(0, 0, mWidgetWidth, mTopLineY - translateY);