欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

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

程序员文章站 2023-10-26 20:44:34
前言:像这类的自定义控件有非常多的开源项目,但还是没有找到我项目想要的,所以简单实现了一个,下面简单讲讲实现原理。 效果图: 实现思路: 首先画固定背景尺子,而实...

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

效果图:

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

实现思路:

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

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

贴上关键代码:

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

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