Android自定义效果 简易示波器 (1)

图表绘制
在项目中,常常会用到一些图表,我们可以使用第三方开源控件进行绘制,但是如果有一些特殊的定制化的需求,
我们可能需要自己进行一些修改。所以,最好对这种需求有所准备。
简易示波器

这里写图片描述
图中为显示一组正弦数据,及其FFT后的数据。接着开始贴代码吧

示波器控件类
类中包含了控件的绘制方法以及相关数据结构。
基本思路:
1.测量控件,获取表格中的缩放比例等信息
2.获取数据,获取原始输入数据,并根据需求转换为控件的显示坐标(控件的左上角为0,0点)
3.绘制线图
package com.example.myxytable.view;

import java.util.ArrayList;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
/**
 * XY控件
 * @author vonchenchen
 *
 */
public class XYWaveTable extends View{

    private static float mScale;

    private Paint paint;

    /** 控件宽度  单位为像素点 */
    private int mWidthSize;
    /** 控件高度  单位为像素点 */
    private int mHheightSize;

    /** 基准点 */
    private Point basePoint = new Point();
    /** 横坐标点数 */
    private int mHorizantalPointsNumber;
    /** 纵坐标点数 */
    private int mVerticalPointsNumber;
    /** 横坐标间隔 */
    private int mHorizantalPointsGap;
    /** 纵坐标间隔 */
    private int mVerticalPointsGap;

    private ArrayList<Line> mLines = new ArrayList<Line>();

    public XYWaveTable(Context context) {
        this(context, null);
    }

    public XYWaveTable(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public XYWaveTable(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);

        mScale = context.getResources().getDisplayMetrics().density;
        // 初始化一个抗锯齿的画笔
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setColor(Color.WHITE);
    }

    /**
     * 设置横坐标 纵坐标点数
     * @param width
     * @param height
     */
    public void setPointsNumber(int width, int height){
        this.mHorizantalPointsNumber = width;
        this.mVerticalPointsNumber = height;
    }

    public static int dip2px(Context context, float dpValue) {
        return (int) (dpValue * mScale);
    }

    /** 增加一条曲线 */
    public void addLine(Line line){

        mLines.add(line);

        invalidate();
    }

    /** 画坐标线 */
    private void drawFrame(Canvas canvas){

        paint.setColor(Color.WHITE);
        canvas.drawLine(0, mHheightSize/2, mWidthSize, mHheightSize/2, paint);
    }

    /**
     * 测量控件,获取需要的各项长度参数信息,此处我们为了获取个点间 水平与垂直的单位距离
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        //int widthMode = MeasureSpec.getMode(widthMeasureSpec);// 获取到当前FlowLayout的宽的模式
        mWidthSize = MeasureSpec.getSize(widthMeasureSpec);
        //int heightMode = MeasureSpec.getMode(heightMeasureSpec);// 获取到当前FlowLayout的高的模式
        mHheightSize = MeasureSpec.getSize(heightMeasureSpec); 

        basePoint.x = 0;
        basePoint.y = mHheightSize/2;

        //计算点与点间的间隔
        mHorizantalPointsGap = (int)(mWidthSize / mHorizantalPointsNumber);
        mVerticalPointsGap = (int)(mHheightSize / mVerticalPointsNumber);

        Log.i("XYWaveTable", "mWidthSize="+mWidthSize+" mHheightSize="+mHheightSize);
    }

    /** 
     * 坐标转换   将绝对坐标转换为显示坐标 
     * 
     * 绝对坐标是实际数据的坐标                                    
     * 显示坐标是我们在当前图像上绘制点时的坐标
     * */
    private Point changeXY(Point originalPoint){

        originalPoint.x = originalPoint.x + basePoint.x;
        originalPoint.y = mHheightSize - (originalPoint.y*mVerticalPointsGap + basePoint.y);

        return originalPoint;
    }

