Home  >  Article  >  Backend Development  >  How to implement a custom circular time wheel

How to implement a custom circular time wheel

零到壹度
零到壹度Original
2018-04-09 10:44:391544browse


This article will share with you how to implement a custom ring-shaped time wheel. It has certain reference value. Friends in need can refer to it

Let’s take a look at the final effect first:

How to implement a custom circular time wheel

Implementation of

Function introduction: Any day and week within a year can be matched , other days and months are not limited to years
Implementation principle: Use a custom scroll wheel to write the logical parts corresponding to the day, week and month
The source code of the custom view is as follows:

package com.lwyy.wheel.view;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;


import com.lwyy.wheel.R;

import java.util.ArrayList;
import java.util.List;

 /**
 * Created by ll on 2017/9/25.
 */
 
 public class CircleWheelView extends View {
    private static final String TAG = CircleWheelView.class.getSimpleName();    
    private int mItemAlign;    
    public static final int ALIGN_CENTER = 0, ALIGN_LEFT = 1, ALIGN_RIGHT = 2;    
    private Paint mPaint;    
    private OnItemSelectedListener mOnItemSelectedListener;    
    private int mTextMaxWidth, mTextMaxHeight;
        
    private Rect mRectDrawn;    
    private int mDrawnCenterX, mDrawnCenterY;                                                  //滚轮选择器绘制中心坐标
    private int mWheelCenterX, mWheelCenterY;                                                  //滚轮选择器中心坐标
    private int mItemTextColor, mSelectedItemTextColor;                                      //数据项文本颜色以及被选中的数据项文本颜色
    private int mItemHeight;    
    private float mItemTextSize;    
    
    private int mLastPointY;                                                                      //用户手指上一次触摸事件发生时事件Y坐标
    private float moveY;    
    
    private List mData = new ArrayList();    
    private List mDataCC = new ArrayList();    
    private int mTurnToCenterX;    
    
    private int mCurrentItemPosition;    
    private int mDefaultHalfNum = 7, mDefaultVisibleNum = 13;                                         //设置当前view默认可见的item个数
    private int mVisibleHalfNum, mVisibleCount;                                                 //视图区域内的展示的item个数
    private int mDataSize;    
    
    private boolean isCyclic;                                                                    //数据是否循环展示  针对list的数目小于mDefaultVisibleNum
    private boolean isLoopDisplay;                                                              //所有的数据是否是球形展示,即永远的头尾衔接,如果isCyclic是true,则该值也是true

    private float[] rates = null;    
    private float[] textSizeRates = null;    
    
    public CircleWheelView(Context context) {    
        this(context, null);
    }    
    
    public CircleWheelView(Context context, AttributeSet attrs) {    
        super(context, attrs);
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleWheelView);
        mItemTextSize = a.getDimensionPixelSize(R.styleable.CircleWheelView_wheel_item_text_size,
                getResources().getDimensionPixelSize(R.dimen.WheelItemTextSize_Default));
        mItemAlign = a.getInt(R.styleable.CircleWheelView_wheel_item_align, ALIGN_CENTER);
        mItemTextColor = a.getColor(R.styleable.CircleWheelView_wheel_item_text_color, 0xFF888888);
        mSelectedItemTextColor = a.getColor(R.styleable.CircleWheelView_wheel_selected_item_text_color, 0xFF888899);
        mTurnToCenterX = a.getInt(R.styleable.CircleWheelView_wheel_turn_to_centerx, 0);             //转向中间,偏移的距离
        isCyclic = a.getBoolean(R.styleable.CircleWheelView_wheel_cyclic, false);
        isLoopDisplay = a.getBoolean(R.styleable.CircleWheelView_wheel_loop_display, false);
        a.recycle();

        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG | Paint.LINEAR_TEXT_FLAG);
        mPaint.setStyle(Paint.Style.FILL);
        mRectDrawn = new Rect();
    }    
    
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {   
        int modeWidth = MeasureSpec.getMode(widthMeasureSpec);        
        int modeHeight = MeasureSpec.getMode(heightMeasureSpec);        
        int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);        
        int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);        
        
        // 计算原始内容尺寸
        int resultWidth = mTextMaxWidth;        
        int resultHeight = mTextMaxHeight * mVisibleCount;        
        
        // 考虑内边距对尺寸的影响
        resultWidth += getPaddingLeft() + getPaddingRight();
        resultHeight += getPaddingTop();        
        
        // 考虑父容器对尺寸的影响
        resultWidth = measureSize(modeWidth, sizeWidth, resultWidth);
        resultHeight = measureSize(modeHeight, sizeHeight, resultHeight);
        setMeasuredDimension(resultWidth, resultHeight);
    }    
    
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {    
        super.onSizeChanged(w, h, oldw, oldh);        
        // 获取内容区域中心坐标
        mRectDrawn.set(getPaddingLeft(), getPaddingTop(), getWidth() - getPaddingRight(),
                getHeight() - getPaddingBottom());
        mWheelCenterX = mRectDrawn.centerX();
        mWheelCenterY = mRectDrawn.centerY();
        computeDrawnCenter();
    }    
    
    @Override
    protected void onDraw(Canvas canvas) {    
        if (mDataCC.size() > 0) {
            mPaint.setStrokeWidth(1);
            mPaint.setSubpixelText(true);                                                               //设置该项为true,将有助于文本在LCD屏幕上的显示效果
            canvas.save();            
            int indexBottom = 0;            
            float distanceX = 0, mDrawnItemCenterY = 0;            
            int endIndex = isCyclic ? 13 : Math.min(mDefaultVisibleNum, mDataCC.size());            
            if (endIndex > mData.size())
                endIndex = mData.size();            
            for (int i = 0; i < endIndex; i++) {        
                if (i == 0) mPaint.setColor(mSelectedItemTextColor);                
                else mPaint.setColor(mItemTextColor);                
                if (isCyclic) {          
                        if (i < mVisibleHalfNum)
                        indexBottom = i;                    
                else
                        indexBottom = i - mVisibleHalfNum + 1;                    
                if (i < mVisibleHalfNum)
                        mDrawnItemCenterY = (int) (mDrawnCenterY - (mItemHeight * rates[indexBottom]));                    
                else
                        mDrawnItemCenterY = (int) (mDrawnCenterY + (mItemHeight * rates[indexBottom]));                    
                if (i == endIndex - 1)
                        mDrawnItemCenterY = (int) (mDrawnCenterY + (mItemHeight * rates[indexBottom]) - 10);
                } else if (mData.size() < 13) {                    if (i < mVisibleHalfNum + 1)
                        indexBottom = i;                    else
                        indexBottom = i - mVisibleHalfNum;                    if (i < mVisibleHalfNum + 1)
                        mDrawnItemCenterY = (int) (mDrawnCenterY - (mItemHeight * rates[indexBottom]));                    else
                        mDrawnItemCenterY = (int) (mDrawnCenterY + (mItemHeight * rates[indexBottom]));                    if (i == endIndex - 1)
                        mDrawnItemCenterY = (int) (mDrawnCenterY + (mItemHeight * rates[indexBottom]) - 10);//                    if (i < mVisibleHalfNum)//                        indexBottom = i;//                    else//                        indexBottom = i - mVisibleHalfNum + 1;////                    if (i < mVisibleHalfNum)//                        mDrawnItemCenterY = (int) (mDrawnCenterY - (mItemHeight * rates[indexBottom]));//                    else//                        mDrawnItemCenterY = (int) (mDrawnCenterY + (mItemHeight * rates[indexBottom]));//                    if (i == endIndex - 1)//                        mDrawnItemCenterY = (int) (mDrawnCenterY + (mItemHeight * rates[indexBottom]) - 10);
                } else {          
                   if (i < mVisibleHalfNum)
                        indexBottom = i;                    
                   else
                        indexBottom = i - mVisibleHalfNum + 1;
  //                    LogUtil.e(TAG, "indexBottom:" + indexBottom + ",mVisibleHalfNum:" + mVisibleHalfNum + ",i:" + i);
                    if (indexBottom >= mDefaultHalfNum)                        
                        continue;                    
                    if (i < mVisibleHalfNum)
                        mDrawnItemCenterY = (int) (mDrawnCenterY - (mItemHeight * rates[indexBottom]));                    else
                        mDrawnItemCenterY = (int) (mDrawnCenterY + (mItemHeight * rates[indexBottom]));                    if (i == endIndex - 1)
                        mDrawnItemCenterY = (int) (mDrawnCenterY + (mItemHeight * rates[indexBottom]) - 10);
                }


                mPaint.setTextSize(mItemTextSize * textSizeRates[indexBottom]);
                paintText(canvas, distanceX, rates, mDrawnItemCenterY, indexBottom, i);
            }
        }
    }    
    
    private void paintText(Canvas canvas, float distanceX, float[] rates, float drawnCenterY, int indexBottom, int i) {   
         float mDistanceSubX = mDrawnCenterX - mTurnToCenterX * rates[indexBottom] * rates[indexBottom];        
         float mDistanceAddX = mDrawnCenterX + mTurnToCenterX * rates[indexBottom] * rates[indexBottom];        
         switch (mItemAlign) {      
           case ALIGN_CENTER:
                distanceX = mDrawnCenterX;                
                break;            
           case ALIGN_LEFT:
                distanceX = mData.get(i).toString().length() <= 1 ?
                        mDistanceSubX + 4 * (mVisibleHalfNum - indexBottom) : mDistanceSubX;                
                break;            
           case ALIGN_RIGHT:
                distanceX = mData.get(i).toString().length() <= 1 ?
                        mDistanceAddX - 4 * (mVisibleHalfNum - indexBottom) : mDistanceAddX;                
                break;
        }
        String text = "";    
            if (!isLoopDisplay && !isCyclic && mDataSize > mDefaultHalfNum) {       
              if (mCurrentItemPosition < mDefaultHalfNum - 1 && (i >= mDefaultHalfNum || i <= mCurrentItemPosition)) {
                text = String.valueOf(mData.get(i));
            } else if (mCurrentItemPosition > mDefaultHalfNum - 1 && i < mDefaultHalfNum + (mDataSize - mCurrentItemPosition) - 1)
                text = String.valueOf(mData.get(i));            
            else if (mCurrentItemPosition == mDefaultHalfNum - 1)
                text = String.valueOf(mData.get(i));
        } else if (isCyclic && !isLoopDisplay) {        
             if (mCurrentItemPosition < mDefaultHalfNum - 1 && ((i >= mDefaultHalfNum && i < mDataCC.size() - mCurrentItemPosition - 1 + mDefaultHalfNum) || i <= mCurrentItemPosition)) {
                text = String.valueOf(mData.get(i));
            } else if (mCurrentItemPosition > mDefaultHalfNum - 1 && i < mDefaultHalfNum + (mDataCC.size() - mCurrentItemPosition) - 1)
                text = String.valueOf(mData.get(i));            else if (mCurrentItemPosition == mDefaultHalfNum - 1 && i < mDataCC.size())
                text = String.valueOf(mData.get(i));
        } else {
            text = String.valueOf(mData.get(i));
       //            LogUtil.i(TAG, "text:" + mData.get(i) + ",i:" + i);
        }
        canvas.drawText(text, distanceX, drawnCenterY, mPaint);
    }    
    
    @Override
    public boolean onTouchEvent(MotionEvent event) {    
        switch (event.getAction()) {     
            case MotionEvent.ACTION_DOWN:
                mLastPointY = (int) event.getY();                
                break;            
            case MotionEvent.ACTION_MOVE:
                moveY = event.getY() - mLastPointY;                
                if (Math.abs(moveY) < 1) break;                
                if (mDataCC.size() < mDefaultHalfNum)                    
                    break;                
                if (!isLoopDisplay) {         
                 if (mCurrentItemPosition == 0 && moveY > 0)                        
                   break;                    
                if (isCyclic && mDataCC.size() < mDefaultVisibleNum && mCurrentItemPosition == mDataCC.size() - 1 && moveY < 0)                        
                   break;                    
                else if (mCurrentItemPosition == mDataSize - 1 && moveY < 0)                        
                  break;
                }

                changeMoveItemPosition(event, mItemHeight * 3 / 2);                
                break;            
             case MotionEvent.ACTION_UP:                
                if (mDataCC.size() < mDefaultHalfNum) {
                    changeMoveItemPosition(event, mItemHeight / 2);
                }
                
                if (null != mOnItemSelectedListener && mCurrentItemPosition < mDataCC.size() && mDataCC.size() > 0 && mCurrentItemPosition >= 0)
                    mOnItemSelectedListener.onItemSelected(this, mDataCC.get(mCurrentItemPosition), mCurrentItemPosition);                break;
        }        
        return true;
    }    
    
    private void changeMoveItemPosition(MotionEvent event, int moveHeight) {    
        if (Math.abs(moveY) > moveHeight) {
            mLastPointY = (int) event.getY();            
            if (moveY > 0)
                mCurrentItemPosition--;            
            else
                mCurrentItemPosition++;            
                
             if (mCurrentItemPosition > mDataCC.size() - 1)
                mCurrentItemPosition -= mDataCC.size();            
             if (mCurrentItemPosition < 0)
                mCurrentItemPosition += mDataCC.size();            
             if (mCurrentItemPosition >= 0 && mCurrentItemPosition < mDataCC.size())
                setSelectedItemPosition(mDataCC, mCurrentItemPosition);
        }
    }    
    
    private void computeTextSize() {
        mTextMaxWidth = mTextMaxHeight = 0;        
        for (Object obj : mData) {
            String text = String.valueOf(obj);            
            int width = (int) mPaint.measureText(text);
            mTextMaxWidth = Math.max(mTextMaxWidth, width);
        }
        Paint.FontMetrics metrics = mPaint.getFontMetrics();
        mTextMaxHeight = (int) (metrics.bottom - metrics.top);
    }    
    
    private int measureSize(int mode, int sizeExpect, int sizeActual) {     
       int realSize;        if (mode == MeasureSpec.EXACTLY) {
            realSize = sizeExpect;
        } else {
            realSize = sizeActual;            if (mode == MeasureSpec.AT_MOST)
                realSize = Math.min(realSize, sizeExpect);
        }        return realSize;
    }    
    
    //固定的等比高度
    private void computeDrawnCenter() {   
        int num = 7;
        mItemHeight = mRectDrawn.height() / (num * 2 - 2);
        mPaint.setTextAlign(Paint.Align.CENTER);
        mDrawnCenterX = mWheelCenterX;
        mDrawnCenterY = mWheelCenterY + mItemHeight / num;
    }    
    
    public void updateVisibleItemCount(int num) {    
        if (isCyclic) {
            mVisibleCount = mDefaultVisibleNum = 13;
            mVisibleHalfNum = mDefaultHalfNum = 7;
        } else {       
            if (num >= mDefaultHalfNum)
                mVisibleHalfNum = mDefaultHalfNum;            
            else {
                mVisibleHalfNum = num % 2 == 1 ? num / 2 + 1 : num / 2;
            }
        }
        
   //        LogUtil.e(TAG, "mVisibleHalfNum:" + mVisibleHalfNum);
        mVisibleCount = Math.min(mDefaultVisibleNum, num);
        mDataSize = isCyclic ? mDefaultVisibleNum : num;
    }    
    
    public void setData(List mData, int pos) {
        setData(mData, pos, true);
    }    
    
    public void setData(List mData, int pos, boolean isLoopDisplay) {
        setData(mData, pos, isLoopDisplay, false, 13);
    }    
    
    public void setData(List mData, int pos, boolean isLoopDisplay, boolean isCycle) {
        setData(mData, pos, isLoopDisplay, isCycle, 13);
    }    
    
    
    /**
     * @param mData          传入的展示列表数据
     * @param pos            当前view的中间item展示的内容
     * @param isLoopDisplay  所有的数据是否是球形展示,即永远的头尾衔接,即item由mDataSize - 1下滑变为0,默认true
     * @param isCycle        数据是否循环展示  针对list的数目小于mDefaultVisibleNum. 默认false
     * @param visibleShowNum 当前界面展示的item个数
     */
    public void setData(List mData, int pos, boolean isLoopDisplay, boolean isCycle, int visibleShowNum) {    
        if (mData.size() <= 0 || pos < 0 || pos >= mData.size())         
           return;        
        if (visibleShowNum % 2 == 1)
            visibleShowNum++;        
        if (mData.size() > 12 && isCycle)
            isCycle = false;        
        else if (mData.size() < 13 && !isCycle && !isLoopDisplay) {
            isCycle = true;
        }

        mDefaultVisibleNum = mData.size() < 13 && !isCycle ? mData.size() : 13;
        mDefaultHalfNum = mDefaultVisibleNum < 13 ? mDefaultVisibleNum / 2 : 7;        
        this.isLoopDisplay = isLoopDisplay;        
        this.isCyclic = isCycle;        
        
        if (this.mDataCC.size() > 0)            
        this.mDataCC.clear();        
        this.mDataCC.addAll(mData);        
        this.mCurrentItemPosition = pos;

        updateVisibleItemCount(mData.size());
        setSelectedItemPosition(mData, pos);
        updateRates();
        computeTextSize();
        requestLayout();
        invalidate();
    }    
    
    public void updateDataPos(int pos) {    
        if (pos < 0)            
            return;        
        if (pos >= mDataCC.size())
            pos = mDataCC.size() / 2 - 1;        
        this.mCurrentItemPosition = pos;
        setSelectedItemPosition(mDataCC, pos);
        invalidate();
    }    
    
    private void updateRates() {    
        int num = 7;        
        if (null == rates)
            rates = new float[num];        
        if (null == textSizeRates)
            textSizeRates = new float[num];        
            
        float rate = 0.0f;        
        for (int i = 0; i < num; i++) {      
              if (i > 0) {
                rate = (3 * (11 * i - (i - 1) * (i - 1))) / 22f;                
              if (i == num - 1)
                    rate = (3 * (11 * i - (i - 1) * (i - 1)) + 4) / 22f;
            }
            rates[i] = rate;
            textSizeRates[i] = ((18 - 7 * i / 3) / 13f);
        }
    }    
    
    public void setItemAlign(int align) {     
        this.mItemAlign = align;
        invalidate();
    }    
    
    public void setTurnToCenter(int turnToCenterX) {    
       this.mTurnToCenterX = turnToCenterX;
        requestLayout();
        invalidate();
    }    
    
    public void setSelectedItemPosition(List mDatas, int centerPos) {   
         if (mData.size() > 0)
            mData.clear();
        mDataSize = isCyclic ? mDefaultVisibleNum : mDatas.size();        
        int halfIndex = isCyclic ? 7 : Math.min(mVisibleHalfNum, mDefaultHalfNum);
  //        LogUtil.i(TAG, "endIndex:" + endIndex + ",topPos:" + topPos + ",centerPos:" + centerPos + ",bottomPos:" + bottomPos + ",halfIndex:" + halfIndex);
  //        LogUtil.i(TAG, "endIndex:" + endIndex + ",mVisibleHalfNum:" + mVisibleHalfNum + ",mDefaultHalfNum:" + mDefaultHalfNum + ",mDefaultVisibleNum:" + mDefaultVisibleNum);
        int bottom = 0;
        mData.add(0, mDatas.get(centerPos));        
        if ((isCyclic && mDatas.size() < 13) || mDatas.size() >= 13) {      
              for (int i = 1; i < halfIndex; i++) {
                bottom = centerPos - i;
                bottom = chooseIndex(bottom, mDatas.size());
                mData.add(mDatas.get(bottom));
            }            
            
            for (int i = mDatas.size() - 1; i >= halfIndex; i--) {
                bottom = centerPos + mDatas.size() - i;
                bottom = chooseIndex(bottom, mDatas.size());
                mData.add(mDatas.get(bottom));
            }            
            
            if (isCyclic)         
               for (int i = mDatas.size(); i < 13; i++) {
                    bottom = i - halfIndex + 1 + centerPos;
                    bottom = chooseIndex(bottom, mDatas.size());
    //                    LogUtil.e(TAG, "bottom:" + bottom + ",:" + mDatas.get(bottom));
                    mData.add(mDatas.get(bottom));
                }

        } else if (mDatas.size() < 13) {        
            //下面个数比上面多
            for (int i = 1; i <= halfIndex; i++) {
                bottom = centerPos - i;
                bottom = chooseIndex(bottom, mDatas.size());
                mData.add(mDatas.get(bottom));
            }            
            
            for (int i = mDatas.size() - 1; i > halfIndex; i--) {
                bottom = centerPos + mDatas.size() - i;
                bottom = chooseIndex(bottom, mDatas.size());
                mData.add(mDatas.get(bottom));
            }
        }
        invalidate();
    }    
    
    public void setTextSize(float textSize) {   
        this.mItemTextSize = textSize;
        invalidate();
    }    
    
    public void setTextSelectColor(int colorRes) {    
        this.mSelectedItemTextColor = colorRes;
        requestLayout();
        invalidate();
    }    
    
    public int chooseIndex(int index, int dataSize) {   
        if (index > dataSize - 1)
            index -= dataSize;        
        if (index < 0)
            index += dataSize;       
        return index;
    }    
    
    public void setOnItemSelectedListener(OnItemSelectedListener onItemSelectedListener) {     
       this.mOnItemSelectedListener = onItemSelectedListener;
    }    
    
    public int getListSize() {     
       if (null != mDataCC)            
           return mDataCC.size();        
       return 0;
    }    
    
    /**
     * 滚轮选择器Item项被选中时监听接口
     */
    public interface OnItemSelectedListener {
        void onItemSelected(CircleWheelView view, Object data, int position);
    }
}

The above is the detailed content of How to implement a custom circular time wheel. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn