package com.dayu.baselibrary.view.datepicker;
|
|
import android.animation.ObjectAnimator;
|
import android.animation.PropertyValuesHolder;
|
import android.content.Context;
|
import android.graphics.Canvas;
|
import android.graphics.Paint;
|
import android.graphics.Paint.Align;
|
import android.graphics.Paint.Style;
|
import android.os.Handler;
|
import android.os.Message;
|
import android.text.TextUtils;
|
import android.util.AttributeSet;
|
import android.view.MotionEvent;
|
import android.view.View;
|
|
|
import java.lang.ref.WeakReference;
|
import java.util.ArrayList;
|
import java.util.List;
|
import java.util.Timer;
|
import java.util.TimerTask;
|
|
/**
|
* 说明:内容选择器,例如年月日、省市区等
|
* 作者:liuwan1992
|
* 添加时间:2016/9/28
|
* 修改人:liuwan1992
|
* 修改时间:2018/12/21
|
*/
|
public class PickerView extends View {
|
|
private Context mContext;
|
|
private Paint mPaint;
|
private int mLightColor, mDarkColor;
|
private float mHalfWidth, mHalfHeight, mQuarterHeight;
|
private float mMinTextSize, mTextSizeRange;
|
private float mTextSpacing, mHalfTextSpacing;
|
|
private float mScrollDistance;
|
private float mLastTouchY;
|
private List<String> mDataList = new ArrayList<>();
|
private int mSelectedIndex;
|
private boolean mCanScroll = true;
|
private boolean mCanScrollLoop = true;
|
private OnSelectListener mOnSelectListener;
|
private ObjectAnimator mScrollAnim;
|
private boolean mCanShowAnim = true;
|
|
private Timer mTimer = new Timer();
|
private TimerTask mTimerTask;
|
private Handler mHandler = new ScrollHandler(this);
|
|
/**
|
* 自动回滚到中间的速度
|
*/
|
private static final float AUTO_SCROLL_SPEED = 10;
|
|
/**
|
* 透明度:最小 120,最大 255,极差 135
|
*/
|
private static final int TEXT_ALPHA_MIN = 120;
|
private static final int TEXT_ALPHA_RANGE = 135;
|
|
/**
|
* 选择结果回调接口
|
*/
|
public interface OnSelectListener {
|
void onSelect(View view, String selected);
|
}
|
|
private static class ScrollTimerTask extends TimerTask {
|
private WeakReference<Handler> mWeakHandler;
|
|
private ScrollTimerTask(Handler handler) {
|
mWeakHandler = new WeakReference<>(handler);
|
}
|
|
@Override
|
public void run() {
|
Handler handler = mWeakHandler.get();
|
if (handler == null) return;
|
|
handler.sendEmptyMessage(0);
|
}
|
}
|
|
private static class ScrollHandler extends Handler {
|
private WeakReference<PickerView> mWeakView;
|
|
private ScrollHandler(PickerView view) {
|
mWeakView = new WeakReference<>(view);
|
}
|
|
@Override
|
public void handleMessage(Message msg) {
|
PickerView view = mWeakView.get();
|
if (view == null) return;
|
|
view.keepScrolling();
|
}
|
}
|
|
public PickerView(Context context, AttributeSet attrs) {
|
super(context, attrs);
|
|
mContext = context;
|
initPaint();
|
}
|
|
private void initPaint() {
|
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
mPaint.setStyle(Style.FILL);
|
mPaint.setTextAlign(Align.CENTER);
|
mLightColor = getContext().getResources().getColor(com.dayu.baselibrary.R.color.date_picker_text_light);
|
mDarkColor = getContext().getResources().getColor(com.dayu.baselibrary.R.color.date_picker_text_dark);
|
}
|
|
@Override
|
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
|
mHalfWidth = getMeasuredWidth() / 2f;
|
int height = getMeasuredHeight();
|
mHalfHeight = height / 2f;
|
mQuarterHeight = height / 4f;
|
float maxTextSize = height / 7f;
|
mMinTextSize = maxTextSize / 2.2f;
|
mTextSizeRange = maxTextSize - mMinTextSize;
|
mTextSpacing = mMinTextSize * 2.8f;
|
mHalfTextSpacing = mTextSpacing / 2f;
|
}
|
|
@Override
|
protected void onDraw(Canvas canvas) {
|
super.onDraw(canvas);
|
|
if (mSelectedIndex >= mDataList.size()) return;
|
|
// 绘制选中的 text
|
drawText(canvas, mLightColor, mScrollDistance, mDataList.get(mSelectedIndex));
|
|
// 绘制选中上方的 text
|
for (int i = 1; i <= mSelectedIndex; i++) {
|
drawText(canvas, mDarkColor, mScrollDistance - i * mTextSpacing,
|
mDataList.get(mSelectedIndex - i));
|
}
|
|
// 绘制选中下方的 text
|
int size = mDataList.size() - mSelectedIndex;
|
for (int i = 1; i < size; i++) {
|
drawText(canvas, mDarkColor, mScrollDistance + i * mTextSpacing,
|
mDataList.get(mSelectedIndex + i));
|
}
|
}
|
|
private void drawText(Canvas canvas, int textColor, float offsetY, String text) {
|
if (TextUtils.isEmpty(text)) return;
|
|
float scale = 1 - (float) Math.pow(offsetY / mQuarterHeight, 2);
|
scale = scale < 0 ? 0 : scale;
|
mPaint.setTextSize(mMinTextSize + mTextSizeRange * scale);
|
mPaint.setColor(textColor);
|
mPaint.setAlpha(TEXT_ALPHA_MIN + (int) (TEXT_ALPHA_RANGE * scale));
|
|
// text 居中绘制,mHalfHeight + offsetY 是 text 的中心坐标
|
Paint.FontMetrics fm = mPaint.getFontMetrics();
|
float baseline = mHalfHeight + offsetY - (fm.top + fm.bottom) / 2f;
|
canvas.drawText(text, mHalfWidth, baseline, mPaint);
|
}
|
|
@Override
|
public boolean dispatchTouchEvent(MotionEvent event) {
|
return mCanScroll && super.dispatchTouchEvent(event);
|
}
|
|
@Override
|
public boolean onTouchEvent(MotionEvent event) {
|
switch (event.getActionMasked()) {
|
case MotionEvent.ACTION_DOWN:
|
cancelTimerTask();
|
mLastTouchY = event.getY();
|
break;
|
|
case MotionEvent.ACTION_MOVE:
|
float offsetY = event.getY();
|
mScrollDistance += offsetY - mLastTouchY;
|
if (mScrollDistance > mHalfTextSpacing) {
|
if (!mCanScrollLoop) {
|
if (mSelectedIndex == 0) {
|
mLastTouchY = offsetY;
|
invalidate();
|
break;
|
} else {
|
mSelectedIndex--;
|
}
|
} else {
|
// 往下滑超过离开距离,将末尾元素移到首位
|
moveTailToHead();
|
}
|
mScrollDistance -= mTextSpacing;
|
} else if (mScrollDistance < -mHalfTextSpacing) {
|
if (!mCanScrollLoop) {
|
if (mSelectedIndex == mDataList.size() - 1) {
|
mLastTouchY = offsetY;
|
invalidate();
|
break;
|
} else {
|
mSelectedIndex++;
|
}
|
} else {
|
// 往上滑超过离开距离,将首位元素移到末尾
|
moveHeadToTail();
|
}
|
mScrollDistance += mTextSpacing;
|
}
|
mLastTouchY = offsetY;
|
invalidate();
|
break;
|
|
case MotionEvent.ACTION_UP:
|
// 抬起手后 mSelectedIndex 由当前位置滚动到中间选中位置
|
if (Math.abs(mScrollDistance) < 0.01) {
|
mScrollDistance = 0;
|
break;
|
}
|
cancelTimerTask();
|
mTimerTask = new ScrollTimerTask(mHandler);
|
mTimer.schedule(mTimerTask, 0, 10);
|
break;
|
}
|
return true;
|
}
|
|
private void cancelTimerTask() {
|
if (mTimerTask != null) {
|
mTimerTask.cancel();
|
mTimerTask = null;
|
}
|
if (mTimer != null) {
|
mTimer.purge();
|
}
|
}
|
|
private void moveTailToHead() {
|
if (!mCanScrollLoop || mDataList.isEmpty()) return;
|
|
String tail = mDataList.get(mDataList.size() - 1);
|
mDataList.remove(mDataList.size() - 1);
|
mDataList.add(0, tail);
|
}
|
|
private void moveHeadToTail() {
|
if (!mCanScrollLoop || mDataList.isEmpty()) return;
|
|
String head = mDataList.get(0);
|
mDataList.remove(0);
|
mDataList.add(head);
|
}
|
|
private void keepScrolling() {
|
if (Math.abs(mScrollDistance) < AUTO_SCROLL_SPEED) {
|
mScrollDistance = 0;
|
if (mTimerTask != null) {
|
cancelTimerTask();
|
|
if (mOnSelectListener != null && mSelectedIndex < mDataList.size()) {
|
mOnSelectListener.onSelect(this, mDataList.get(mSelectedIndex));
|
}
|
}
|
} else if (mScrollDistance > 0) {
|
// 向下滚动
|
mScrollDistance -= AUTO_SCROLL_SPEED;
|
} else {
|
// 向上滚动
|
mScrollDistance += AUTO_SCROLL_SPEED;
|
}
|
invalidate();
|
}
|
|
/**
|
* 设置数据
|
*/
|
public void setDataList(List<String> list) {
|
if (list == null || list.isEmpty()) return;
|
|
mDataList = list;
|
// 重置 mSelectedIndex,防止溢出
|
mSelectedIndex = 0;
|
invalidate();
|
}
|
|
/**
|
* 选择选中项
|
*/
|
public void setSelected(int index) {
|
if (index >= mDataList.size()) return;
|
|
mSelectedIndex = index;
|
if (mCanScrollLoop) {
|
// 可循环滚动时,mSelectedIndex 值固定为 mDataList / 2
|
int position = mDataList.size() / 2 - mSelectedIndex;
|
if (position < 0) {
|
for (int i = 0; i < -position; i++) {
|
moveHeadToTail();
|
mSelectedIndex--;
|
}
|
} else if (position > 0) {
|
for (int i = 0; i < position; i++) {
|
moveTailToHead();
|
mSelectedIndex++;
|
}
|
}
|
}
|
invalidate();
|
}
|
|
/**
|
* 设置选择结果监听
|
*/
|
public void setOnSelectListener(OnSelectListener listener) {
|
mOnSelectListener = listener;
|
}
|
|
/**
|
* 是否允许滚动
|
*/
|
public void setCanScroll(boolean canScroll) {
|
mCanScroll = canScroll;
|
}
|
|
/**
|
* 是否允许循环滚动
|
*/
|
public void setCanScrollLoop(boolean canLoop) {
|
mCanScrollLoop = canLoop;
|
}
|
|
/**
|
* 执行滚动动画
|
*/
|
public void startAnim() {
|
if (!mCanShowAnim) return;
|
|
if (mScrollAnim == null) {
|
PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1f, 0f, 1f);
|
PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1f, 1.3f, 1f);
|
PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1f, 1.3f, 1f);
|
mScrollAnim = ObjectAnimator.ofPropertyValuesHolder(this, alpha, scaleX, scaleY).setDuration(200);
|
}
|
|
if (!mScrollAnim.isRunning()) {
|
mScrollAnim.start();
|
}
|
}
|
|
/**
|
* 是否允许滚动动画
|
*/
|
public void setCanShowAnim(boolean canShowAnim) {
|
mCanShowAnim = canShowAnim;
|
}
|
|
/**
|
* 销毁资源
|
*/
|
public void onDestroy() {
|
mOnSelectListener = null;
|
mHandler.removeCallbacksAndMessages(null);
|
if (mScrollAnim != null && mScrollAnim.isRunning()) {
|
mScrollAnim.cancel();
|
}
|
cancelTimerTask();
|
if (mTimer != null) {
|
mTimer.cancel();
|
mTimer = null;
|
}
|
}
|
|
}
|