Android怎么自定义View实现柱状波形图(android,view,开发技术)

时间:2024-04-28 21:59:40 作者 : 石家庄SEO 分类 : 开发技术
  • TAG :

希望大家仔细阅读,能够学有所成!

前言

柱状波形图是一种常见的图形。一个个柱子按顺序排列,构成一个波形图。

柱子的高度由输入数据决定。如果输入的是音频的音量,则可得到一个声波图。

Android怎么自定义View实现柱状波形图

在一些音频软件中,我们也可以左右拖动声波,来改变音频的播放进度

本文举例的自定View,实现如下功能:

  • 以柱状形式展示数据的大小

  • 标明图形当前最中间的数据

  • 可以横向拖动进度,进度就是让某个特定的数据居中展示

  • 可以改变左右两边的柱子颜色

  • 可以调整柱子的宽度

  • 拖动完毕后监听当前进度

实现

首先创建类SoundWaveView继承自View

我们可以先记录给定的宽高,方便后面找到View的中间点

privateintviewWid=1000;//px
privateintviewHeight=100;//px

@Override
protectedvoidonSizeChanged(intw,inth,intoldw,intoldh){
super.onSizeChanged(w,h,oldw,oldh);
viewWid=w;
viewHeight=h;
//..
}

基本属性

例如柱子的颜色,宽度。可以设置个属性来记录,并开放出去可由外部来设置。

privatefloatbarWidDp=1.5f;
privatefloatbarWidPx=3f;
privatefloatbarGapPx=barWidPx/2;
privateintbarCount=1;//当前宽度能绘制多少个柱子

privatefinalPaintpaint=newPaint();
privateintleftColor=Color.GREEN;
privateintrightColor=Color.LTGRAY;
privateintmiddleLineColor=Color.parseColor("#55000000");

设计监听器

拖动完毕后,可以将当前进度通知出去。也可以直接把触摸事件传出去。

publicinterfaceOnEvent{
voidonMoveEnd();//停止拖动了

voidonDragTouchEvent(MotionEventevent);
}

privateOnEventonEventListener;

privatevoidtellOnMoveEnd(){
if(onEventListener!=null){
onEventListener.onMoveEnd();
}
}

绘制图形

onDraw方法中根据数据绘制图形

本例没有设计背景,直接绘制数据。

图形需求之一是要求某个数据能居中显示,我们用midIndex来标记这个数据的下标。

比较简单粗暴的实现方法,遍历整个数据列表,计算出每个数据的x坐标。超出范围的不绘制,范围内的逐一绘制。

@Override
protectedvoidonDraw(Canvascanvas){
super.onDraw(canvas);
if(dataList==null||dataList.isEmpty()){
//drawnothing
drawMiddleLine(canvas);
return;
}
floatx0=viewWid/2.0f;

if(midIndex>0){
x0=x0-(barGapPx+barWidPx)midIndex;//可能是负数
}
for(inti=0;i<dataList.size();i++){
floatd=dataList.get(i);
floatx=x0+(barWidPx+barGapPx)
i;
if(x<0){
continue;
}
if(x>viewWid){
break;
}
if(i<=midIndex){
paint.setColor(leftColor);
}else{
paint.setColor(rightColor);
}
paint.setStrokeWidth(barWidPx);
floatbh=(d/showMaxData)*viewHeight;
bh=Math.max(bh,4);//最小也要一点高度(1)
floatbhGap=(viewHeight-bh)/2f;
canvas.drawLine(x,bhGap,x,viewHeight-bhGap,paint);
}

drawMiddleLine(canvas);
}

privatevoiddrawMiddleLine(Canvascanvas){
paint.setColor(middleLineColor);
canvas.drawLine(viewWid/2f,0,viewWid/2f,viewHeight,paint);
}

如果数据太小,为了更美观,也要显示一点东西

左右拖动

本例给出的思路是在SoundWaveView中直接获取触摸事件并进行处理。

简单区分一下模式,分为纯展示和可拖动模式

/*
单纯播放展示无交互
*/
publicstaticfinalintMODE_PLAY=1;