    /**
     * 绘制坐标连线
     * @param canvas
     * @param line
     */
    private void drawLine(Canvas canvas, Line line){
        paint.setColor(line.color);
        ArrayList<Point> points = line.points;
        Point pointStart;     //起始点
        Point pointStop;      //结束点

        pointStart = changeXY(points.get(0));      //获取起始点的显示坐标

        int horizantalLenthStart = pointStart.x;
        int horizantalLenthStop = horizantalLenthStart + mHorizantalPointsGap;
        for(int i=1; i<line.points.size(); i++){

            pointStop = changeXY(points.get(i));   //获取结束点的显示坐标

            pointStop.y += mVerticalPointsGap;

            //画线
            canvas.drawLine(horizantalLenthStart, pointStart.y, horizantalLenthStop, pointStop.y, paint);

            //更新起始点与结束点
            pointStart = pointStop;

            horizantalLenthStart = horizantalLenthStop;
            horizantalLenthStop = horizantalLenthStart + mHorizantalPointsGap;
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {

        drawFrame(canvas);

        //画出所有的曲线
        for(int i=0; i<mLines.size(); i++){
            drawLine(canvas, mLines.get(i));
        }

        super.onDraw(canvas);
    }

    public static class Point{
        public int x;
        public int y;
    }

    public static class Line{
        public ArrayList<Point> points;
        public int color;
    }
}
控件的引用
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >

    <com.example.myxytable.view.XYWaveTable 
        android:id="@+id/xy_table"
        android:layout_width="match_parent"
        android:layout_height="300dp"
        android:background="#000000"
        />

</RelativeLayout>
控件的调用
调用分为四步,详见代码注释
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mFFt = new FFT();

        //1.获取控件实例
        mXYTable = (XYWaveTable) findViewById(R.id.xy_table);
        //2.设置 横坐标 纵坐标 点数
        mXYTable.setPointsNumber(128,128);

        //3.按照Line的结构创建序列
        Line line1 = createDemoLine();

        Line line2 = createFFtLine(line1);

        //4.使用 addLine方法在控件上绘制一条曲线
        mXYTable.addLine(line1);

        mXYTable.addLine(line2);
    }
        protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mFFt = new FFT();

        //1.获取控件实例
        mXYTable = (XYWaveTable) findViewById(R.id.xy_table);
        //2.设置 横坐标 纵坐标 点数
        mXYTable.setPointsNumber(128,128);

        //3.按照Line的结构创建序列
        Line line1 = createDemoLine();

        Line line2 = createFFtLine(line1);

        //4.使用 addLine方法在控件上绘制一条曲线
        mXYTable.addLine(line1);

        mXYTable.addLine(line2);
    }
创建曲线
    /**
     * 构造一条正弦曲线并绘制
     * @return
     */
    private Line createDemoLine(){

        ArrayList<Point> list = new ArrayList<Point>();

        for(int i=0; i<128; i++){
            Point point = new Point();
            point.x = i;
            point.y = (int) (Math.sin((double)i)*32);
            list.add(point);
        }

        Line line = new Line();
        line.points = list;
        line.color = Color.RED;

        return line;
    }
附录 java FFT代码

把原先嵌入式系统里的FFT C函数包装了一下,本文用到的FFT类,放在这里,方便日后粘贴

package com.example.myxytable.fft;

public class FFT {

    private static double PI = 3.141592653;

    private static int FFT_N = 128;

    public FFT(){
        create_sin_tab(SIN_TAB);
    }

    public static class Compx {
        public float real;
        public float imag;
    };

    //private Compx[] S = new Compx[FFT_N];
    private float SIN_TAB[] = new float[FFT_N/2]; 

    /*******************************************************************
    函数原型:struct compx EE(struct compx b1,struct compx b2)  
    函数功能:对两个复数进行乘法运算
    输入参数:两个以联合体定义的复数a,b
    输出参数:a和b的乘积,以联合体的形式输出
    *******************************************************************/
    private Compx EE(Compx a,Compx b)      
    {
         Compx c = new Compx();
         c.real=a.real*b.real-a.imag*b.imag;
         c.imag=a.real*b.imag+a.imag*b.real;
         return(c);
    }
    /******************************************************************
    函数原型:void create_sin_tab(float *sin_t)
    函数功能:创建一个正弦采样表,采样点数与福利叶变换点数相同
    输入参数:*sin_t存放正弦表的数组指针
    输出参数:无
    ******************************************************************/
    private void create_sin_tab(float[] sin_t)                     
    {
      int i;
      for(i=0;i<FFT_N/2;i++)
      sin_t[i]=(float) Math.sin(2*PI*i/FFT_N);
    }
    /******************************************************************
    函数原型:void sin_tab(float pi)
    函数功能:采用查表的方法计算一个数的正弦值
    输入参数:pi 所要计算正弦值弧度值,范围0--2*PI,不满足时需要转换
    输出参数:输入值pi的正弦值
    ******************************************************************/
    private float sin_tab(float pi)
    {
      int n;
      float a = 0;
       n=(int)(pi*FFT_N/2/PI);

      if(n>=0&&n<FFT_N/2)
       a=SIN_TAB[n];
      else if(n>=FFT_N/2&&n<FFT_N)
        a=-SIN_TAB[n-FFT_N/2];
      return a;
    }
    /******************************************************************
    函数原型:void cos_tab(float pi)
    函数功能:采用查表的方法计算一个数的余弦值
    输入参数:pi 所要计算余弦值弧度值,范围0--2*PI,不满足时需要转换
    输出参数:输入值pi的余弦值
    ******************************************************************/
    private float cos_tab(float d)
    {
       float a,pi2;
       pi2=(float) (d+PI/2);
       if(pi2>2*PI)
         pi2-=2*PI;
       a=sin_tab(pi2);
       return a;
    }
    /*****************************************************************
    函数原型:void FFT(struct compx *xin,int N)
    函数功能:对输入的复数组进行快速傅里叶变换(FFT)
    输入参数:*xin复数结构体组的首地址指针,struct型
    输出参数:无
    *****************************************************************/
    public void FFT(Compx[] xin)
    {
      int f,m,nv2,nm1,i,k,l,j=0;
      Compx u,w,t;

       nv2=FFT_N/2;             //变址运算,即把自然顺序变成倒位序,采用雷德算法
       nm1=FFT_N-1;  
       for(i=0;i<nm1;i++)        
       {
        if(i<j)                    //如果i<j,即进行变址
         {
          t=xin[j];           
          xin[j]=xin[i];
          xin[i]=t;
         }
        k=nv2;                    //求j的下一个倒位序
        while(k<=j)               //如果k<=j,表示j的最高位为1   
         {           
          j=j-k;                 //把最高位变成0
          k=k/2;                 //k/2,比较次高位,依次类推,逐个比较,直到某个位为0
         }
       j=j+k;                   //把0改为1
      }

      {
       int le,lei,ip;                            //FFT运算核,使用蝶形运算完成FFT运算
        f=FFT_N;
       for(l=1;(f=f/2)!=1;l++)              //计算l的值,即计算蝶形级数
               ;
      for(m=1;m<=l;m++)                   // 控制蝶形结级数
       {                               //m表示第m级蝶形,l为蝶形级总数l=log(2)N
        le=2<<(m-1);                    //le蝶形结距离,即第m级蝶形的蝶形结相距le点
        lei=le/2;                         //同一蝶形结中参加运算的两点的距离

        u = new Compx();
        u.real=(float) 1;                        //u为蝶形结运算系数,初始值为1
        u.imag=(float) 0;
        //w.real=cos(PI/lei);                  //不适用查表法计算sin值和cos值
        // w.imag=-sin(PI/lei);

        w = new Compx();
        w.real=cos_tab((float) (PI/lei));             //w为系数商,即当前系数与前一个系数的商
        w.imag=-sin_tab((float) (PI/lei));
        for(j=0;j<=lei-1;j++)              //控制计算不同种蝶形结,即计算系数不同的蝶形结
         {
          for(i=j;i<=FFT_N-1;i=i+le)       //控制同一蝶形结运算,即计算系数相同蝶形结
           {
            ip=i+lei;                          //i,ip分别表示参加蝶形运算的两个节点
            t=EE(xin[ip],u);                   //蝶形运算,详见公式
            xin[ip].real=xin[i].real-t.real;
            xin[ip].imag=xin[i].imag-t.imag;
            xin[i].real=xin[i].real+t.real;
            xin[i].imag=xin[i].imag+t.imag;
           }
          u=EE(u,w);                          //改变系数,进行下一个蝶形运算
         }
       }
      }   
    }
}
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值