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 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 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 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 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; } } }