/*
允许左右拖动
/
publicstaticfinalintMODE_CAN_DRAG=2;

复写onTouchEvent方法,如果是MODE_CAN_DRAG模式,则拦截触摸事件。判断拖动的横向(x)距离。

@Override
publicbooleanonTouchEvent(MotionEventevent){
if(mode==MODE_CAN_DRAG){
switch(event.getAction()){
caseMotionEvent.ACTION_MOVE:
floatdx=(downX-event.getX());//不要那么灵敏
floatmovePercent=dx/viewWid;
intdIndex=(int)(movePercent
barCount);
inttargetMidIndex=downOldMidIndex+dIndex;
targetMidIndex=Math.max(0,targetMidIndex);
targetMidIndex=Math.min(targetMidIndex,dataList.size()-1);
setMidIndex(targetMidIndex);
Log.d(TAG,"onTouchEvent-MOVE;dx:"+dx+",dIndex:"+dIndex+";targetMidIndex:"+targetMidIndex);
break;
caseMotionEvent.ACTION_DOWN:
downX=event.getX();
downOldMidIndex=midIndex;
break;
caseMotionEvent.ACTION_CANCEL:
caseMotionEvent.ACTION_UP:
downOldMidIndex=midIndex;
tellOnMoveEnd();
break;
}
if(onEventListener!=null){
onEventListener.onDragTouchEvent(event);
}
returntrue;
}
returnsuper.onTouchEvent(event);
}

完整代码

文件SoundWaveView.java,这个view主要目的是展现声波,取名为「SoundWave」

importandroid.content.Context;
importandroid.graphics.Canvas;
importandroid.graphics.Color;
importandroid.graphics.Paint;
importandroid.util.AttributeSet;
importandroid.util.Log;
importandroid.view.MotionEvent;
importandroid.view.View;

importandroidx.annotation.Nullable;

importjava.util.ArrayList;
importjava.util.List;

/*
@authoran.rustfisher.com
*/
publicclassSoundWaveViewextendsView{
privatestaticfinalStringTAG="rustAppSoundWaveView";

/*
单纯播放展示无交互
*/
publicstaticfinalintMODE_PLAY=1;

/*
允许左右拖动
*/
publicstaticfinalintMODE_CAN_DRAG=2;

privateintmode=MODE_PLAY;//1播放
privateList<Float>dataList=newArrayList<>(100);
privatefloatshowMaxData=40f;//能显示的最大数据
privateintmidIndex=0;//在中间显示的数据的下标
privatefloatbarWidDp=1.5f;
privatefloatbarWidPx=3f;
privatefloatbarGapPx=barWidPx/2;
privateintbarCount=1;//当前宽度能绘制多少个柱子
privateintviewWid=1000;//px
privateintviewHeight=100;//px

privatefinalPaintpaint=newPaint();
privateintleftColor=Color.GREEN;
privateintrightColor=Color.LTGRAY;
privateintmiddleLineColor=Color.parseColor("#55000000");

privatefloatdownX=0;//getX
privateintdownOldMidIndex=0;

publicinterfaceOnEvent{
voidonMoveEnd();//停止拖动了

voidonDragTouchEvent(MotionEventevent);
}

privateOnEventonEventListener;

publicSoundWaveView(Contextcontext){
this(context,null);
}

publicSoundWaveView(Contextcontext,@NullableAttributeSetattrs){
this(context,attrs,0);
}

publicSoundWaveView(Contextcontext,@NullableAttributeSetattrs,intdefStyleAttr){
super(context,attrs,defStyleAttr);
paint.setColor(Color.BLUE);
}

@Override
protectedvoidonSizeChanged(intw,inth,intoldw,intoldh){
super.onSizeChanged(w,h,oldw,oldh);
viewWid=w;
viewHeight=h;
calBarPara();
Log.d(TAG,"onSizeChanged:"+w+","+h);
Log.d(TAG,"onSizeChanged:barWidPx:"+barWidPx);
}

@Override
protectedvoidonDraw(Canvascanvas){
super.onDraw(canvas);
if(dataList==null||dataList.isEmpty()){
//drawnothing
drawMiddleLine(canvas);
return;
}
floatx0=viewWid/2.0f;

//绘制数据
if(midIndex>0){
x0=x0-(barGapPx+barWidPx)midIndex;//可能是负数
}
for(inti=0;i<dataList.size();i++){
floatd=dataList.get(i);
floatx=x0+(barWidPx+barGapPx)
i;
if(x<0){
continue;
}
if(x>viewWid){
break;
}
if(i<=midIndex){
paint.setColor(leftColor);
}else{
paint.setColor(rightColor);
}
paint.setStrokeWidth(barWidPx);
floatbh=(d/showMaxData)*viewHeight;
bh=Math.max(bh,4);//最小也要一点高度
floatbhGap=(viewHeight-bh)/2f;
canvas.drawLine(x,bhGap,x,viewHeight-bhGap,paint);
}
drawMiddleLine(canvas);
}

privatevoiddrawMiddleLine(Canvascanvas){
paint.setColor(middleLineColor);
canvas.drawLine(viewWid/2f,0,viewWid/2f,viewHeight,paint);
}

publicfloatgetMidByPercent(){
returnmidIndex/(float)(dataList.size()-1);
}

@Override
publicbooleanonTouchEvent(MotionEventevent){
if(mode==MODE_CAN_DRAG){
switch(event.getAction()){
caseMotionEvent.ACTION_MOVE:
floatdx=(downX-event.getX());//不要那么灵敏
floatmovePercent=dx/viewWid;
intdIndex=(int)(movePercent*barCount);
inttargetMidIndex=downOldMidIndex+dIndex;
targetMidIndex=Math.max(0,targetMidIndex);
targetMidIndex=Math.min(targetMidIndex,dataList.size()-1);
setMidIndex(targetMidIndex);
Log.d(TAG,"onTouchEvent-MOVE;dx:"+dx+",dIndex:"+dIndex+";targetMidIndex:"+targetMidIndex);
break;
caseMotionEvent.ACTION_DOWN:
downX=event.getX();
downOldMidIndex=midIndex;
break;
caseMotionEvent.ACTION_CANCEL:
caseMotionEvent.ACTION_UP:
downOldMidIndex=midIndex;
tellOnMoveEnd();
break;
}
if(onEventListener!=null){
onEventListener.onDragTouchEvent(event);
}
returntrue;
}
returnsuper.onTouchEvent(event);
}

publicvoidsetMode(intmode){
this.mode=mode;
}

publicintgetMode(){
returnmode;
}

publicintgetMidIndex(){
returnmidIndex;
}

publicList<Float>getDataList(){
returndataList;
}

publicvoidsetOnEventListener(OnEventonEventListener){
this.onEventListener=onEventListener;
}

publicvoidclear(){
dataList=newArrayList<>();
midIndex=0;
invalidate();
}

privatevoidcalBarPara(){
barWidPx=dp2Px(barWidDp);
barGapPx=barWidPx;
barCount=(int)((viewWid-barGapPx)/(barWidPx+barGapPx));
paint.setStrokeWidth(barWidPx);
Log.d(TAG,"calBarPara:barCount:"+barCount);
}

publicvoidsetDataList(List<Float>input){
dataList=newArrayList<>(input);
midIndex=0;
invalidate();
}

publicvoidsetMidIndex(intmidIndex){
this.midIndex=midIndex;
invalidate();
}

publicvoidsetMidEnd(){
setMidIndex(dataList.size()-1);
}

//设置当前播放进度
publicvoidsetPlayPercent(floatpercent){
midIndex=(int)(percent*(dataList.size()-1));
if(percent>=1){
midIndex=dataList.size()-1;
}
invalidate();
}

publicvoidsetShowMaxData(floatshowMaxData){
this.showMaxData=showMaxData;
}

publicfloatgetShowMaxData(){
returnshowMaxData;
}

//不停地插入数据
publicvoidaddDataEnd(floatf){
dataList.add(f);
midIndex=dataList.size()-1;
invalidate();
}

publicvoidsetLeftColor(intleftColor){
this.leftColor=leftColor;
}

publicvoidsetRightColor(intrightColor){
this.rightColor=rightColor;
}

privatefloatdp2Px(floatdp){
floatdensity=getContext().getResources().getDisplayMetrics().density;
intmark=dp>0?1:-1;
returndpdensitymark;
}

privatevoidtellOnMoveEnd(){
if(onEventListener!=null){
onEventListener.onMoveEnd();
}
}
}

