Android自定义WheelView

来源:互联网 发布:淘宝保健食品认证 编辑:程序博客网 时间:2024/06/03 13:16

功能

滚轮式选择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);            //设置文字大小```使用示例

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(“检查”);
picker.addData(“体征”);
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代码**

**源码**

package com.newbjgoodwill.mobilecwr.view;

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 java.util.ArrayList;

/**
* Created by zhangjianmin on 2017/12/1.
*/

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;           //遮罩paintprivate 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.dp2px(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 addDatas(String data,Object showPage){    addData(data, showPage);}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.dp2px(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;    }}@Overridepublic void computeScroll() {    //scroller的滚动是否完成    if(mScroller.computeScrollOffset()){        scrollY=mScroller.getCurrY();        invalidate();    }    super.computeScroll();}@Overrideprotected 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;    }}/** * 获取选中的内容 * * @return */public Object getCenterData(){    if(cacheNowItem>=0){        return data.get(cacheNowItem).showStr;    }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).showStr:null;    }}@Overridepublic 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)

**WPopupWindow的源码**

package com.newbjgoodwill.mobilecwr.view;

import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.graphics.drawable.BitmapDrawable;
import android.os.Build;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.PopupWindow;

/**
* Created by zhangjianmin on 2017/12/1.
*/

public class WPopupWindow extends PopupWindow {
private Context context;
private boolean isBgAlpha=true;
private float alpha=0.5f;

public WPopupWindow(View contentView) {    this(contentView, ViewGroup.LayoutParams.MATCH_PARENT , ViewGroup.LayoutParams.WRAP_CONTENT);}public WPopupWindow(Context context) {    this(context,null);}public WPopupWindow(int width, int height) {    this(null,width, height);}public WPopupWindow(Context context, AttributeSet attrs) {    this(context, attrs, 0);}public WPopupWindow(View contentView, int width, int height) {    this(contentView, width, height, false);}public WPopupWindow(Context context, AttributeSet attrs, int defStyleAttr) {    this(context, attrs, defStyleAttr, 0);}@TargetApi(Build.VERSION_CODES.HONEYCOMB)public WPopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {    super(context, attrs, defStyleAttr, defStyleRes);    this.context=context;    init();}public WPopupWindow(View contentView, int width, int height, boolean focusable) {    super(contentView, width, height, focusable);    this.context=contentView.getContext();    init();}public void setBgAlpha(boolean isAlpha,float alpha){    this.isBgAlpha=isAlpha;    this.alpha=alpha;}@Overridepublic void showAsDropDown(View anchor) {    this.showAsDropDown(anchor,0,0);}@Overridepublic void showAsDropDown(View anchor, int xoff, int yoff) {    this.showAsDropDown(anchor, xoff, yoff, Gravity.TOP | Gravity.START);}@Overridepublic void showAsDropDown(View anchor, int xoff, int yoff, int gravity) {    setWindowFilter(isBgAlpha, alpha);    super.showAsDropDown(anchor, xoff, yoff, gravity);}@Overridepublic void showAtLocation(View parent, int gravity, int x, int y) {    setWindowFilter(isBgAlpha, alpha);    super.showAtLocation(parent, gravity, x, y);}public void init(){    setOnDismissListener(new OnDismissListener() {        @Override        public void onDismiss() {            setWindowFilter(isBgAlpha, 1f);        }    });    setFocusable(true);    setTouchable(true);    setOutsideTouchable(true);    setOutTouchCancel(false);}/** * @param isCancel  点击对话框外时,是否取消对话框 */public void setOutTouchCancel(boolean isCancel){    if(isCancel){        setBackgroundDrawable(new BitmapDrawable());        setTouchInterceptor(new View.OnTouchListener() {            @Override            public boolean onTouch(View v, MotionEvent event) {                if (MotionEvent.ACTION_OUTSIDE == event.getAction()) {                    dismiss();                    return true;                }                return false;            }        });    }else{        setBackgroundDrawable(null);        setTouchInterceptor(new View.OnTouchListener() {            @Override            public boolean onTouch(View v, MotionEvent event) {                return false;            }        });    }}public void setWindowFilter(boolean isBgAlpha,float alpha) {    if (isBgAlpha) {        WindowManager.LayoutParams lp = ((Activity) context).getWindow().getAttributes();        lp.alpha = alpha;        //保证华为honor颜色变暗        lp.dimAmount=alpha;        ((Activity) context).getWindow().setFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND,                WindowManager.LayoutParams.FLAG_BLUR_BEHIND);        ((Activity) context).getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);        ////////////////////////////////////////////////////        ((Activity) context).getWindow().setAttributes(lp);    }}

}

**DensityUtils 源码**

package com.newbjgoodwill.mobilecwr.view;

import android.content.Context;
import android.util.TypedValue;

/**
* Created by zhangjianmin on 2017/12/1.
*/

public class DensityUtils {
private DensityUtils() {
/* cannot be instantiated */
throw new UnsupportedOperationException(“cannot be instantiated”);
}

/** * dp转px * * @param context * @param val * @return */public static float dp2px(Context context, float dpVal) {    return (float) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,            dpVal, context.getResources().getDisplayMetrics());}/** * sp转px * * @param context * @param val * @return */public static int sp2px(Context context, float spVal) {    return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,            spVal, context.getResources().getDisplayMetrics());}/** * px转dp * * @param context * @param pxVal * @return */public static float px2dp(Context context, float pxVal) {    final float scale = context.getResources().getDisplayMetrics().density;    return (pxVal / scale);}/** * px转sp * * @param fontScale * @param pxVal * @return */public static float px2sp(Context context, float pxVal) {    return (pxVal / context.getResources().getDisplayMetrics().scaledDensity);}

}

“`