背景
Android開發中我們會常常用到類似時間滾輪的控件,這類控件到了UI設計師手中常常會被修改成各種樣子,與其從網上蕩個類似的Demo漫無目的地修改,不如寫個我們自己的demo,對其瞭如指掌,修改樣式自然不成問題。下圖是我們的滾輪控件實現效果:
原理
我覺得對於看似複雜的控件,我們需要一步一步拆解,將功能拆解開,對應到我們所學的知識點上,然後再將這些知識點組裝起來,功能也就自然出來了。
此處我們需要繼承一個View,根據Touch事件,將數據按照一定的規則繪製在View上。
這裏首先就是要複寫View的onTouchEvent方法,捕獲手指移動的距離,根據移動距離來設置顯示的數據。
同時當我們鬆手時,應當有一個判斷標準來當前中間欄應該顯示的數據,並設置一個動畫,讓這個數據自動移動到中間位置。
關於顯示文字大小的變化效果,這裏參考了網上的一些代碼,使用拉動距離與控件高度一半的關係,此處我們使用了一個開口向下的拋物線的函數。
說了這麼多廢話,先上一張示意圖吧:
代碼
引用控件
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_height="match_parent">
<com.vonchenchen.uistudy.ui.TimePicker.WheelPicker
android:layout_width="match_parent"
android:layout_height="200dp"
android:background="#ff9e9e9e"/>
</LinearLayout>
實現代碼
控件代碼比較簡單,重要部分已做註釋,即粘即用
/**
* Created by vonchenchen on 2016/3/25 0025.
*/
public class WheelPicker extends View {
public static final String TAG = "TimePicker";
private Context mContext;
private int mViewHeight;
private int mViewWidth;
/** 中間條目寬度的 */
private int mCenterItemHeight;
private int mCenterY;
private float mLastDownY; //使用偏移量爲了
private float mMoveDistanceY = 0;
/** 當前數組索引 */
private int mCurrentDataIndex = 0;
/** 繪製文本畫筆 */
private Paint mTextPaint;
/** 繪製直線畫筆 */
private Paint mLinePaint;
/** 文字最大尺寸 */
private int mMaxTextSize;
/** 間隔長度比率 */
private float mGapRatio = 0;
/** 是否允許滑動 */
private boolean mIsTouchEnable = true;
/** 是否允許下拉 */
private boolean mIsTouchDownEnable = true;
/** 是否允許上拉 */
private boolean mIsTouchUpEnable = true;
private ArrayList<String> mDataList = new ArrayList<String>();
public WheelPicker(Context context) {
super(context);
init(context);
}
public WheelPicker(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
private void init(Context context){
this.mContext = context;
initData();
mTextPaint = new Paint();
mTextPaint.setStyle(Paint.Style.FILL);
mTextPaint.setTextAlign(Paint.Align.CENTER);
mTextPaint.setColor(0xff1f1f1f);
mLinePaint = new Paint();
mLinePaint.setStyle(Paint.Style.FILL);
mLinePaint.setTextAlign(Paint.Align.CENTER);
mLinePaint.setColor(0xff1f1f1f);
}
private void initData(){
for(int i = 0; i < 24; i++){
mDataList.add(i+"");
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mViewHeight = getMeasuredHeight();
mViewWidth = getMeasuredWidth();
mCenterY = mViewHeight/2;
mMaxTextSize = mViewHeight/8;
mCenterItemHeight = mViewHeight/4;
mGapRatio = 0.7f;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if(mIsTouchEnable) {
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
onActionDown(event);
break;
case MotionEvent.ACTION_MOVE:
onActionMove(event);
break;
case MotionEvent.ACTION_UP:
onActionUp(event);
break;
}
}
return true;
}
private void onActionDown(MotionEvent event){
mLastDownY = event.getY();
//mMoveDistanceY = 0;
}
private void onActionMove(MotionEvent event){
float currentY = event.getY();
float diff = currentY - mLastDownY;
if(mIsTouchUpEnable && mIsTouchDownEnable) {
mMoveDistanceY += diff;
}else if(!mIsTouchUpEnable && mIsTouchDownEnable){ //拉到第一個 只能向下滑動
if(diff > 0){
mMoveDistanceY += diff;
mIsTouchUpEnable = true;
}
}else if(mIsTouchUpEnable && !mIsTouchDownEnable){ //拉到最後一個 只能向上滑動
if(diff < 0){
mMoveDistanceY += diff;
mIsTouchDownEnable = true;
}
}
mLastDownY = currentY;
invalidate();
}
private void onActionUp(MotionEvent event){
returnBack(mMoveDistanceY, 0);
}
private void drawText(Canvas canvas, int currentIndex, int position){
float length = mCenterY + mMoveDistanceY;
if(position == 0){ //繪製當前中間的文字
float scale = getScale((int) mMoveDistanceY);
mTextPaint.setTextSize((float) (mMaxTextSize * scale));
mTextPaint.setColor(getAlphaTextColor(scale));
Paint.FontMetricsInt fontMetricsInt = mTextPaint.getFontMetricsInt();
int diff = (fontMetricsInt.bottom + fontMetricsInt.top)/2;
float baseY = length-diff;
canvas.drawText(mDataList.get(currentIndex), mViewWidth / 2, baseY, mTextPaint);
if(mMoveDistanceY > mCenterItemHeight/2){ //向下拉 index減
if(mCurrentDataIndex > 0) {
mCurrentDataIndex--;
}else{
mIsTouchDownEnable = false; //禁止下拉
}
mMoveDistanceY = 0;
}else if(mMoveDistanceY < -mCenterItemHeight/2){ //向上拉 index加
if(mCurrentDataIndex < mDataList.size()-1) {
mCurrentDataIndex++;
}else{
mIsTouchUpEnable = false; //禁止上拉
}
mMoveDistanceY = 0;
}
}else { //繪製上下文字
int realIndex = currentIndex + position;
if(realIndex >= 0 && realIndex < mDataList.size()){
length = length + mCenterItemHeight * mGapRatio * position;
float scale = getScale((int) length - mCenterY);
mTextPaint.setTextSize((float) (mMaxTextSize * scale));
mTextPaint.setColor(getAlphaTextColor(scale));
Paint.FontMetricsInt fontMetricsInt = mTextPaint.getFontMetricsInt();
int diff = (fontMetricsInt.bottom + fontMetricsInt.top)/2;
canvas.drawText(mDataList.get(realIndex), mViewWidth/2 , length-diff, mTextPaint);
}
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for(int i=-3; i<=3; i++){
drawText(canvas, mCurrentDataIndex, i);
}
canvas.drawLine(0, mCenterY-mCenterItemHeight/2, mViewWidth, mCenterY-mCenterItemHeight/2, mLinePaint);
canvas.drawLine(0, mCenterY + mCenterItemHeight / 2, mViewWidth, mCenterY + mCenterItemHeight / 2, mLinePaint);
}
private float getScale(int lengthToCenter){
float ret = (float) (1 - Math.pow(((float)lengthToCenter)/mCenterY, 2));
if(ret < 0){
ret = 0;
}
return ret;
}
private void returnBack(float start, float end){
mIsTouchEnable = false;
ValueAnimator valueAnimator = ValueAnimator.ofFloat(start, end);
valueAnimator.setDuration(100);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
mMoveDistanceY = (float) valueAnimator.getAnimatedValue();
if (Math.abs(mMoveDistanceY) <= 3) {
mMoveDistanceY = 0;
mIsTouchEnable = true;
}
invalidate();
}
});
valueAnimator.start();
}
/**
* 根據比率設置字體顏色
* @param scale
* @return
*/
private int getAlphaTextColor(float scale){
int data = (int) (255 * scale);
return (data << 24);
}
public String getTimeData(){
return mDataList.get(mCurrentDataIndex);
}
}