Android自定义WheelView
2017-12-05 16:50
363 查看
功能
滚轮式选择View,类似于TimePicker、DataPicker,可以设置有无边界(首尾是否相接)
说明
继承于View,以OverScroller协助完成滚动效果,使用最低API版本为9。如果有需要,可以使用Scroller代替,不影响效果。
尚未添加xml自定义属性,样式设置当前只能使用代码设置。\
部分方法说明
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());
}
});
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;
// 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)
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;
}
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”);
}
}
“`
滚轮式选择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; //遮罩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.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; } } @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; } } /** * 获取选中的内容 * * @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; } } @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)
**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; } @Override public void showAsDropDown(View anchor) { this.showAsDropDown(anchor,0,0); } @Override public void showAsDropDown(View anchor, int xoff, int yoff) { this.showAsDropDown(anchor, xoff, yoff, Gravity.TOP | Gravity.START); } @Override public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) { setWindowFilter(isBgAlpha, alpha); super.showAsDropDown(anchor, xoff, yoff, gravity); } @Override public 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); }
}
“`
相关文章推荐
- Android自定义WheelView的40行关键代码
- Android 自定义WheelView
- android 自定义实现滚动View:WheelView
- Android 自定义WheelView
- Android 仿iOS时间选择器自定义WheelView
- Android自定义实现循环滚轮控件WheelView
- Android自定义实现循环滚轮控件WheelView
- Android自定义WheelView
- android 自定义按钮实现 home键 和返回键
- Android自定义Button的点击效果
- Android--自定义属性,attr format取值类型
- Android 自定义VIew仿IOS弹出框
- Android之Canvas自定义画一个时钟
- Android自定义View的实现方法,带你一步步深入了解View
- Android自定义view实现阻尼效果的加载动画
- Android中自定义Dialog样式
- android 自定义 锁屏
- android自定义View-继承
- Android 之 自定义progressdialog的实现
- Android进阶——自定义View之重写ViewGroup组合系统控件实现自定义ToolBar模板