Android滚轮选择时间控件使用详解
程序员文章站
2024-02-27 23:25:57
滚轮选择控件
android自带的选择时间控件有点丑,往往产品和设计都比较嫌弃,希望做成ios一样的滚轮选择,下面是我在numberpicker的基础上自定义的选择控件,...
滚轮选择控件
android自带的选择时间控件有点丑,往往产品和设计都比较嫌弃,希望做成ios一样的滚轮选择,下面是我在numberpicker的基础上自定义的选择控件,效果如下:
原理
- 基于numberpicker实现
- 动态填充数值
- 联动
- 接口监听回调
实现滚轮效果有github上mark比较多的wheelview,但是阅读源码发现数据是一次性填入的,选择时间的话,填入10年就是10*365=3650条数据,也就是new出三千多个textview,想想都觉得恐怖,肯定是不行的,于是便想到用numberpicker,动态填充数据,一次只设置5个数据,当选中变化时,重新设置数据填充,所以关键在于填充的数据的计算。
设置数据部分逻辑代码:
/** * 更新左侧控件 * 日期:选择年控件 * 时间:选择月份和日期控件 * * @param timemillis */ private void updateleftvalue(long timemillis) { simpledateformat sdf; string str[] = new string[data_size]; if (mcurrenttype == type_pick_date) { sdf = new simpledateformat("yyyy"); for (int i = 0; i < data_size; i++) { calendar cal = calendar.getinstance(); cal.settimeinmillis(timemillis); cal.add(calendar.year, (i - data_size / 2)); str[i] = sdf.format(cal.gettimeinmillis()); } } else { sdf = new simpledateformat("mm-dd eee"); for (int i = 0; i < data_size; i++) { calendar cal = calendar.getinstance(); cal.settimeinmillis(timemillis); cal.add(calendar.day_of_month, (i - data_size / 2)); str[i] = sdf.format(cal.gettimeinmillis()); } } mnpleft.setdisplayedvalues(str); mnpleft.setvalue(data_size / 2); mnpleft.postinvalidate(); }
对滚轮的监听,并重新设置填充数据:
@override public void onvaluechange(numberpicker picker, int oldval, int newval) { calendar calendar = calendar.getinstance(); calendar.settimeinmillis(mtimemillis); int year = calendar.get(calendar.year); int month = calendar.get(calendar.month); int day = calendar.get(calendar.day_of_month); int hour = calendar.get(calendar.hour_of_day); int offset = newval - oldval; if (picker == mnpleft) { if (mcurrenttype == type_pick_date) { calendar.add(calendar.year, offset); } else { calendar.add(calendar.day_of_month, offset); } updateleftvalue(calendar.gettimeinmillis()); mtimemillis = calendar.gettimeinmillis(); } else if (picker == mnpmiddle) { if (mcurrenttype == type_pick_date) { calendar.add(calendar.month, offset); if (calendar.get(calendar.year) != year) { calendar.set(calendar.year, year); } } else { calendar.add(calendar.hour_of_day, offset); if (calendar.get(calendar.day_of_month) != day) { calendar.set(calendar.day_of_month, day); } if (calendar.get(calendar.month) != month) { calendar.set(calendar.month, month); } if (calendar.get(calendar.year) != year) { calendar.set(calendar.year, year); } } updatemiddlevalue(calendar.gettimeinmillis()); updaterightvalue(calendar.gettimeinmillis()); mtimemillis = calendar.gettimeinmillis(); } else if (picker == mnpright) { if (mcurrenttype == type_pick_date) { int days = getmaxdayofmonth(year, month + 1); if(day == 1 && offset < 0){ calendar.set(calendar.day_of_month,days); }else if(day == days && offset > 0){ calendar.set(calendar.day_of_month,1); }else{ calendar.add(calendar.day_of_month, offset); } if (calendar.get(calendar.month) != month) { calendar.set(calendar.month, month); } if (calendar.get(calendar.year) != year) { calendar.set(calendar.year, year); } log.e(tag,"time:::"+test.format(calendar.gettimeinmillis())); } else { calendar.add(calendar.minute, offset); if (calendar.get(calendar.hour_of_day) != hour) { calendar.set(calendar.hour_of_day, hour); } if (calendar.get(calendar.day_of_month) != day) { calendar.set(calendar.day_of_month, day); } if (calendar.get(calendar.month) != month) { calendar.set(calendar.month, month); } if (calendar.get(calendar.year) != year) { calendar.set(calendar.year, year); } } updaterightvalue(calendar.gettimeinmillis()); mtimemillis = calendar.gettimeinmillis(); } /** * 向外部发送当前选中时间 */ if (monselectedchangelistener != null) { monselectedchangelistener.onselected(this,mtimemillis); } log.e(tag, "selected time:" + test.format(mtimemillis)); }
选择数值和字符串
同样的,使用numberpicker进行封装,动态填充数值从而实现滚动变换的效果。
- 考虑到通用性,传入的是object类型的数组,在控件里进行判断。
- 可以选择一列数值、两列数值、三列数值,字符串同理。每一列数值可以设置它的单位、标题等,默认是隐藏,需要自己设置。
- 可以设置步长step
完整代码如下:
package com.example.moore.picktimeview.widget; import android.content.context; import android.graphics.color; import android.util.attributeset; import android.util.log; import android.view.gravity; import android.view.viewgroup; import android.widget.linearlayout; import android.widget.numberpicker; import android.widget.textview; /** * created by moore on 2016/10/21. */ public class pickvalueview extends linearlayout implements numberpicker.onvaluechangelistener { private context mcontext; /** * 组件 标题、单位、滚轮 */ private textview mtitleleft, mtitlemiddle, mtitleright; private textview munitleft, munitmiddle, munitright; private mynumberpicker mnpleft, mnpmiddle, mnpright; /** * 数据个数 1列 or 2列 or 3列 */ private int mviewcount = 1; /** * 一组数据长度 */ private final int data_size = 3; /** * 需要设置的值与默认值 */ private object[] mleftvalues; private object[] mmiddlevalues; private object[] mrightvalues; private object mdefaultleftvalue; private object mdefaultmiddlevalue; private object mdefaultrightvalue; /** * 当前正在显示的值 */ private object[] mshowingleft = new object[data_size]; private object[] mshowingmiddle = new object[data_size]; private object[] mshowingright = new object[data_size]; /** * 步长 */ private int mleftstep = 5; private int mmiddlestep = 1; private int mrightstep = 1; /** * 回调接口对象 */ private onselectedchangelistener mselectedchangelistener; public pickvalueview(context context) { super(context); this.mcontext = context; generateview(); } public pickvalueview(context context, attributeset attrs) { super(context, attrs); this.mcontext = context; generateview(); } public pickvalueview(context context, attributeset attrs, int defstyleattr) { super(context, attrs, defstyleattr); this.mcontext = context; generateview(); } /** * 生成视图 */ private void generateview() { //标题 linearlayout titlelayout = new linearlayout(mcontext); layoutparams titleparams = new layoutparams(viewgroup.layoutparams.match_parent, viewgroup.layoutparams.wrap_content); titleparams.setmargins(0, 0, 0, dip2px(12)); titlelayout.setlayoutparams(new layoutparams(viewgroup.layoutparams.match_parent, viewgroup.layoutparams.wrap_content)); titlelayout.setorientation(horizontal); mtitleleft = new textview(mcontext); mtitlemiddle = new textview(mcontext); mtitleright = new textview(mcontext); layoutparams params = new layoutparams(0, viewgroup.layoutparams.wrap_content, 1); textview[] titles = new textview[]{mtitleleft, mtitlemiddle, mtitleright}; for (int i = 0; i < titles.length; i++) { titles[i].setlayoutparams(params); titles[i].setgravity(gravity.center); titles[i].settextcolor(color.parsecolor("#3434ee")); } titlelayout.addview(mtitleleft); titlelayout.addview(mtitlemiddle); titlelayout.addview(mtitleright); //内容 linearlayout contentlayout = new linearlayout(mcontext); contentlayout.setlayoutparams(new layoutparams(viewgroup.layoutparams.match_parent, viewgroup.layoutparams.wrap_content)); contentlayout.setorientation(horizontal); contentlayout.setgravity(gravity.center); mnpleft = new mynumberpicker(mcontext); mnpmiddle = new mynumberpicker(mcontext); mnpright = new mynumberpicker(mcontext); munitleft = new textview(mcontext); munitmiddle = new textview(mcontext); munitright = new textview(mcontext); mynumberpicker[] nps = new mynumberpicker[]{mnpleft, mnpmiddle, mnpright}; for (int i = 0; i < nps.length; i++) { nps[i].setlayoutparams(params); nps[i].setdescendantfocusability(focus_block_descendants); nps[i].setonvaluechangedlistener(this); } contentlayout.addview(mnpleft); contentlayout.addview(munitleft); contentlayout.addview(mnpmiddle); contentlayout.addview(munitmiddle); contentlayout.addview(mnpright); contentlayout.addview(munitright); this.setlayoutparams(new layoutparams(viewgroup.layoutparams.match_parent, viewgroup.layoutparams.wrap_content)); this.setorientation(vertical); this.addview(titlelayout); this.addview(contentlayout); } /** * 初始化数据和值 */ private void initviewandpicker() { if (mviewcount == 1) { this.mnpmiddle.setvisibility(gone); this.mnpright.setvisibility(gone); this.munitmiddle.setvisibility(gone); this.munitright.setvisibility(gone); } else if (mviewcount == 2) { this.mnpright.setvisibility(gone); this.munitright.setvisibility(gone); } //初始化数组值 if (mleftvalues != null && mleftvalues.length != 0) { if (mleftvalues.length < data_size) { for (int i = 0; i < mleftvalues.length; i++) { mshowingleft[i] = mleftvalues[i]; } for (int i = mleftvalues.length; i < data_size; i++) { mshowingleft[i] = -9999; } } else { for (int i = 0; i < data_size; i++) { mshowingleft[i] = mleftvalues[i]; } } mnpleft.setminvalue(0); mnpleft.setmaxvalue(data_size - 1); if (mdefaultleftvalue != null) updateleftview(mdefaultleftvalue); else updateleftview(mshowingleft[0]); } /** * 中间控件 */ if (mviewcount == 2 || mviewcount == 3) { if (mmiddlevalues != null && mmiddlevalues.length != 0) { if (mmiddlevalues.length < data_size) { for (int i = 0; i < mmiddlevalues.length; i++) { mshowingmiddle[i] = mmiddlevalues[i]; } for (int i = mmiddlevalues.length; i < data_size; i++) { mshowingmiddle[i] = -9999; } } else { for (int i = 0; i < data_size; i++) { mshowingmiddle[i] = mmiddlevalues[i]; } } mnpmiddle.setminvalue(0); mnpmiddle.setmaxvalue(data_size - 1); if (mdefaultmiddlevalue != null) updatemiddleview(mdefaultmiddlevalue); else updatemiddleview(mshowingmiddle[0]); } } /** * 右侧控件 */ if (mviewcount == 3) { if (mrightvalues != null && mrightvalues.length != 0) { if (mrightvalues.length < data_size) { for (int i = 0; i < mrightvalues.length; i++) { mshowingright[i] = mrightvalues[i]; } for (int i = mrightvalues.length; i < data_size; i++) { mshowingright[i] = -9999; } } else { for (int i = 0; i < data_size; i++) { mshowingright[i] = mrightvalues[i]; } } mnpright.setminvalue(0); mnpright.setmaxvalue(data_size - 1); if (mdefaultrightvalue != null) updaterightview(mdefaultrightvalue); else updaterightview(mshowingright[0]); } } } private void updateleftview(object value) { updatevalue(value, 0); } private void updatemiddleview(object value) { updatevalue(value, 1); } private void updaterightview(object value) { updatevalue(value, 2); } /** * 更新滚轮视图 * * @param value * @param index */ private void updatevalue(object value, int index) { string showstr[] = new string[data_size]; mynumberpicker picker; object[] showingvalue; object[] values; int step; if (index == 0) { picker = mnpleft; showingvalue = mshowingleft; values = mleftvalues; step = mleftstep; } else if (index == 1) { picker = mnpmiddle; showingvalue = mshowingmiddle; values = mmiddlevalues; step = mmiddlestep; } else { picker = mnpright; showingvalue = mshowingright; values = mrightvalues; step = mrightstep; } if (values instanceof integer[]) { for (int i = 0; i < data_size; i++) { showingvalue[i] = (int) value - step * (data_size / 2 - i); int offset = (int) values[values.length - 1] - (int) values[0] + step; if ((int) showingvalue[i] < (int) values[0]) { showingvalue[i] = (int) showingvalue[i] + offset; } if ((int) showingvalue[i] > (int) values[values.length - 1]) { showingvalue[i] = (int) showingvalue[i] - offset; } showstr[i] = "" + showingvalue[i]; } } else { int strindex = 0; for (int i = 0; i < values.length; i++) { if (values[i].equals(value)) { strindex = i; break; } } for (int i = 0; i < data_size; i++) { int temp = strindex - (data_size / 2 - i); if (temp < 0) { temp += values.length; } if (temp >= values.length) { temp -= values.length; } showingvalue[i] = values[temp]; showstr[i] = (string) values[temp]; } } picker.setdisplayedvalues(showstr); picker.setvalue(data_size / 2); picker.postinvalidate(); } @override public void onvaluechange(numberpicker picker, int oldval, int newval) { if (picker == mnpleft) { updateleftview(mshowingleft[newval]); } else if (picker == mnpmiddle) { updatemiddleview(mshowingmiddle[newval]); } else if (picker == mnpright) { updaterightview(mshowingright[newval]); } if (mselectedchangelistener != null) { mselectedchangelistener.onselected(this, mshowingleft[data_size / 2], mshowingmiddle[data_size / 2], mshowingright[data_size / 2]); } } /** * 设置数据--单列数据 * * @param leftvalues * @param mdefaultleftvalue */ public void setvaluedata(object[] leftvalues, object mdefaultleftvalue) { this.mviewcount = 1; this.mleftvalues = leftvalues; this.mdefaultleftvalue = mdefaultleftvalue; initviewandpicker(); } /** * 设置数据--两列数据 * * @param leftvalues * @param mdefaultleftvalue * @param middlevalues * @param defaultmiddlevalue */ public void setvaluedata(object[] leftvalues, object mdefaultleftvalue, object[] middlevalues, object defaultmiddlevalue) { this.mviewcount = 2; this.mleftvalues = leftvalues; this.mdefaultleftvalue = mdefaultleftvalue; this.mmiddlevalues = middlevalues; this.mdefaultmiddlevalue = defaultmiddlevalue; initviewandpicker(); } /** * 设置数据--三列数据 * * @param leftvalues * @param mdefaultleftvalue * @param middlevalues * @param defaultmiddlevalue * @param rightvalues * @param defaultrightvalue */ public void setvaluedata(object[] leftvalues, object mdefaultleftvalue, object[] middlevalues, object defaultmiddlevalue, object[] rightvalues, object defaultrightvalue) { this.mviewcount = 3; this.mleftvalues = leftvalues; this.mdefaultleftvalue = mdefaultleftvalue; this.mmiddlevalues = middlevalues; this.mdefaultmiddlevalue = defaultmiddlevalue; this.mrightvalues = rightvalues; this.mdefaultrightvalue = defaultrightvalue; initviewandpicker(); } /** * 设置左边数据步长 * * @param step */ public void setleftstep(int step) { this.mleftstep = step; initviewandpicker(); } /** * 设置中间数据步长 * * @param step */ public void setmiddlestep(int step) { this.mmiddlestep = step; initviewandpicker(); } /** * 设置右边数据步长 * * @param step */ public void setrightstep(int step) { this.mrightstep = step; initviewandpicker(); } /** * 设置标题 * * @param left * @param middle * @param right */ public void settitle(string left, string middle, string right) { if (left != null) { mtitleleft.setvisibility(visible); mtitleleft.settext(left); } else { mtitleleft.setvisibility(gone); } if (middle != null) { mtitlemiddle.setvisibility(visible); mtitlemiddle.settext(middle); } else { mtitlemiddle.setvisibility(gone); } if (right != null) { mtitleright.setvisibility(visible); mtitleright.settext(right); } else { mtitleright.setvisibility(gone); } this.postinvalidate(); } public void setunitleft(string unitleft) { setunit(unitleft, 0); } public void setmunitmiddle(string unitmiddle) { setunit(unitmiddle, 1); } public void setunitright(string unitright) { setunit(unitright, 2); } private void setunit(string unit, int index) { textview tvunit; if (index == 0) { tvunit = munitleft; } else if (index == 1) { tvunit = munitmiddle; } else { tvunit = munitright; } if (unit != null) { tvunit.settext(unit); } else { tvunit.settext(" "); } initviewandpicker(); } /** * 设置回调 * * @param listener */ public void setonselectedchangelistener(onselectedchangelistener listener) { this.mselectedchangelistener = listener; } /** * dp转px * * @param dp * @return */ private int dip2px(int dp) { float scale = mcontext.getresources().getdisplaymetrics().density; return (int) (scale * dp + 0.5f); } /** * 回调接口 */ public interface onselectedchangelistener { void onselected(pickvalueview view, object leftvalue, object middlevalue, object rightvalue); } }
关于numberpicker
默认的numberpicker往往字体颜色、分割线颜色等都是跟随系统,不能改变,考虑到可能比较丑或者有其他需求,所以自定义的numberpicker,通过反射的方式更改里面的一些属性,代码如下:
package com.example.moore.picktimeview.widget; import android.content.context; import android.graphics.color; import android.graphics.drawable.colordrawable; import android.util.attributeset; import android.util.log; import android.view.view; import android.view.viewgroup; import android.widget.edittext; import android.widget.imagebutton; import android.widget.numberpicker; import java.lang.reflect.field; /** * created by moore on 2016/10/20. */ public class mynumberpicker extends numberpicker { private static int mtextsize = 16; private static int mtextcolor = 0x000000; private static int mdividercolor = 0xffff00; public mynumberpicker(context context) { super(context); setnumberpickerdividercolor(); } public mynumberpicker(context context, attributeset attrs) { super(context, attrs); setnumberpickerdividercolor(); } public mynumberpicker(context context, attributeset attrs, int defstyleattr) { super(context, attrs, defstyleattr); setnumberpickerdividercolor(); } @override public void addview(view child) { super.addview(child); updateview(child); } @override public void addview(view child, int index, viewgroup.layoutparams params) { super.addview(child, index, params); updateview(child); } @override public void addview(view child, viewgroup.layoutparams params) { super.addview(child, params); updateview(child); } private void updateview(view view) { if (view instanceof edittext) { // ((edittext) view).settextsize(mtextsize); ((edittext) view).settextsize(17); // ((edittext) view).settextcolor(mtextcolor); ((edittext) view).settextcolor(color.parsecolor("#6495ed")); } } private void setnumberpickerdividercolor() { field[] pickerfields = numberpicker.class.getdeclaredfields(); /** * 设置分割线颜色 */ for (field pf : pickerfields) { if (pf.getname().equals("mselectiondivider")) { pf.setaccessible(true); try { // pf.set(this, new colordrawable(mdividercolor)); pf.set(this, new colordrawable(color.parsecolor("#c4c4c4"))); } catch (illegalaccessexception e) { e.printstacktrace(); } break; } } /** * 设置分割线高度 */ for (field pf : pickerfields) { if (pf.getname().equals("mselectiondividerheight")) { pf.setaccessible(true); try { pf.set(this, 2); } catch (illegalaccessexception e) { e.printstacktrace(); } break; } } for (field pf : pickerfields) { if (pf.getname().equals("mselectorelementheight")) { pf.setaccessible(true); try { pf.set(this, 2); } catch (illegalaccessexception e) { e.printstacktrace(); } break; } } } public void setdividercolor(int color) { this.mdividercolor = color; // this.postinvalidate(); } public void settextcolor(int color) { this.mtextcolor = color; // this.postinvalidate(); } public void settextsize(int textsize) { this.mtextsize = textsize; // this.postinvalidate(); } }
完整demo可前往github查看与下载,地址:https://github.com/lizebinbin/picktimeview.git 谢谢!
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
上一篇: java实现区域内屏幕截图示例