Android 自定义WheelView

功能

滚轮式选择View,类似于TimePicker、DataPicker,可以设置有无边界(首尾是否相接)

效果图

说明

继承于View,以OverScroller协助完成滚动效果,使用最低API版本为9。如果有需要,可以使用Scroller代替,不影响效果。
尚未添加xml自定义属性,样式设置当前只能使用代码设置。

部分方法说明

public void addData(String show,Object backData);   //增加数据
public void addData(String data);                   //增加数据
public void setCircle(boolean isCircle);            //设置是否首尾相接
public void setRate(int rate);                      //设置滑动速度变化率
public void notifyDataSetChanged();                 //刷新数据及设置
public void setCenterItem(int position);            //设置被选中的位置(必须在数据添加后调用)
public void setCenterItem(String showData);         //设置被选中的数据(必须在数据添加后调用)
public Object getCenterItem();                      //获取当前被选中的数据
public void setLineColor(int lineColor);            //设置中间线条的颜色
public void setTextColor(int textColor);            //设置文字的颜色
public void setTextSize(float textSize);            //设置文字大小

使用示例

java代码

View wh= LayoutInflater.from(this).inflate(R.layout.common_window_wheel,null);
final WheelView picker= (WheelView) wh.findViewById(R.id.wheel);
picker.addData("轰趴馆");
picker.addData("台球");
picker.addData("密室逃脱");
picker.addData("卡丁车");
picker.addData("桌游");
picker.addData("真人CS");
picker.addData("DIY");
picker.setCenterItem(4);
WPopupWindow popupWindow=new WPopupWindow(wh);
popupWindow.showAtLocation(getContentView(), Gravity.BOTTOM, 0, 0);
wh.findViewById(R.id.right).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        LogUtils.e("nowData->"+picker.getCenterItem());
    }
});

xml代码

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent">
    <RelativeLayout  android:layout_width="match_parent" android:background="@color/theme_fore_bg" android:layout_height="260dp">
        <RelativeLayout  android:layout_width="match_parent" android:layout_height="@dimen/theme_menu_height" android:id="@+id/top">
            <TextView  android:layout_width="wrap_content" android:layout_height="match_parent" android:gravity="center" android:paddingLeft="@dimen/theme_padding" android:paddingRight="@dimen/theme_padding" android:textSize="@dimen/theme_text" android:textColor="@color/theme_text_light" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" android:id="@+id/left" android:text="@string/f_cancel" />
            <TextView  android:layout_width="wrap_content" android:layout_height="match_parent" android:gravity="center" android:paddingLeft="@dimen/theme_padding" android:paddingRight="@dimen/theme_padding" android:textSize="@dimen/theme_text" android:textColor="@color/theme_orange_light" android:layout_alignParentRight="true" android:layout_alignParentEnd="true" android:id="@+id/right" android:text="@string/f_sure" />

            <TextView  android:layout_width="wrap_content" android:layout_height="match_parent" android:gravity="center" android:paddingLeft="@dimen/theme_padding" android:paddingRight="@dimen/theme_padding" android:textSize="@dimen/theme_text" android:textColor="@color/theme_text_dark" android:layout_centerInParent="true" android:id="@+id/center" android:text="@string/f_game_theme" />
        </RelativeLayout>
        <com.wuwang.widget.WheelView  android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/wheel" android:layout_below="@id/top" />
    </RelativeLayout>
</RelativeLayout>

源码

package com.wuwang.widget;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Shader;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.OverScroller;

import com.wuwang.utils.DensityUtils;

import java.util.ArrayList;

/*** * * @description api 9以上的wheelview * * 由 午王 创建于 2016年03月09日 16:42 ***/

public class WheelView extends View {

    private float scrollY=0;
    private int scrollX=0;

    private int showSize=5;             //展示Item的个数
    private float textSize=16;          //文字的大小
    private boolean isCircle=false;     //是否为环形
    private int rate=120;               //惯性滑动比率,rate越大,速率越快
    private int textColor=0xFF000000;   //文字颜色
    private int lineColor=0xFF888888;   //线条的颜色

    private int cacheNowItem=-1;        //预设当前item的位置,负数表示不设定

    private int currentItem=-1;         //当前item位置



    private int width;                  //view的宽度
    private int height;                 //view的高度
    private int itemHeight;             //item的高度
    private int itemX;                  //item的X位置
    private float centerItemTop;        //中心Item的上边距位置
    private float centerItemBottom;     //中心Item的下边距位置
    private float centerItemHeight;     //中心Item的高度

    private int realHeight;             //内容的实际高度
    private int minScrollY;             //最小滚动高度
    private int maxScrollY;             //最大滚动高度

    private ArrayList<HashBean> data;   //数据集合
    private int dataSize=0;
    private Paint paint;
    private Paint coverPaint;           //遮罩paint
    private Shader shader;              //遮罩渐变