layout中使用

<com.rustfisher.tutorial2020.customview.soundwave.SoundWaveView
android:id="@+id/sound_wave_view"
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_marginTop="4dp"
android:background="@android:color/white"
app:layout_constraintTop_toTopOf="parent"/>

activity中使用模拟数据

privatevoidsetData1(){
List<Float>dataList=newArrayList<>();
for(inti=0;i<1000;i++){
dataList.add((float)(Math.random()*soundWaveView.getShowMaxData()));
}
soundWaveView.setDataList(dataList);
soundWaveView.setMidIndex(0);

soundWaveView.setOnEventListener(newSoundWaveView.OnEvent(){
@Override
publicvoidonMoveEnd(){
Log.d(TAG,"onMoveEnd:"+soundWaveView.getMidIndex());
}

@Override
publicvoidonDragTouchEvent(MotionEventevent){
//在这里可以收到触摸事件
}
});
}

运行示例:

Android怎么自定义View实现柱状波形图

本文:Android怎么自定义View实现柱状波形图的详细内容,希望对您有所帮助,信息来源于网络。
上一篇:android如何实现自定义圆形取色盘下一篇:

18 人围观 / 0 条评论 ↓快速评论↓

(必须)

(必须,保密)

阿狸1 阿狸2 阿狸3 阿狸4 阿狸5 阿狸6 阿狸7 阿狸8 阿狸9 阿狸10 阿狸11 阿狸12 阿狸13 阿狸14 阿狸15 阿狸16 阿狸17 阿狸18