时间选择器大多是年月日那种三级联动的,今天实现了一个小小的区别三级联动的时间选择器,先把效果图贴出来给大家展示一下下:
图片有点大,大家将就看一下,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);
canvas.drawText(itemCount[count], mPaddingLeftRight, mMaxTextHeight, mTopBottomTextPaint);
canvas.restore();
canvas.save();
canvas.clipRect(0, mTopLineY - translateY, mWidgetWidth, (int) (itemHeight));
canvas.drawText(itemCount[count], mPaddingLeftRight, mMaxTextHeight, mCenterTextPaint);
canvas.restore();
} else if (mMaxTextHeight + translateY >= mBottomLineY) {
//draw text y between mTopLineY -> mBottomLineY ,include incomplete text
canvas.save();
canvas.clipRect(0, 0, mWidgetWidth, mBottomLineY - translateY);
canvas.drawText(itemCount[count], mPaddingLeftRight, mMaxTextHeight, mCenterTextPaint);
canvas.restore();
canvas.save();
canvas.clipRect(0, mBottomLineY - translateY, mWidgetWidth, (int) (itemHeight));
canvas.drawText(itemCount[count], mPaddingLeftRight, mMaxTextHeight, mTopBottomTextPaint);
canvas.restore();
} else if (translateY >= mTopLineY && mMaxTextHeight + translateY <= mBottomLineY) {
//draw center complete text
canvas.clipRect(0, 0, mWidgetWidth, (int) (itemHeight));
canvas.drawText(itemCount[count], mPaddingLeftRight, mMaxTextHeight, mCenterTextPaint);
//center one indicate selected item
mSelectedItem = mDataList.indexOf(itemCount[count]);
}
canvas.restore();
}
count++;
}
}
@Override
public boolean onTouchEvent(MotionEvent motionevent) {
switch (motionevent.getAction()) {
case MotionEvent.ACTION_UP:
default:
if (!mGestureDetector.onTouchEvent(motionevent)) {
startSmoothScrollTo();
}
}
return true;
}
public final void setCanLoop(boolean canLoop) {
mCanLoop = canLoop;
invalidate();
}
/**
* set text size
*
* @param size size indicate sp,not px
*/
public final void setTextSize(float size) {
if (size > 0) {
mTextSize = sp2px(mContext, size);
}
}
public void setInitPosition(int initPosition) {
this.mInitPosition = initPosition;
invalidate();
}
public void setLoopListener(LoopScrollListener LoopListener) {
mLoopListener = LoopListener;
}
/**
* All public method must be called before this method
* @param list data list
*/
public final void setDataList(List<String> list) {
this.mDataList = (ArrayList) list;
initData();
}
public int getSelectedItem() {
return mSelectedItem;
}
private void itemSelected() {
if (mLoopListener != null) {
postDelayed(new SelectedRunnable(), 200L);
}
}
private void cancelSchedule() {
if (mScheduledFuture != null && !mScheduledFuture.isCancelled()) {
mScheduledFuture.cancel(true);
mScheduledFuture = null;
}
}
private void startSmoothScrollTo() {
int offset = (int) (mTotalScrollY % (mItemHeight));
cancelSchedule();
mScheduledFuture = mExecutor.scheduleWithFixedDelay(new HalfHeightRunnable(offset), 0, 10, TimeUnit.MILLISECONDS);
}
private void startSmoothScrollTo(float velocityY) {
cancelSchedule();
int velocityFling = 20;
mScheduledFuture = mExecutor.scheduleWithFixedDelay(new FlingRunnable(velocityY), 0, velocityFling, TimeUnit.MILLISECONDS);
}
class LoopViewGestureListener extends android.view.GestureDetector.SimpleOnGestureListener {
@Override
public final boolean onDown(MotionEvent motionevent) {
cancelSchedule();
Log.i(TAG, "LoopViewGestureListener->onDown");
return true;
}
@Override
public final boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
startSmoothScrollTo(velocityY);
Log.i(TAG, "LoopViewGestureListener->onFling");
return true;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
Log.i(TAG, "LoopViewGestureListener->onScroll");
mTotalScrollY = (int) ((float) mTotalScrollY + distanceY);
if (!mCanLoop) {
int initPositionCircleLength = (int) (mInitPosition * (mItemHeight));
int initPositionStartY = -1 * initPositionCircleLength;
if (mTotalScrollY < initPositionStartY) {
mTotalScrollY = initPositionStartY;
}
int circleLength = (int) ((float) (mDataList.size() - 1 - mInitPosition) * (mItemHeight));
if (mTotalScrollY >= circleLength) {
mTotalScrollY = circleLength;
}
}
invalidate();
return true;
}
}
public int sp2px(Context context, float spValue) {
final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
return (int) (spValue * fontScale + 0.5f);
}
class SelectedRunnable implements Runnable {
@Override
public final void run() {
LoopScrollListener listener = LoopView.this.mLoopListener;
int selectedItem = getSelectedItem();
mDataList.get(selectedItem);
listener.onItemSelect(selectedItem);
}
}
/**
* Use in ACTION_UP
*/
class HalfHeightRunnable implements Runnable {
int realTotalOffset;
int realOffset;
int offset;
public HalfHeightRunnable(int offset) {
this.offset = offset;
realTotalOffset = Integer.MAX_VALUE;
realOffset = 0;
}
@Override
public void run() {
//first in
if (realTotalOffset == Integer.MAX_VALUE) {
if ((float) offset > mItemHeight / 2.0F) {
//move to next item
realTotalOffset = (int) (mItemHeight - (float) offset);
} else {
//move to pre item
realTotalOffset = -offset;
}
}
realOffset = (int) ((float) realTotalOffset * 0.1F);
if (realOffset == 0) {
if (realTotalOffset < 0) {
realOffset = -1;
} else {
realOffset = 1;
}
}
if (Math.abs(realTotalOffset) <= 0) {
cancelSchedule();
mHandler.sendEmptyMessage(MSG_SELECTED_ITEM);
return;
} else {
mTotalScrollY = mTotalScrollY + realOffset;
mHandler.sendEmptyMessage(MSG_INVALIDATE);
realTotalOffset = realTotalOffset - realOffset;
return;
}
}
}
/**
* Use in {@link LoopViewGestureListener#onFling(MotionEvent, MotionEvent, float, float)}
*/
class FlingRunnable implements Runnable {
float velocity;
final float velocityY;
FlingRunnable(float velocityY) {
this.velocityY = velocityY;
velocity = Integer.MAX_VALUE;
}
@Override
public void run() {
if (velocity == Integer.MAX_VALUE) {
if (Math.abs(velocityY) > 2000F) {
if (velocityY > 0.0F) {
velocity = 2000F;
} else {
velocity = -2000F;
}
} else {
velocity = velocityY;
}
}
Log.i(TAG, "velocity->" + velocity);
if (Math.abs(velocity) >= 0.0F && Math.abs(velocity) <= 20F) {
cancelSchedule();
mHandler.sendEmptyMessage(MSG_SCROLL_LOOP);
return;
}
int i = (int) ((velocity * 10F) / 1000F);
mTotalScrollY = mTotalScrollY - i;
if (!mCanLoop) {
float itemHeight = lineSpacingMultiplier * mMaxTextHeight;
if (mTotalScrollY <= (int) ((float) (-mInitPosition) * itemHeight)) {
velocity = 40F;
mTotalScrollY = (int) ((float) (-mInitPosition) * itemHeight);
} else if (mTotalScrollY >= (int) ((float) (mDataList.size() - 1 - mInitPosition) * itemHeight)) {
mTotalScrollY = (int) ((float) (mDataList.size() - 1 - mInitPosition) * itemHeight);
velocity = -40F;
}
}
if (velocity < 0.0F) {
velocity = velocity + 20F;
} else {
velocity = velocity - 20F;
}
mHandler.sendEmptyMessage(MSG_INVALIDATE);
}
}
}
其中,需要在values 文件夹下的attrs.xml文件中定义<declare-styleable name="LoopView">
<attr name="lineColor" format="color"/>
<attr name="topBottomTextColor" format="color"/>
<attr name="centerTextColor" format="color"/>
<attr name="textSize" format="dimension"/>
<attr name="canLoop" format="boolean"/>
<attr name="initPosition" format="integer"/>
<attr name="drawItemCount" format="integer"/>
</declare-styleable>
2 自定义的控件中需要定义一个接口
LoopScrollListener 很简单,只需要定义public interface LoopScrollListener {
void onItemSelect(int item)
}
即可。
3 布局文件中我们直接引用即可布局文件的名称:activity_time_picker.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:background="#33000000">
<LinearLayout
android:id="@+id/container_picker"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:orientation="vertical">
<RelativeLayout
android:id="@+id/container_toolbar"
android:layout_width="660px"
android:layout_height="90px"
android:layout_marginBottom="10px"
android:gravity="center"
android:background="#f2f7fa"
android:padding="20px">
<Button
android:id="@+id/btn_cancel"
android:layout_width="wrap_content"
android:layout_height="30dp"
android:background="@null"
android:text="@string/alarmclock_naozhong_dingshi_quxiao"
android:textColor="@color/dcjbqzi"
android:textSize="@dimen/size_24px"/>
<TextView
android:id="@+id/tv_date_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@null"
android:text="@string/alarmclock_naozhong_dingshi_naozhongsheding"
android:textColor="@color/dcjbqzi"
android:layout_marginLeft="80px"
android:textSize="@dimen/size_32px"
android:layout_toRightOf="@id/btn_cancel" />
<Button
android:id="@+id/btn_confirm"
android:layout_width="wrap_content"
android:layout_height="30dp"
android:layout_alignParentRight="true"
android:background="@null"
android:textColor="@color/dcjbqzi"
android:text="@string/alarmclock_naozhong_dingshi_queren"
android:textSize="@dimen/size_24px"/>
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="180dp"
android:background="#f2f7fa"
android:padding="3dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="5px">
<LoopView的全路径
android:id="@+id/picker_year"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:padding="10px"
app:centerTextColor="@color/dcjbqzi"
app:textSize="@dimen/size_30px"
app:canLoop="false"/>public class DatePickerPopWin extends PopupWindow implements View.OnClickListener{
public Button cancelBtn;
public TextView infoTv;
public Button confirmBtn;
public LoopView yearLoopView;
public LoopView monthLoopView;
public LoopView dayLoopView;
public View pickerContainerV;
public View contentView;//root view
private int am; // 上午
private int pm; //下午
private int yearPos = 0;
private int monthPos = 0;
private int dayPos = 0;
private Context mContext;
private String textCancel;
private String textConfirm;
private int colorCancel;
private int colorConfirm;
private int btnTextsize;//text btnTextsize of cancel and confirm button
private int viewTextSize;
private boolean showDayMonthYear;
List<String> yearList = new ArrayList();
List<String> monthList = new ArrayList();
List<String> dayList = new ArrayList();
public static class Builder{
//Required
private Context context;
private OnDatePickedListener listener;
public Builder(Context context,OnDatePickedListener listener){
this.context = context;
this.listener = listener;
}
//Option
private boolean showDayMonthYear = false;
private int am ;
private int pm ;
private String textCancel = "取消";
private String textConfirm = "确认";
private String textInfo = "确认";
private String dateChose = getStrDate();
private int colorCancel = Color.parseColor("#a1aeb3");
private int colorConfirm = Color.parseColor("#2c95ee");
private int btnTextSize = 16;//text btnTextsize of cancel and confirm button
private int viewTextSize = 25;
public Builder minYear(String am){
this.am = 1;
return this;
}
public Builder maxYear(String pm){
this.pm = 2;
return this;
}
public Builder textCancel(String textCancel){
this.textCancel = textCancel;
return this;
}
public Builder textInfo(String textInfo){
this.textInfo = textInfo;
return this;
}
public Builder textConfirm(String textConfirm){
this.textConfirm = textConfirm;
return this;
}
public Builder dateChose(String dateChose){
this.dateChose = dateChose;
return this;
}
public Builder colorCancel(int colorCancel){
this.colorCancel = colorCancel;
return this;
}
public Builder colorConfirm(int colorConfirm){
this.colorConfirm = colorConfirm;
return this;
}
/**
* set btn text btnTextSize
* @param textSize dp
*/
public Builder btnTextSize(int textSize){
this.btnTextSize = textSize;
return this;
}
public Builder viewTextSize(int textSize){
this.viewTextSize = textSize;
return this;
}
public DatePickerPopWin build(){
if(am > pm){
throw new IllegalArgumentException();
}
return new DatePickerPopWin(this);
}
public Builder showDayMonthYear(boolean useDayMonthYear) {
this.showDayMonthYear = useDayMonthYear;
return this;
}
}
public DatePickerPopWin(Builder builder){
this.am = builder.am;
this.pm = builder.pm;
this.textCancel = builder.textCancel;
this.textConfirm = builder.textConfirm;
this.mContext = builder.context;
this.mListener = builder.listener;
this.colorCancel = builder.colorCancel;
this.colorConfirm = builder.colorConfirm;
this.btnTextsize = builder.btnTextSize;
this.viewTextSize = builder.viewTextSize;
this.showDayMonthYear = builder.showDayMonthYear;
setSelectedDate(builder.dateChose);
initView();
}
private OnDatePickedListener mListener;
private void initView() {
contentView = LayoutInflater.from(mContext).inflate(showDayMonthYear ? R.layout.layout_date_picker_inverted : R.layout.activity_time_picker, null);
cancelBtn = (Button) contentView.findViewById(R.id.btn_cancel);
infoTv = (TextView)contentView.findViewById(R.id.tv_date_info);
confirmBtn = (Button) contentView.findViewById(R.id.btn_confirm);
yearLoopView = (LoopView) contentView.findViewById(R.id.picker_year);
monthLoopView = (LoopView) contentView.findViewById(R.id.picker_month);
dayLoopView = (LoopView) contentView.findViewById(R.id.picker_day);
pickerContainerV = contentView.findViewById(R.id.container_picker);
//
//set checked listen
yearLoopView.setLoopListener(new LoopScrollListener() {
@Override
public void onItemSelect(int item) {
yearPos = item;
// initDayPickerView();
}
});
monthLoopView.setLoopListener(new LoopScrollListener() {
@Override
public void onItemSelect(int item) {
monthPos = item;
initDayPickerView();
}
});
dayLoopView.setLoopListener(new LoopScrollListener() {
@Override
public void onItemSelect(int item) {
dayPos = item;
}
});
initPickerViews(); // init year and month loop view
initDayPickerView(); //init day loop view
cancelBtn.setOnClickListener(this);
confirmBtn.setOnClickListener(this);
contentView.setOnClickListener(this);
if(!TextUtils.isEmpty(textConfirm)){
confirmBtn.setText(textConfirm);
}
if(!TextUtils.isEmpty(textCancel)){
cancelBtn.setText(textCancel);
}
setTouchable(true);
setFocusable(true);
// setOutsideTouchable(true);
setBackgroundDrawable(new BitmapDrawable());
setAnimationStyle(R.style.FadeInPopWin);
setContentView(contentView);
setWidth(ViewGroup.LayoutParams.MATCH_PARENT);
setHeight(ViewGroup.LayoutParams.MATCH_PARENT);
}
/**
* Init year and month loop view,
* Let the day loop view be handled separately
*/
private void initPickerViews() {
int yearCount = pm - am;
for (int i = 0; i <= yearCount; i++) {
if (i == 0){
yearList.add("上午");
}else{
yearList.add("下午");
}
}
for (int j = 0; j < 12; j++) {
monthList.add(format2LenStr(j + 1));
}
yearLoopView.setDataList((ArrayList) yearList);
yearLoopView.setInitPosition(yearPos);
monthLoopView.setDataList((ArrayList) monthList);
monthLoopView.setInitPosition(monthPos);
}
/**
* Init day item
*/
private void initDayPickerView() {
Calendar calendar = Calendar.getInstance();
dayList = new ArrayList<String>();
for (int i = 0; i<60;i++){
if (i < 10){
//灏嗗崟浣嶆暟瀛楁敼鎴愬弻浣嶆暟瀛? String str = String.valueOf(i);
if (str.length() == 1){
str = "0"+str;
dayList.add(String.valueOf(str));
}
}else{
dayList.add(String.valueOf(i));
}
}
calendar.set(Calendar.MONTH, monthPos);
dayLoopView.setDataList((ArrayList) dayList);
dayLoopView.setInitPosition(dayPos);
}
/**
* set selected date position value when initView.
*
* @param dateStr
*/
public void setSelectedDate(String dateStr) {
if (!TextUtils.isEmpty(dateStr)) {
long milliseconds = getLongFromyyyyMMdd(dateStr);
Calendar calendar = Calendar.getInstance(Locale.CHINA);
if (milliseconds != -1) {
calendar.setTimeInMillis(milliseconds);
monthPos = calendar.get(Calendar.MONTH);
}
}
}
/**
* Show date picker popWindow
*
* @param activity
*/
public void showPopWin(Activity activity) {
if (null != activity) {
TranslateAnimation trans = new TranslateAnimation(
Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF,
0, Animation.RELATIVE_TO_SELF, 1,
Animation.RELATIVE_TO_SELF, 0);
showAtLocation(activity.getWindow().getDecorView(), Gravity.BOTTOM,
0, 0);
trans.setDuration(400);
trans.setInterpolator(new AccelerateDecelerateInterpolator());
pickerContainerV.startAnimation(trans);
}
}
/**
* Dismiss date picker popWindow
*/
public void dismissPopWin() {
TranslateAnimation trans = new TranslateAnimation(
Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 0,
Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 1);
trans.setDuration(400);
trans.setInterpolator(new AccelerateInterpolator());
trans.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationRepeat(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
dismiss();
}
});
pickerContainerV.startAnimation(trans);
}
@Override
public void onClick(View v) {
if (v == contentView || v == cancelBtn) {
dismissPopWin();
} else if (v == confirmBtn) {
if (null != mListener) {
//涓婂崍涓嬪崍锛屽皬鏃讹紝鍒嗛挓
int amOrpm = am + yearPos;
int hour = monthPos + 1;
int minute = dayPos + 1;
StringBuffer sb = new StringBuffer();
if (amOrpm == 1){
sb.append("涓婂崍");
}else{
sb.append("涓嬪崍");
}
sb.append("-");
sb.append(format2LenStr(hour));
sb.append("-");
sb.append(format2LenStr(minute-1));
mListener.onDatePickCompleted(amOrpm, hour, minute, sb.toString());
}
dismissPopWin();
}
}
/**
* get long from yyyy-MM-dd
*
* @param date
* @return
*/
public static long getLongFromyyyyMMdd(String date) {
SimpleDateFormat mFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
Date parse = null;
try {
parse = mFormat.parse(date);
} catch (ParseException e) {
e.printStackTrace();
}
if (parse != null) {
return parse.getTime();
} else {
return -1;
}
}
public static String getStrDate() {
SimpleDateFormat dd = new SimpleDateFormat("yyyy-MM-dd", Locale.CHINA);
return dd.format(new Date());
}
/**
* Transform int to String with prefix "0" if less than 10
* @param num
* @return
*/
public static String format2LenStr(int num) {
return (num < 10) ? "0" + num : String.valueOf(num);
}
public static int spToPx(Context context, int spValue) {
final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
return (int) (spValue * fontScale + 0.5f);
}
public interface OnDatePickedListener {
/**
* Listener when date has been checked
*
* @param amOrpm
* @param hour
* @param minute
* @param dateDesc yyyy-MM-dd
*/
void onDatePickCompleted(int amOrpm, int hour, int minute,
String dateDesc);
}
}
<LoopView的全路径 android:id="@+id/picker_month" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginLeft="1dp" android:layout_weight="1" android:padding="10px" app:centerTextColor="@color/dcjbqzi" app:textSize="@dimen/size_38px" app:canLoop="false"/> <LoopView的全路径 android:id="@+id/picker_day" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginLeft="1dp" android:padding="10px" android:layout_weight="1" app:centerTextColor="@color/dcjbqzi" app:textSize="@dimen/size_38px" app:canLoop="false"/> </LinearLayout> </RelativeLayout> </LinearLayout></RelativeLayout>
4 我们知道这个时间选择器时通过一个popupwindow弹出的窗口,所以接下来我们应该定义一个弹窗:DatePickerPopWin
public class DatePickerPopWin extends PopupWindow implements View.OnClickListener{
public Button cancelBtn;
public TextView infoTv;
public Button confirmBtn;
public LoopView yearLoopView;
public LoopView monthLoopView;
public LoopView dayLoopView;
public View pickerContainerV;
public View contentView;//root view
private int am; // 上午
private int pm; //下午
private int yearPos = 0;
private int monthPos = 0;
private int dayPos = 0;
private Context mContext;
private String textCancel;
private String textConfirm;
private int colorCancel;
private int colorConfirm;
private int btnTextsize;//text btnTextsize of cancel and confirm button
private int viewTextSize;
private boolean showDayMonthYear;
List<String> yearList = new ArrayList();
List<String> monthList = new ArrayList();
List<String> dayList = new ArrayList();
public static class Builder{
//Required
private Context context;
private OnDatePickedListener listener;
public Builder(Context context,OnDatePickedListener listener){
this.context = context;
this.listener = listener;
}
//Option
private boolean showDayMonthYear = false;
private int am ;
private int pm ;
private String textCancel = "取消";
private String textConfirm = "确认";
private String textInfo = "确认";
private String dateChose = getStrDate();
private int colorCancel = Color.parseColor("#a1aeb3");
private int colorConfirm = Color.parseColor("#2c95ee");
private int btnTextSize = 16;//text btnTextsize of cancel and confirm button
private int viewTextSize = 25;
public Builder minYear(String am){
this.am = 1;
return this;
}
public Builder maxYear(String pm){
this.pm = 2;
return this;
}
public Builder textCancel(String textCancel){
this.textCancel = textCancel;
return this;
}
public Builder textInfo(String textInfo){
this.textInfo = textInfo;
return this;
}
public Builder textConfirm(String textConfirm){
this.textConfirm = textConfirm;
return this;
}
public Builder dateChose(String dateChose){
this.dateChose = dateChose;
return this;
}
public Builder colorCancel(int colorCancel){
this.colorCancel = colorCancel;
return this;
}
public Builder colorConfirm(int colorConfirm){
this.colorConfirm = colorConfirm;
return this;
}
/**
* set btn text btnTextSize
* @param textSize dp
*/
public Builder btnTextSize(int textSize){
this.btnTextSize = textSize;
return this;
}
public Builder viewTextSize(int textSize){
this.viewTextSize = textSize;
return this;
}
public DatePickerPopWin build(){
if(am > pm){
throw new IllegalArgumentException();
}
return new DatePickerPopWin(this);
}
public Builder showDayMonthYear(boolean useDayMonthYear) {
this.showDayMonthYear = useDayMonthYear;
return this;
}
}
public DatePickerPopWin(Builder builder){
this.am = builder.am;
this.pm = builder.pm;
this.textCancel = builder.textCancel;
this.textConfirm = builder.textConfirm;
this.mContext = builder.context;
this.mListener = builder.listener;
this.colorCancel = builder.colorCancel;
this.colorConfirm = builder.colorConfirm;
this.btnTextsize = builder.btnTextSize;
this.viewTextSize = builder.viewTextSize;
this.showDayMonthYear = builder.showDayMonthYear;
setSelectedDate(builder.dateChose);
initView();
}
private OnDatePickedListener mListener;
private void initView() {
contentView = LayoutInflater.from(mContext).inflate(showDayMonthYear ? R.layout.layout_date_picker_inverted : R.layout.activity_time_picker, null);
cancelBtn = (Button) contentView.findViewById(R.id.btn_cancel);
infoTv = (TextView)contentView.findViewById(R.id.tv_date_info);
confirmBtn = (Button) contentView.findViewById(R.id.btn_confirm);
yearLoopView = (LoopView) contentView.findViewById(R.id.picker_year);
monthLoopView = (LoopView) contentView.findViewById(R.id.picker_month);
dayLoopView = (LoopView) contentView.findViewById(R.id.picker_day);
pickerContainerV = contentView.findViewById(R.id.container_picker);
//
//set checked listen
yearLoopView.setLoopListener(new LoopScrollListener() {
@Override
public void onItemSelect(int item) {
yearPos = item;
// initDayPickerView();
}
});
monthLoopView.setLoopListener(new LoopScrollListener() {
@Override
public void onItemSelect(int item) {
monthPos = item;
initDayPickerView();
}
});
dayLoopView.setLoopListener(new LoopScrollListener() {
@Override
public void onItemSelect(int item) {
dayPos = item;
}
});
initPickerViews(); // init year and month loop view
initDayPickerView(); //init day loop view
cancelBtn.setOnClickListener(this);
confirmBtn.setOnClickListener(this);
contentView.setOnClickListener(this);
if(!TextUtils.isEmpty(textConfirm)){
confirmBtn.setText(textConfirm);
}
if(!TextUtils.isEmpty(textCancel)){
cancelBtn.setText(textCancel);
}
setTouchable(true);
setFocusable(true);
// setOutsideTouchable(true);
setBackgroundDrawable(new BitmapDrawable());
setAnimationStyle(R.style.FadeInPopWin);
setContentView(contentView);
setWidth(ViewGroup.LayoutParams.MATCH_PARENT);
setHeight(ViewGroup.LayoutParams.MATCH_PARENT);
}
/**
* Init year and month loop view,
* Let the day loop view be handled separately
*/
private void initPickerViews() {
int yearCount = pm - am;
for (int i = 0; i <= yearCount; i++) {
if (i == 0){
yearList.add("上午");
}else{
yearList.add("下午");
}
}
for (int j = 0; j < 12; j++) {
monthList.add(format2LenStr(j + 1));
}
yearLoopView.setDataList((ArrayList) yearList);
yearLoopView.setInitPosition(yearPos);
monthLoopView.setDataList((ArrayList) monthList);
monthLoopView.setInitPosition(monthPos);
}
/**
* Init day item
*/
private void initDayPickerView() {
Calendar calendar = Calendar.getInstance();
dayList = new ArrayList<String>();
for (int i = 0; i<60;i++){
if (i < 10){
String str = String.valueOf(i);
if (str.length() == 1){
str = "0"+str;
dayList.add(String.valueOf(str));
}
}else{
dayList.add(String.valueOf(i));
}
}
calendar.set(Calendar.MONTH, monthPos);
dayLoopView.setDataList((ArrayList) dayList);
dayLoopView.setInitPosition(dayPos);
}
/**
* set selected date position value when initView.
*
* @param dateStr
*/
public void setSelectedDate(String dateStr) {
if (!TextUtils.isEmpty(dateStr)) {
long milliseconds = getLongFromyyyyMMdd(dateStr);
Calendar calendar = Calendar.getInstance(Locale.CHINA);
if (milliseconds != -1) {
calendar.setTimeInMillis(milliseconds);
monthPos = calendar.get(Calendar.MONTH);
}
}
}
/**
* Show date picker popWindow
*
* @param activity
*/
public void showPopWin(Activity activity) {
if (null != activity) {
TranslateAnimation trans = new TranslateAnimation(
Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF,
0, Animation.RELATIVE_TO_SELF, 1,
Animation.RELATIVE_TO_SELF, 0);
showAtLocation(activity.getWindow().getDecorView(), Gravity.BOTTOM,
0, 0);
trans.setDuration(400);
trans.setInterpolator(new AccelerateDecelerateInterpolator());
pickerContainerV.startAnimation(trans);
}
}
/**
* Dismiss date picker popWindow
*/
public void dismissPopWin() {
TranslateAnimation trans = new TranslateAnimation(
Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 0,
Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 1);
trans.setDuration(400);
trans.setInterpolator(new AccelerateInterpolator());
trans.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationRepeat(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
dismiss();
}
});
pickerContainerV.startAnimation(trans);
}
@Override
public void onClick(View v) {
if (v == contentView || v == cancelBtn) {
dismissPopWin();
} else if (v == confirmBtn) {
if (null != mListener) {
int amOrpm = am + yearPos;
int hour = monthPos + 1;
int minute = dayPos + 1;
StringBuffer sb = new StringBuffer();
if (amOrpm == 1){
sb.append("上午");
}else{
sb.append("下午");
}
sb.append("-");
sb.append(format2LenStr(hour));
sb.append("-");
sb.append(format2LenStr(minute-1));
mListener.onDatePickCompleted(amOrpm, hour, minute, sb.toString());
}
dismissPopWin();
}
}
/**
* get long from yyyy-MM-dd
*
* @param date
* @return
*/
public static long getLongFromyyyyMMdd(String date) {
SimpleDateFormat mFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
Date parse = null;
try {
parse = mFormat.parse(date);
} catch (ParseException e) {
e.printStackTrace();
}
if (parse != null) {
return parse.getTime();
} else {
return -1;
}
}
public static String getStrDate() {
SimpleDateFormat dd = new SimpleDateFormat("yyyy-MM-dd", Locale.CHINA);
return dd.format(new Date());
}
/**
* Transform int to String with prefix "0" if less than 10
* @param num
* @return
*/
public static String format2LenStr(int num) {
return (num < 10) ? "0" + num : String.valueOf(num);
}
public static int spToPx(Context context, int spValue) {
final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
return (int) (spValue * fontScale + 0.5f);
}
public interface OnDatePickedListener {
/**
* Listener when date has been checked
*
* @param amOrpm
* @param hour
* @param minute
* @param dateDesc yyyy-MM-dd
*/
void onDatePickCompleted(int amOrpm, int hour, int minute,
String dateDesc);
}
}
5 接下来只需要在我们想要弹出时间选择器的那个activity的某一个控件的点击事件中直接new 出来我们创建的popwindow就好
private void showTimeDialog() {
DatePickerPopWin pickerPopWin = new DatePickerPopWin.Builder(AlarmClockActivity.this, new DatePickerPopWin.OnDatePickedListener() {
@Override
public void onDatePickCompleted(int year, int month, int day, String dateDesc) {
//将选择的结果进行操作,此处你可以弹出一个土司或者直接打印log来看看结果
}
}).textConfirm("确认") //text of confirm button
.textInfo("闹钟定时")
.textCancel("取消") //text of cancel button
.btnTextSize(12) // button text size
.viewTextSize(16) // pick view text size
.colorCancel(Color.parseColor("#183b7b")) //color of cancel button
.colorConfirm(Color.parseColor("#183b7b"))//color of confirm button
.minYear("上午") //min year in loop
.maxYear("下午") // max year in loop
.dateChose("上午-01-00") // date chose when init popwindow
.build();
pickerPopWin.showPopWin(AlarmClockActivity.this);
}
第4步中的红色部分忘记贴出来了,再次补充一下
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#33000000">
<LinearLayout
android:id="@+id/container_picker"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:background="#f2f7fa"
android:orientation="vertical" >
<RelativeLayout
android:id="@+id/container_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="3px"
android:padding="5dp" >
<Button
android:id="@+id/btn_cancel"
android:layout_width="wrap_content"
android:layout_height="30dp"
android:background="@null"
tools:text="取消"
android:textSize="14sp" />
<TextView
android:id="@+id/tv_date_info"
android:layout_width="wrap_content"
android:layout_height="30dp"
android:background="@null"
android:text="闹钟设定"
android:gravity="center"
android:layout_marginLeft="110px"
android:layout_toRightOf="@id/btn_cancel"
android:textSize="20sp" />
<Button
android:id="@+id/btn_confirm"
android:layout_width="wrap_content"
android:layout_height="30dp"
android:layout_alignParentRight="true"
android:background="@null"
tools:text="确定"
android:textSize="14sp"/>
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="180dp"
android:background="#f2f7fa"
android:padding="3dp" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent" >
<com.bruce.pickerview.LoopView
android:id="@+id/picker_day"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginLeft="1dp"
android:layout_weight="1" />
<com.bruce.pickerview.LoopView
android:id="@+id/picker_month"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginLeft="1dp"
android:layout_weight="1" />
<com.bruce.pickerview.LoopView
android:id="@+id/picker_year"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
</LinearLayout>
</RelativeLayout>
</LinearLayout>
</RelativeLayout>
这样还有一个小小的问题就是弹窗出现和消失的方式那个只需要你在res 文件夹下创建一个anim的文件夹,在这文件下我们可以创建弹出弹出和消失的动画,大家可以随意创建,我就不贴出来啦。这样就实现了最初的效果,很简单,希望对大家有那么一小丢丢的帮助
这篇博客介绍了一种不采用三级联动效果的时间选择器实现,通过自定义控件LoopView来完成。作者展示了时间选择器的最终效果,并提供了布局文件activity_time_picker.xml的引用信息。
1131

被折叠的 条评论
为什么被折叠?