    private float lastY,downY;          //上次操作的坐标以及按下时候的坐标
    private long downTime;              //按下时的时间

    private OverScroller mScroller;

    public boolean isStart=true;

    public WheelView(Context context) {
        this(context, null);
    }

    public WheelView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public WheelView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init(){
        mScroller=new OverScroller(getContext());
        data=new ArrayList<>();
        paint=new Paint();
        paint.setAntiAlias(true);
        paint.setTextSize(DensityUtils.dip2px(getContext(), textSize));
        paint.setTextAlign(Paint.Align.CENTER);
        coverPaint=new Paint();
        if(showSize%2==0){
            showSize+=1;
        }
    }

    /** * 增加数据 * @param show 显示数据 * @param backData 选中时的返回数据 */
    public void addData(String show,Object backData){
        data.add(new HashBean(show, backData));
        dataSize++;
    }

    /** * 增加数据 * @param data 显示数据和选中时的返回数据 */
    public void addData(String data){
        addData(data, data);
    }

    public void clearData(){
        data.clear();
    }

    public void setCircle(boolean isCircle){
        this.isCircle=isCircle;
    }

    public void setTextColor(int textColor){
        this.textColor=textColor;
        invalidate();
    }

    public void setLineColor(int lineColor){
        this.lineColor=lineColor;
        invalidate();
    }

    public void setTextSize(float textSize){
        this.textSize=textSize;
        paint.setTextSize(DensityUtils.dip2px(getContext(), textSize));
        invalidate();
    }

    public void setRate(int rate){
        this.rate=rate;
    }

    public void notifyDataSetChanged(){
        isStart=true;
        invalidate();
    }

    private void measureData(){
        if(isStart){
            width=getWidth();
            itemX=width/2;
            height=getHeight();
            itemHeight=(height-getPaddingTop()-getPaddingBottom())/showSize;
            realHeight=dataSize*itemHeight;
            minScrollY=-(getRealHeight()-(showSize+1)/2*itemHeight);
            maxScrollY=(showSize-1)/2*itemHeight;
            centerItemHeight=itemHeight;
            centerItemTop=(height-getPaddingTop()-getPaddingBottom())/2+getPaddingTop()-centerItemHeight/2;
            centerItemBottom=(height-getPaddingTop()-getPaddingBottom())/2+getPaddingTop()+centerItemHeight/2;
            shader=new LinearGradient(0,0,0,height,new int[]{
                    0xFFFFFFFF,0xAAFFFFFF,0x00FFFFFF,0x00FFFFFF,0xAAFFFFFF,0xFFFFFFFF
            },new float[]{
                    0.0f,centerItemTop/height,centerItemTop/height,centerItemBottom/height,centerItemBottom/height,1.0f
            }, Shader.TileMode.REPEAT);
            coverPaint.setShader(shader);
            isStart=false;
        }
    }

    @Override
    public void computeScroll() {
        //scroller的滚动是否完成
        if(mScroller.computeScrollOffset()){
            scrollY=mScroller.getCurrY();
            invalidate();
        }
        super.computeScroll();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        measureData();
        //如果设置了当前选中
        if(cacheNowItem>=0){
            scrollY=-(cacheNowItem-(showSize-1)/2)*itemHeight;
            cacheNowItem=-1;
        }
        int startItemPos=(int)-scrollY/itemHeight;      //绘制的数据的起始位置
        paint.setColor(textColor);
        for(int i=startItemPos,j=0;i<startItemPos+showSize+2;j++,i++){
            float topY=j*itemHeight+scrollY%itemHeight;
            if(i>=0&&i<dataSize){
                canvas.drawText(data.get(i).showStr,itemX,
                        getBaseLine(paint,topY,itemHeight),paint);
            }else{
                if(isCircle){
                    int pos=i%dataSize;
                    canvas.drawText(data.get(pos<0?pos+dataSize:pos).showStr,itemX,
                            getBaseLine(paint,topY,itemHeight),paint);
                }
            }
        }

        //绘制中间的线条和遮罩层
        paint.setColor(lineColor);
        canvas.drawLine(getPaddingLeft(), centerItemTop, width-getPaddingRight(),centerItemTop,paint);
        canvas.drawLine(getPaddingLeft(), centerItemBottom, width-getPaddingRight(), centerItemBottom, paint);
        coverPaint.setShader(shader);
        canvas.drawRect(0, 0, width, height, coverPaint);
    }

    /** * 获取数据集合的大小 * @param isRefresh 是否重新计算数据集合大小 * @return */
    public int getDataSize(boolean isRefresh){
        if(isRefresh){
            dataSize=data.size();
        }
        return data.size();
    }

    /** * 设置当前Item的位置 * @param position */
    public void setCenterItem(int position){
        if(position>=0&&position<dataSize){
            cacheNowItem=position;
        }
        invalidate();
    }

