当前位置: 移动技术网 > 移动技术>移动开发>Android > Android自定义收音机搜台控件RadioRulerView

Android自定义收音机搜台控件RadioRulerView

2019年07月24日  | 移动技术网移动技术  | 我要评论

前言:像这类的自定义控件有非常多的开源项目,但还是没有找到我项目想要的,所以简单实现了一个,下面简单讲讲实现原理。

效果图:

image

实现思路:

首先画固定背景尺子,而实现这个则要计算刻度线的宽度、刻度线间的距离,以及要确定刻度线的总是,根据这些可以求出第一条刻度线的x坐标,使得整个尺子居中;下图为尺子尺寸的计算方法:

image

贴上关键代码:

 /**
 * 画固定的尺子
 * @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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持移动技术网。

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网