Android自定义收音机搜台控件RadioRulerView
程序员文章站
2023-10-26 20:44:34
前言:像这类的自定义控件有非常多的开源项目,但还是没有找到我项目想要的,所以简单实现了一个,下面简单讲讲实现原理。
效果图:
实现思路:
首先画固定背景尺子,而实...
前言:像这类的自定义控件有非常多的开源项目,但还是没有找到我项目想要的,所以简单实现了一个,下面简单讲讲实现原理。
效果图:
实现思路:
首先画固定背景尺子,而实现这个则要计算刻度线的宽度、刻度线间的距离,以及要确定刻度线的总是,根据这些可以求出第一条刻度线的x坐标,使得整个尺子居中;下图为尺子尺寸的计算方法:
贴上关键代码:
/** * 画固定的尺子 * @param canvas */ private void drawline(canvas canvas) { canvas.save(); int height = mheight; int drawcount = 0;//已经画了刻度线的个数 float xposition; for(int i=0; drawcount<=mmaxlinecount; i++){ xposition = (mlinedivider*mdensity + mlinewidth)*drawcount + mleftwidth; if(i%5 == 0 && i%10 != 0){//刻度为5的倍数,但同时不是10的倍数 canvas.drawline(xposition,height*0.85f-mpaddingbottom,xposition,height*0.15f+mpaddingtop,mlinepaint); }else if(i%10 == 0){//刻度为10的倍数 canvas.drawline(xposition,height-mpaddingbottom,xposition,mpaddingtop,mlinepaint); }else {//普通的刻度 canvas.drawline(xposition,height*0.75f-mpaddingbottom,xposition,height*0.25f+mpaddingtop,mlinepaint); } drawcount++; } canvas.restore(); }
然后画出可以拖动的刻度线(首图粉红色线),要实现该功能其实不难,第一种情况:通过在ontouch里面获取event.getx()坐标,而在这其中用到pointf类来保存x坐标,然后根据x坐标在ondraw()里面绘制即可;第二种情况:自动搜台,这其实很简单,开启子线程每thread.sleep(200)就累加一定x值即可实现;
最后通过回调把计算好的值传递到activity中,任务完成!
要是不太清楚回调原理的可看我另外一篇博客:android回调与观察者模式的实现原理
下面贴上view的源码:
package com.xhunmon.radiorule; import android.content.context; import android.content.res.typedarray; import android.graphics.canvas; import android.graphics.paint; import android.graphics.pointf; import android.util.attributeset; import android.util.sparsearray; import android.view.motionevent; import android.view.view; /** * user: uidq0530 ,date: 2017-04-16. * description:收音机fm搜台尺子 * * @author xhunmon */ public class radiorulerview extends view { private static final string tag = "radiorulerview"; private int mheight; //view的高度 private int mwidth; //view的宽度 private paint mlinepaint; //固定的尺子画笔 private int mlinewidth;//尺子刻度线的宽 private int mlinecolor;//固定尺子刻度线的颜色 private int mmovelinecolor;//移动尺子刻度线的颜色 private float mdensity; private int mlinedivider; //两条刻度线间的距离 private float mleftwidth; //尺子离view左边的距离 private int mmaxlinecount = 220; //总共要画多少条刻度 private paint mmovelinepaint; //移动尺子的画笔 private int mvalue; //尺子被选中的值 private float mmaxx; //ontouch中能触摸的最大x值 private float mminx; //ontouch中能触摸的最小x值 private onvaluechangelistener mlistener; private sparsearray<pointf> activepointers; private pointf xpoint; private int mpaddingbottom; private int mpaddingtop; private boolean misauto = false; public radiorulerview(context context) { this(context,null); } public radiorulerview(context context, attributeset attrs) { this(context, attrs,0); } public radiorulerview(context context, attributeset attrs, int defstyleattr) { this(context, attrs, defstyleattr,0); } public radiorulerview(context context, attributeset attrs, int defstyleattr, int defstyleres) { super(context, attrs, defstyleattr, defstyleres); mdensity = context.getresources().getdisplaymetrics().density; typedarray a = context.obtainstyledattributes(attrs, r.styleable.radiorulerview, defstyleattr, defstyleres); mlinewidth = (int) a.getdimension(r.styleable.radiorulerview_line_width,5*mdensity); mlinedivider = (int) a.getdimension(r.styleable.radiorulerview_line_divider,15*mdensity); mlinecolor = a.getcolor(r.styleable.radiorulerview_line_color,0xff888888); mmovelinecolor = a.getcolor(r.styleable.radiorulerview_move_line_color,0xffff0000); a.recycle(); init(); } private void init() { activepointers = new sparsearray<>(); mlinepaint = new paint(); mlinepaint.setantialias(true); mlinepaint.setcolor(mlinecolor); mlinepaint.setstrokewidth(mlinewidth); mlinepaint.setstyle(paint.style.stroke); mmovelinepaint = new paint(); mmovelinepaint.setantialias(true); mmovelinepaint.setcolor(mmovelinecolor); mmovelinepaint.setstrokewidth(mlinewidth); mmovelinepaint.setstyle(paint.style.stroke); } //此方法在view的尺寸确定后调用 @override protected void onsizechanged(int w, int h, int oldw, int oldh) { super.onsizechanged(w, h, oldw, oldh); mheight = getheight(); mwidth = getwidth(); mpaddingbottom = getpaddingbottom(); mpaddingtop = getpaddingtop(); mleftwidth = (mwidth - mmaxlinecount*(mlinewidth +mlinedivider))/2; mmaxx = mmaxlinecount*(mlinewidth +mlinedivider) + mleftwidth; mminx = mleftwidth; } @override protected void ondraw(canvas canvas) { super.ondraw(canvas); drawline(canvas); drawmoveline(canvas); } /** * 画固定的尺子 * @param canvas */ private void drawline(canvas canvas) { canvas.save(); int height = mheight; int drawcount = 0;//已经画了刻度线的个数 float xposition; for(int i=0; drawcount<=mmaxlinecount; i++){ xposition = (mlinedivider*mdensity + mlinewidth)*drawcount + mleftwidth; if(i%5 == 0 && i%10 != 0){//刻度为5的倍数,但同时不是10的倍数 canvas.drawline(xposition,height*0.85f-mpaddingbottom,xposition,height*0.15f+mpaddingtop,mlinepaint); }else if(i%10 == 0){//刻度为10的倍数 canvas.drawline(xposition,height-mpaddingbottom,xposition,mpaddingtop,mlinepaint); }else {//普通的刻度 canvas.drawline(xposition,height*0.75f-mpaddingbottom,xposition,height*0.25f+mpaddingtop,mlinepaint); } drawcount++; } canvas.restore(); } /** * 搜索fm频道的刻度线 * @param canvas */ private void drawmoveline(canvas canvas) { canvas.save(); xpoint = activepointers.valueat(0); if (xpoint != null) { canvas.drawline(xpoint.x,mheight-mpaddingbottom, xpoint.x,mpaddingtop,mmovelinepaint); setvalue(eventxvalue(xpoint.x)); }else { canvas.drawline(mminx,mheight-mpaddingbottom, mminx,mpaddingtop,mmovelinepaint); } canvas.restore(); } @override public boolean ontouchevent(motionevent event) { int pointerindex = event.getactionindex(); int pointerid = event.getpointerid(pointerindex); switch (event.getactionmasked()) { case motionevent.action_down: case motionevent.action_pointer_down: { float downx = event.getx(pointerindex); if(downx > mmaxx || downx < mminx) break; pointf position = new pointf(downx, event.gety(pointerindex)); activepointers.put(pointerid, position); break; } case motionevent.action_move: { int pointercount = event.getpointercount(); for (int i = 0; i < pointercount; i++) { pointf point = activepointers.get(event.getpointerid(i)); if (point == null) continue; float movex = event.getx(i); if(movex > mmaxx || movex < mminx) break; point.x = event.getx(i); point.y = event.gety(i); } break; } case motionevent.action_up: case motionevent.action_pointer_up: case motionevent.action_cancel: { int pointercount = event.getpointercount(); pointf point = activepointers.get(event.getpointerid(pointercount-1)); if (point == null) break; float upx = event.getx(pointercount-1); if(upx > mmaxx || upx < mminx) break; point.x = eventxvalue(event.getx(pointercount-1)); point.y = event.gety(pointercount-1); break; } } invalidate(); return true; } /** *作用:使得放手后moveline和line重合;精确mvalue * @param x ontouch中的event.getx() * @return */ public int eventxvalue(float x){ mlinedivider = (int) (mlinedivider*mdensity); return (int) ((x-mleftwidth)%(mlinewidth +mlinedivider)>((mlinewidth +mlinedivider)/2) ? (((mlinewidth +mlinedivider)*((int)((x-mleftwidth)/(mlinewidth +mlinedivider))+1))+mleftwidth) : (((mlinewidth +mlinedivider)*((int)((x-mleftwidth)/(mlinewidth +mlinedivider))))+mleftwidth)); } /** * 设置最大刻度线个数 * @param count */ public void setmaxlinecount(int count) { mmaxlinecount = count; } /** * 设置是否启用自动搜索功能 * @param isauto */ public void setautosearchfm(boolean isauto){ this.misauto = isauto; } /** * 开始自动搜台 */ public void startautoseachfm(){ if(misauto) new thread(new seachthread()).start(); } /** * 搜台要在开启子线程 */ private class seachthread implements runnable{ @override public void run() { while(misauto){ xpoint = activepointers.valueat(0); if(xpoint != null){ xpoint.x += (mlinewidth + mlinedivider); if(xpoint.x > mmaxx) xpoint.x = mleftwidth; }else { pointf position = new pointf(mleftwidth, mheight); activepointers.put(0, position); } try { thread.sleep(200); } catch (interruptedexception e) { e.printstacktrace(); } postinvalidate(); } } } /*****************************值传递的回调*************************************/ public interface onvaluechangelistener { void onvaluechange(float value); } public void setonvaluechangelistener(onvaluechangelistener listener){ mlistener = listener; } private void setvalue(float value) { if(mlistener != null){ mvalue = (int) ((value - mleftwidth)/(mlinedivider*mdensity + mlinewidth)); //fm的范围从88.0 ~ 108.0 mlistener.onvaluechange(mvalue/10f + 88); } } /******************************************************************/ }
贴上activity代码:
package com.xhunmon.radiorule; import android.app.activity; import android.os.bundle; import android.view.view; import android.widget.button; import android.widget.checkbox; import android.widget.compoundbutton; import android.widget.textview; public class mainactivity extends activity implements radiorulerview.onvaluechangelistener, compoundbutton.oncheckedchangelistener, view.onclicklistener { private textview mshow; private radiorulerview mrule; private checkbox mcbauto; private button mbtstart; @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); mshow = (textview) findviewbyid(r.id.tv); mrule = (radiorulerview) findviewbyid(r.id.rule); mcbauto = (checkbox) findviewbyid(r.id.cb_auto); mbtstart = (button) findviewbyid(r.id.bt_start); mrule.setmaxlinecount(200);//fm从88.0 ~ 108.0总共有200频道 mrule.setonvaluechangelistener(this); mcbauto.setoncheckedchangelistener(this); mbtstart.setonclicklistener(this); } @override public void onvaluechange(float value) { mshow.settext("fm:"+value); } @override public void oncheckedchanged(compoundbutton buttonview, boolean ischecked) { if(ischecked){ mrule.setautosearchfm(true); }else { mrule.setautosearchfm(false); } } @override public void onclick(view v) { if(v.getid() == r.id.bt_start){ mrule.startautoseachfm(); } } }
整个项目都放在github上面了,欢迎做客与讨论:
https://github.com/xhunmon/radiorule
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
上一篇: 关于js原型的面试题讲解
下一篇: 推荐几道好吃的清淡的中餐,一起来看看吧