Android PickerView滚动选择器的使用方法
手机里设置闹钟需要选择时间,那个选择时间的控件就是滚动选择器,前几天用手机刷了miui,发现自带的那个时间选择器效果挺好看的,于是就自己仿写了一个,权当练手。先来看效果:
效果还行吧?实现思路就是自定义一个pickerview,单独滚动的是一个pickerview,显然上图中有分和秒的选择所以在布局里用了两个pickerview。由于这里不涉及到text的点击事件,所以只需要继承view就行了,直接把text用canvas画上去。pickerview的实现的主要难点:
难点1:
字体随距离的渐变。可以看到,text随离中心位置的距离变化而变化,这里变化的是透明度alpha和字体大小texsize,这两个值我都设置了max和min值,通过其与中心点的距离计算scale。我用的是变化曲线是抛物线scale=1-ax^2(x<=height/4),scale = 0(x>height/4),a=(4/height)^2。x就是距离view中心的偏移量。用图片表示如下:
难点2:
text的居中。绘制text的时候不仅要使其在x方向上居中,还要在y方向上居中,在x方向上比较简单,设置paint的align为align.center就行了,但是y方向上很蛋疼,需要计算text的baseline。
难点3:
循环滚动。为了解决循环滚动的问题我把存放text的list从中间往上下摊开,通过不断地moveheadtotail和movetailtohead使选中的text始终是list的中间position的值。
以上就是几个难点,了解了之后可以来看pickerview的代码了:
package com.jingchen.timerpicker; import java.util.arraylist; import java.util.list; import java.util.timer; import java.util.timertask; import android.content.context; import android.graphics.canvas; import android.graphics.paint; import android.graphics.paint.align; import android.graphics.paint.fontmetricsint; import android.graphics.paint.style; import android.os.handler; import android.os.message; import android.util.attributeset; import android.view.motionevent; import android.view.view; /** * 滚动选择器 * * @author chenjing * */ public class pickerview extends view { public static final string tag = "pickerview"; /** * text之间间距和mintextsize之比 */ public static final float margin_alpha = 2.8f; /** * 自动回滚到中间的速度 */ public static final float speed = 2; private list<string> mdatalist; /** * 选中的位置,这个位置是mdatalist的中心位置,一直不变 */ private int mcurrentselected; private paint mpaint; private float mmaxtextsize = 80; private float mmintextsize = 40; private float mmaxtextalpha = 255; private float mmintextalpha = 120; private int mcolortext = 0x333333; private int mviewheight; private int mviewwidth; private float mlastdowny; /** * 滑动的距离 */ private float mmovelen = 0; private boolean isinit = false; private onselectlistener mselectlistener; private timer timer; private mytimertask mtask; handler updatehandler = new handler() { @override public void handlemessage(message msg) { if (math.abs(mmovelen) < speed) { mmovelen = 0; if (mtask != null) { mtask.cancel(); mtask = null; performselect(); } } else // 这里mmovelen / math.abs(mmovelen)是为了保有mmovelen的正负号,以实现上滚或下滚 mmovelen = mmovelen - mmovelen / math.abs(mmovelen) * speed; invalidate(); } }; public pickerview(context context) { super(context); init(); } public pickerview(context context, attributeset attrs) { super(context, attrs); init(); } public void setonselectlistener(onselectlistener listener) { mselectlistener = listener; } private void performselect() { if (mselectlistener != null) mselectlistener.onselect(mdatalist.get(mcurrentselected)); } public void setdata(list<string> datas) { mdatalist = datas; mcurrentselected = datas.size() / 2; invalidate(); } public void setselected(int selected) { mcurrentselected = selected; } private void moveheadtotail() { string head = mdatalist.get(0); mdatalist.remove(0); mdatalist.add(head); } private void movetailtohead() { string tail = mdatalist.get(mdatalist.size() - 1); mdatalist.remove(mdatalist.size() - 1); mdatalist.add(0, tail); } @override protected void onmeasure(int widthmeasurespec, int heightmeasurespec) { super.onmeasure(widthmeasurespec, heightmeasurespec); mviewheight = getmeasuredheight(); mviewwidth = getmeasuredwidth(); // 按照view的高度计算字体大小 mmaxtextsize = mviewheight / 4.0f; mmintextsize = mmaxtextsize / 2f; isinit = true; invalidate(); } private void init() { timer = new timer(); mdatalist = new arraylist<string>(); mpaint = new paint(paint.anti_alias_flag); mpaint.setstyle(style.fill); mpaint.settextalign(align.center); mpaint.setcolor(mcolortext); } @override protected void ondraw(canvas canvas) { super.ondraw(canvas); // 根据index绘制view if (isinit) drawdata(canvas); } private void drawdata(canvas canvas) { // 先绘制选中的text再往上往下绘制其余的text float scale = parabola(mviewheight / 4.0f, mmovelen); float size = (mmaxtextsize - mmintextsize) * scale + mmintextsize; mpaint.settextsize(size); mpaint.setalpha((int) ((mmaxtextalpha - mmintextalpha) * scale + mmintextalpha)); // text居中绘制,注意baseline的计算才能达到居中,y值是text中心坐标 float x = (float) (mviewwidth / 2.0); float y = (float) (mviewheight / 2.0 + mmovelen); fontmetricsint fmi = mpaint.getfontmetricsint(); float baseline = (float) (y - (fmi.bottom / 2.0 + fmi.top / 2.0)); canvas.drawtext(mdatalist.get(mcurrentselected), x, baseline, mpaint); // 绘制上方data for (int i = 1; (mcurrentselected - i) >= 0; i++) { drawothertext(canvas, i, -1); } // 绘制下方data for (int i = 1; (mcurrentselected + i) < mdatalist.size(); i++) { drawothertext(canvas, i, 1); } } /** * @param canvas * @param position * 距离mcurrentselected的差值 * @param type * 1表示向下绘制,-1表示向上绘制 */ private void drawothertext(canvas canvas, int position, int type) { float d = (float) (margin_alpha * mmintextsize * position + type * mmovelen); float scale = parabola(mviewheight / 4.0f, d); float size = (mmaxtextsize - mmintextsize) * scale + mmintextsize; mpaint.settextsize(size); mpaint.setalpha((int) ((mmaxtextalpha - mmintextalpha) * scale + mmintextalpha)); float y = (float) (mviewheight / 2.0 + type * d); fontmetricsint fmi = mpaint.getfontmetricsint(); float baseline = (float) (y - (fmi.bottom / 2.0 + fmi.top / 2.0)); canvas.drawtext(mdatalist.get(mcurrentselected + type * position), (float) (mviewwidth / 2.0), baseline, mpaint); } /** * 抛物线 * * @param zero * 零点坐标 * @param x * 偏移量 * @return scale */ private float parabola(float zero, float x) { float f = (float) (1 - math.pow(x / zero, 2)); return f < 0 ? 0 : f; } @override public boolean ontouchevent(motionevent event) { switch (event.getactionmasked()) { case motionevent.action_down: dodown(event); break; case motionevent.action_move: domove(event); break; case motionevent.action_up: doup(event); break; } return true; } private void dodown(motionevent event) { if (mtask != null) { mtask.cancel(); mtask = null; } mlastdowny = event.gety(); } private void domove(motionevent event) { mmovelen += (event.gety() - mlastdowny); if (mmovelen > margin_alpha * mmintextsize / 2) { // 往下滑超过离开距离 movetailtohead(); mmovelen = mmovelen - margin_alpha * mmintextsize; } else if (mmovelen < -margin_alpha * mmintextsize / 2) { // 往上滑超过离开距离 moveheadtotail(); mmovelen = mmovelen + margin_alpha * mmintextsize; } mlastdowny = event.gety(); invalidate(); } private void doup(motionevent event) { // 抬起手后mcurrentselected的位置由当前位置move到中间选中位置 if (math.abs(mmovelen) < 0.0001) { mmovelen = 0; return; } if (mtask != null) { mtask.cancel(); mtask = null; } mtask = new mytimertask(updatehandler); timer.schedule(mtask, 0, 10); } class mytimertask extends timertask { handler handler; public mytimertask(handler handler) { this.handler = handler; } @override public void run() { handler.sendmessage(handler.obtainmessage()); } } public interface onselectlistener { void onselect(string text); } }
代码里的注释都写的很清楚了。接下来,我们就用写好的pickerview实现文章开头的图片效果吧~
首先看mainactivity的布局:
<relativelayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#000000" > <relativelayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerinparent="true" android:background="#ffffff" > <com.jingchen.timerpicker.pickerview android:id="@+id/minute_pv" android:layout_width="80dp" android:layout_height="160dp" /> <textview android:id="@+id/minute_tv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centervertical="true" android:layout_torightof="@id/minute_pv" android:text="分" android:textcolor="#ffaa33" android:textsize="26sp" android:textstyle="bold" /> <com.jingchen.timerpicker.pickerview android:id="@+id/second_pv" android:layout_width="80dp" android:layout_height="160dp" android:layout_torightof="@id/minute_tv" /> <textview android:id="@+id/second_tv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centervertical="true" android:layout_torightof="@id/second_pv" android:text="秒" android:textcolor="#ffaa33" android:textsize="26sp" android:textstyle="bold" /> </relativelayout> </relativelayout>
两个pickerview两个textview,很简单。
下面是mainactivity的代码:
package com.jingchen.timerpicker; import java.util.arraylist; import java.util.list; import com.jingchen.timerpicker.pickerview.onselectlistener; import android.app.activity; import android.os.bundle; import android.view.menu; import android.widget.textview; import android.widget.toast; public class mainactivity extends activity { pickerview minute_pv; pickerview second_pv; @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); minute_pv = (pickerview) findviewbyid(r.id.minute_pv); second_pv = (pickerview) findviewbyid(r.id.second_pv); list<string> data = new arraylist<string>(); list<string> seconds = new arraylist<string>(); for (int i = 0; i < 10; i++) { data.add("0" + i); } for (int i = 0; i < 60; i++) { seconds.add(i < 10 ? "0" + i : "" + i); } minute_pv.setdata(data); minute_pv.setonselectlistener(new onselectlistener() { @override public void onselect(string text) { toast.maketext(mainactivity.this, "选择了 " + text + " 分", toast.length_short).show(); } }); second_pv.setdata(seconds); second_pv.setonselectlistener(new onselectlistener() { @override public void onselect(string text) { toast.maketext(mainactivity.this, "选择了 " + text + " 秒", toast.length_short).show(); } }); } @override public boolean oncreateoptionsmenu(menu menu) { getmenuinflater().inflate(r.menu.main, menu); return true; } }
ok了,自定义自己的timerpicker就是这么简单
源码下载:pickerview滚动选择器
希望本文对大家学习滚动选择器pickerview有所帮助。
上一篇: spring cloud gateway 全局过滤器的实现
下一篇: iOS - 获取通讯录