    /** * 设置选中内容 * @param showData */
    public void setCenterItem(String showData){
        int size=data.size();
        for(int i=0;i<size;i++){
            if(showData.equals(data.get(i).showStr)){
                cacheNowItem=i;
                invalidate();
                return;
            }
        }
    }

    /** * 获取选中内容的数据 * * @return */
    public Object getCenterItem(){
        if(cacheNowItem>=0){
            return data.get(cacheNowItem).backData;
        }else{
            int dy=(int)scrollY%itemHeight;         //不足一个Item高度的部分
            if(Math.abs(dy)>itemHeight/2){          //如果偏移大于item的一半,
                if(scrollY<0){
                    scrollY= scrollY-itemHeight-dy;
                }else{
                    scrollY=scrollY+itemHeight-dy;
                }
            }else{
                scrollY=scrollY-dy;
            }
            mScroller.forceFinished(true);
            invalidate();
            int nowChecked;
            if(!isCircle){
                if(scrollY<minScrollY){
                    nowChecked=dataSize-1;
                }else if(scrollY>maxScrollY){
                    nowChecked=0;
                }else{
                    nowChecked= (int) (-scrollY/itemHeight+(showSize-1)/2);
                }
            }else{
                //滚轮时,重置scrollY位置,使它出现在限定范围的等效位置
                //以minScroll为相对0点,进行调整
                if(scrollY<minScrollY||scrollY>=maxScrollY){
                    int mid= (int) ((scrollY-minScrollY)%realHeight);
                    if(mid<0){
                        mid+=realHeight;
                    }
                    scrollY=mid+minScrollY;
                }
                nowChecked= (int) (-scrollY/itemHeight+(showSize-1)/2);
            }
            return dataSize>0?data.get(nowChecked).backData:null;
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                downTime=System.currentTimeMillis();
                downY=event.getRawY();
                lastY=downY;
                break;
            case MotionEvent.ACTION_MOVE:
                float y=event.getRawY();
                float dy=y-lastY;
                pretendScrollY(dy);
                lastY=y;
                break;
            case MotionEvent.ACTION_UP:
                checkStateAndPosition();
                invalidate();
                break;
        }
        return true;
    }

    private int getRealHeight(){
        if(realHeight==0){
            realHeight=dataSize*itemHeight;
        }
        return realHeight;
    }

    private void checkStateAndPosition(){
        //上拉超出
        if(!isCircle&&scrollY<-(getRealHeight()-(showSize+1)/2*itemHeight)){
            mScroller.startScroll(0, (int)scrollY, 0, (showSize+1)/2*itemHeight-getRealHeight() - (int)scrollY,400);
// mScroller.springBack(0,(int)scrollY,0,0,minScrollY,maxScrollY);
        }else if(!isCircle&&scrollY>(showSize-1)/2*itemHeight){  //下拉超出
            mScroller.startScroll(0, (int) scrollY, 0, (showSize - 1) / 2 * itemHeight - (int) scrollY, 400);
// mScroller.springBack(0,(int)scrollY,0,0,minScrollY,maxScrollY);
        }else{
            long endTime=System.currentTimeMillis();
            //超出滑动时间或者不足滑动距离
            if(endTime-downTime>250||Math.abs(lastY-downY)<itemHeight/2){
                int dy=(int)scrollY%itemHeight;         //不足一个Item高度的部分
                if(Math.abs(dy)>itemHeight/2){          //如果偏移大于item的一半,
                    if(scrollY<0){
                        mScroller.startScroll(0, (int)scrollY, 0, -itemHeight-dy);
                    }else{
                        mScroller.startScroll(0, (int)scrollY, 0, itemHeight-dy);
                    }
                }else{
                    mScroller.startScroll(0, (int)scrollY, 0, -dy);
                }
            }else{
                //滑动距离,和手指滑动距离成正比,和滑动时间成反比
                int finalY= (int) ((scrollY+rate*(lastY-downY)/(endTime-downTime)))/itemHeight*itemHeight;
                if(!isCircle){
                    if(finalY<minScrollY){
                        finalY=minScrollY;
                    }else if(finalY>maxScrollY){
                        finalY=maxScrollY;
                    }
                }
                mScroller.startScroll(0, (int) scrollY, 0, (int) (finalY -scrollY), 400);
            }
        }
    }

    private void pretendScrollY(float dy){
        scrollY+=dy;
        invalidate();
    }

    private float getBaseLine(Paint paint,float top,float height){
        Paint.FontMetricsInt fontMetrics = paint.getFontMetricsInt();
        return  ( 2*top+height - fontMetrics.bottom - fontMetrics.top) / 2;
    }

    private float getBaseLine(int position){
        return getBaseLine(paint,itemHeight*position,itemHeight);
    }

    private class HashBean{

        public HashBean(String showStr,Object backData){
            this.showStr=showStr;
            this.backData=backData;
        }

        public String showStr;
        public Object backData;
    }

}

你可能感兴趣的:(android,wheelview)