Android中的自定义动态图表

前端之家收集整理的这篇文章主要介绍了Android中的自定义动态图表前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
[UPDATE]
为了结束这个问题,我使用以下两种方法实现了我的图形(见下文). drawCurve()接收Canvas和float数组.数组已正确填充(时间戳由数组中的值索引假定)并且从0.0到1.0不等.该数组被发送到prepareWindowArray(),它以循环方式从位置windowStart获取windowSize值的数组.

GraphView和数据提供者(蓝牙设备)使用的阵列是相同的.中间的类确保GraphView不读取蓝牙设备正在写入的数据.由于GraphView始终循环通过数组并在每次迭代时重绘它,它将根据蓝牙设备写入的数据进行更新,并通过强制蓝牙设备的写入频率到图表的刷新频率,我获得了平滑动画我的信号.

Graph调用了invalidate()方法,该方法运行一个Timer来每隔x毫秒刷新一次图形.刷新图形的频率是动态设置的,以便它适应来自蓝牙设备的数据流(它在其数据包的标题中指定其信号的频率).

在我下面写的答案中找到我的GraphView的完整代码(在答案部分).如果您发现错误或优化方法,请告诉我们;这将不胜感激!

/**
 * Read a buffer array of size greater than "windowSize" and create a window array out of it.
 * A curve is then drawn from this array using "windowSize" points,from left
 * to right.
 * @param canvas is a Canvas object on which the curve will be drawn.  Ensure the canvas is the
 * later drawn object at its position or you will not see your curve.
 * @param data is a float array of length > windowSize.  The floats must range between 0.0 and 1.0.
 * A value of 0.0 will be drawn at the bottom of the graph,while a value of 1.0 will be drawn at 
 * the top of the graph.  The range is not tested,so you must ensure to pass proper values,or your
 * graph will look terrible. 
 *      0.0  : draw at the bottom of the graph
 *      0.5  : draw in the middle of the graph
 *      1.0  : draw at the top of the graph
 */
private void drawCurve(Canvas canvas,float[] data){

    // Create a reference value to determine the stepping between each points to be drawn
    float incrementX = (mRightSide-mLeftSide)/(float) windowSize;

    float incrementY = (mBottomSide - mTopSide);

    // Prepare the array for the graph
    float[] source = prepareWindowArray(data);

    // Prepare the curve Path
    curve = new Path();
    // Move at the first point.
    curve.moveTo(mLeftSide,source[0]*incrementY);
    // Draw the remaining points of the curve
    for(int i = 1; i < windowSize; i++){
        curve.lineTo(mLeftSide + (i*incrementX),source[i] * incrementY);
    }

    canvas.drawPath(curve,curvePaint);

}

prepareWindowArray()方法实现数组的循环行为:

/**
 * Extract a window array from the data array,and reposition the windowStart 
 * index for next iteration
 * @param data the array of data from which we get the window
 * @return an array of float that represent the window
 */
private float[] prepareWindowArray(float[] data){
    // Prepare the source array for the graph.
    float[] source = new float[windowSize];

    // Copy the window from the data array into the source array
    for(int i = 0; i < windowSize; i++){
        if(windowStart+i < data.length)                         // If the windows holds within the data array
            source[i] = data[windowStart + i];                  // Simply copy the value in the source array
        else{                                                   // If the window goes beyond the data array
            source[i] = data[(windowStart + 1)%data.length];    // Loop at the beginning of the data array and copy from there
        }
    }
    // Reposition the buffer index
    windowStart = windowStart + windowSize;
    // If the index is beyond the end of the array
    if(windowStart >= data.length){
        windowStart = windowStart % data.length;
    }

    return source;
}

[/ UPDATE]

我正在制作一个以固定速率从蓝牙设备读取数据的应用程序.每当我有新数据时,我希望它们在右边的图形上绘制,并将图形的其余部分实时转换为左边.基本上,就像示波器一样.

所以我制作了一个自定义视图,带有xy轴,标题和单位.为此,我只是在View画布上绘制这些东西.现在我想绘制曲线.我设法使用此方法从已填充的数组中绘制静态曲线:

public void drawCurve(Canvas canvas){

    int left = getPaddingLeft();
    int bottom = getHeight()-getPaddingTop();
    int middle = (bottom-10)/2 - 10;

    curvePaint = new Paint();
    curvePaint.setColor(Color.GREEN);
    curvePaint.setStrokeWidth(1f);
    curvePaint.setDither(true);
    curvePaint.setStyle(Paint.Style.STROKE);
    curvePaint.setStrokeJoin(Paint.Join.ROUND);
    curvePaint.setStrokeCap(Paint.Cap.ROUND);
    curvePaint.setPathEffect(new CornerPathEffect(10) );
    curvePaint.setAntiAlias(true);

    mCurve = new Path();
    mCurve.moveTo(left,middle);
    for(int i = 0; i < mData[0].length; i++)
        mCurve.lineTo(left + ((float)mData[0][i] * 5),middle-((float)mData[1][i] * 20));


    canvas.drawPath(mCurve,curvePaint);
}

它给了我这样的东西.

我的图表上还有一些东西要修复(子轴没有正确缩放),但这些是我稍后可以修复的细节.

现在我想改变这个静态图形(接收一个非动态的矩阵值),用动态的东西每隔40ms重绘一次曲线,将旧数据推到左边并将新数据绘制到右边,这样我就可以看到实时地通过蓝牙设备提供的信息.

我知道有一些图形包已经存在,但是我对这些东西很有用,我想通过自己实现这个图来实现.此外,除曲线部分外,我的大多数GraphView类都已完成.

第二个问题,我想知道如何将新值发送到图表.我应该使用类似FIFO堆栈的东西,还是可以用简单的双打矩阵实现我想要的东西?

另外,底部的4个字段已经动态更新.嗯,他们有点伪装“动态”,他们一次又一次地循环通过相同的双重矩阵,他们实际上并没有采取新的价值观.

谢谢你的时间!如果我的问题有些不清楚,请告诉我,我会更详细地更新它.

解决方法

正如我的问题所提到的,这是我设计用来解决问题的课程.
/**
 * A View implementation that displays a scatter graph with 
 * automatic unit scaling.
 * 
 * Call the <i>setupGraph()</i> method to modify the graph's
 * properties.
 * @author Antoine Grondin
 *
 */

public class GraphView extends View {

    //////////////////////////////////////////////////////////////////
    // Configuration
    //////////////////////////////////////////////////////////////////

    // Set to true to impose the graph properties
    private static final boolean TEST = false;  

    // Scale configuration
    private float minX = 0;         // When TEST is true,these values are used to
    private float maxX = 50;        // Draw the graph
    private float minY = 0;
    private float maxY = 100;

    private String titleText = "A Graph...";
    private String xUnitText = "s";
    private String yUnitText = "Volts";

    // Debugging variables
    private boolean D = true;
    private String TAG = "GraphView";

    //////////////////////////////////////////////////////////////////
    // Member fields
    //////////////////////////////////////////////////////////////////

    // Represent the borders of the View
    private int mTopSide = 0;
    private int mLeftSide = 0;
    private int mRightSide = 0;
    private int mBottomSide = 0;
    private int mMiddleX = 0;
    // Size of a DensityIndependentPixel
    private float mDips = 0;

    // Hold the position of the axis in regard to the range of values
    private int positionOfX = 0;
    private int positionOfY = 0;

    // Index for the graph array window,and size of the window
    private int windowStart = 0;
    private int windowSize = 128;
    private float[] dataSource;

    // Painting tools
    private Paint xAxisPaint;
    private Paint yAxisPaint;
    private Paint tickPaint;
    private Paint curvePaint;
    private Paint backgroundPaint;

    private TextPaint unitTextPaint;
    private TextPaint titleTextPaint;

    // Object to be drawn

    private Path curve;
    private Bitmap background;

    ///////////////////////////////////////////////////////////////////////////////
    // Constructors
    ///////////////////////////////////////////////////////////////////////////////

    public GraphView(Context context) {
        super(context);
        init();
    }

    public GraphView(Context context,AttributeSet attrs){
        super(context,attrs);
        init();
    }

    public GraphView(Context context,AttributeSet attrs,int defStyle){
        super(context,attrs,defStyle);
        init();
    }

    ///////////////////////////////////////////////////////////////////////////////
    // Configuration methods
    /////////////////////////////////////////////////////////////////////////////// 

    public void setupGraph(String title,String nameOfX,float min_X,float max_X,String nameOfY,float min_Y,float max_Y){
        if(!TEST){
            titleText = title;
            xUnitText = nameOfX;
            yUnitText = nameOfY;
            minX = min_X;
            maxX = max_X;
            minY = min_Y;
            maxY = max_Y;
        }
    }

    /**
     * Set the array this GraphView is to work with.
     * @param data is a float array of length > windowSize.  The floats must range between 0.0 and 1.0.
     * A value of 0.0 will be drawn at the bottom of the graph,while a value of 1.0 will be drawn at 
     * the top of the graph.  The range is not tested,or your
     * graph will look terrible.
     *      0.0  : draw at the bottom of the graph
     *      0.5  : draw in the middle of the graph
     *      1.0  : draw at the top of the graph
     */
    public void setDataSource(float[] data){
        this.dataSource = data;
    }

    ///////////////////////////////////////////////////////////////////////////////
    // Initialization methods
    /////////////////////////////////////////////////////////////////////////////// 

    private void init(){
        initDrawingTools();
    }

    private void initConstants(){
        mDips = getResources().getDisplayMetrics().density;
        mTopSide = (int) (getTop() + 10*mDips);
        mLeftSide = (int) (getLeft() + 10*mDips);
        mRightSide = (int) (getMeasuredWidth() - 10*mDips);
        mBottomSide = (int) (getMeasuredHeight() - 10*mDips);
        mMiddleX = (mRightSide - mLeftSide)/2 + mLeftSide;
    }

    private void initWindowSetting() throws IllegalArgumentException {

        // Don't do anything if the given values make no sense
        if(maxX < minX || maxY < minY ||
                maxX == minX || maxY == minY){
            throw new IllegalArgumentException("Max and min values make no sense");
        }
        // Transform the values in scanable items
        float[][] maxAndMin = new float[][]{
                {minX,maxX},{minY,maxY}};
        int[] positions = new int[]{positionOfY,positionOfX};

        // Place the X and Y axis in regard to the given max and min
        for(int i = 0; i<2; i++){
            if(maxAndMin[i][0] < 0f){
                if(maxAndMin[i][1] < 0f){
                    positions[i] = (int) maxAndMin[i][0];
                } else{
                    positions[i] = 0;
                }
            } else if (maxAndMin[i][0] > 0f){
                positions[i] = (int) maxAndMin[i][0];
            } else {
                positions[i] = 0;
            }
        }

        // Put the values back in their right place
        minX = maxAndMin[0][0];
        maxX = maxAndMin[0][1];
        minY = maxAndMin[1][0];
        maxY = maxAndMin[1][1];

        positionOfY = mLeftSide +  (int) (((positions[0] - minX)/(maxX-minX))*(mRightSide - mLeftSide));    
        positionOfX = mBottomSide - (int) (((positions[1] - minY)/(maxY-minY))*(mBottomSide - mTopSide));
    }

    private void initDrawingTools(){

        xAxisPaint = new Paint();
        xAxisPaint.setColor(0xff888888);
        xAxisPaint.setStrokeWidth(1f*mDips);
        xAxisPaint.setAlpha(0xff);
        xAxisPaint.setAntiAlias(true);

        yAxisPaint = xAxisPaint;

        tickPaint = xAxisPaint;
        tickPaint.setColor(0xffaaaaaa);

        curvePaint = new Paint();
        curvePaint.setColor(0xff00ff00);
        curvePaint.setStrokeWidth(1f*mDips);
        curvePaint.setDither(true);
        curvePaint.setStyle(Paint.Style.STROKE);
        curvePaint.setStrokeJoin(Paint.Join.ROUND);
        curvePaint.setStrokeCap(Paint.Cap.ROUND);
        curvePaint.setPathEffect(new CornerPathEffect(10));
        curvePaint.setAntiAlias(true);

        backgroundPaint = new Paint();
        backgroundPaint.setFilterBitmap(true);

        titleTextPaint = new TextPaint();
        titleTextPaint.setAntiAlias(true);
        titleTextPaint.setColor(0xffffffff);
        titleTextPaint.setTextAlign(Align.CENTER);
        titleTextPaint.setTextSize(20f*mDips);
        titleTextPaint.setTypeface(Typeface.MONOSPACE);

        unitTextPaint = new TextPaint();
        unitTextPaint.setAntiAlias(true);
        unitTextPaint.setColor(0xff888888);
        unitTextPaint.setTextAlign(Align.CENTER);
        unitTextPaint.setTextSize(20f*mDips);
        unitTextPaint.setTypeface(Typeface.MONOSPACE);

    }

    ///////////////////////////////////////////////////////////////////////////////
    // Overridden methods
    /////////////////////////////////////////////////////////////////////////////// 

    protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec){
        super.onMeasure(widthMeasureSpec,heightMeasureSpec);
    }

    protected void onSizeChanged(int w,int h,int oldw,int oldh) {
        regenerateBackground();
    }

    public void onDraw(Canvas canvas){
        drawBackground(canvas);
        if(dataSource != null)
            drawCurve(canvas,dataSource);
    }

    ///////////////////////////////////////////////////////////////////////////////
    // Drawing methods
    /////////////////////////////////////////////////////////////////////////////// 

    private void drawX(Canvas canvas){
        canvas.drawLine(mLeftSide,positionOfX,mRightSide,xAxisPaint);
        canvas.drawText(xUnitText,mRightSide -  unitTextPaint.measureText(xUnitText)/2,positionOfX - unitTextPaint.getTextSize()/2,unitTextPaint);
    }

    private void drawY(Canvas canvas){
        canvas.drawLine(positionOfY,mTopSide,positionOfY,mBottomSide,yAxisPaint);
        canvas.drawText(yUnitText,positionOfY + unitTextPaint.measureText(yUnitText)/2 + 4*mDips,mTopSide + (int) (unitTextPaint.getTextSize()/2),unitTextPaint);
    }

    private void drawTick(Canvas canvas){
        // No tick at this time
        // TODO decide how I want to put those ticks,if I want them
    }

    private void drawTitle(Canvas canvas){
        canvas.drawText(titleText,mMiddleX,mTopSide + (int) (titleTextPaint.getTextSize()/2),titleTextPaint);
    }

    /**
     * Read a buffer array of size greater than "windowSize" and create a window array out of it.
     * A curve is then drawn from this array using "windowSize" points,from left
     * to right.
     * @param canvas is a Canvas object on which the curve will be drawn.  Ensure the canvas is the
     * later drawn object at its position or you will not see your curve.
     * @param data is a float array of length > windowSize.  The floats must range between 0.0 and 1.0.
     * A value of 0.0 will be drawn at the bottom of the graph,or your
     * graph will look terrible. 
     *      0.0  : draw at the bottom of the graph
     *      0.5  : draw in the middle of the graph
     *      1.0  : draw at the top of the graph
     */
    private void drawCurve(Canvas canvas,float[] data){

        // Create a reference value to determine the stepping between each points to be drawn
        float incrementX = (mRightSide-mLeftSide)/(float) windowSize;

        float incrementY = mBottomSide - mTopSide;

        // Prepare the array for the graph
        float[] source = prepareWindowArray(data);

        // Prepare the curve Path
        curve = new Path();
        // Move at the first point.
        curve.moveTo(mLeftSide,source[0]*incrementY);
        // Draw the remaining points of the curve
        for(int i = 1; i < windowSize; i++){
            curve.lineTo(mLeftSide + (i*incrementX),source[i] * incrementY);
        }

        canvas.drawPath(curve,curvePaint);
    }

    ///////////////////////////////////////////////////////////////////////////////
    // Intimate methods
    /////////////////////////////////////////////////////////////////////////////// 

    /**
     * When asked to draw the background,this method will verify if a bitmap of the
     * background is available.  If not,it will regenerate one.  Then,it will draw
     * the background using this bitmap.  The use of a bitmap to draw the background
     * is to avoid unnecessary processing for static parts of the view.
     */
    private void drawBackground(Canvas canvas){
        if(background == null){
            regenerateBackground();
        } 
        canvas.drawBitmap(background,backgroundPaint);
    }

    /**
     * Call this method to force the <i>GraphView</i> to redraw the cache of it's background,* using new properties if you changed them with <i>setupGraph()</i>.
     */
    public void regenerateBackground(){
        initConstants();
        try{
            initWindowSetting();
        } catch (IllegalArgumentException e){
            Log.e(TAG,"Could not initalize windows.",e);
            return;
        }
        if(background != null){
            background.recycle();
        }
        background = Bitmap.createBitmap(getWidth(),getHeight(),Bitmap.Config.ARGB_8888);
        Canvas backgroundCanvas = new Canvas(background);

        drawX(backgroundCanvas);
        drawY(backgroundCanvas);
        drawTick(backgroundCanvas);
        drawTitle(backgroundCanvas);

    }

    /**
     * Extract a window array from the data array,and reposition the windowStart 
     * index for next iteration
     * @param data the array of data from which we get the window
     * @return an array of float that represent the window
     */
    private float[] prepareWindowArray(float[] data){
        // Prepare the source array for the graph.
        float[] source = new float[windowSize];

        // Copy the window from the data array into the source array
        for(int i = 0; i < windowSize; i++){
            if(windowStart+i < data.length)                         // If the windows holds within the data array
                source[i] = data[windowStart + i];                  // Simply copy the value in the source array
            else{                                                   // If the window goes beyond the data array
                source[i] = data[(windowStart + 1)%data.length];    // Loop at the beginning of the data array and copy from there
            }
        }
        // Reposition the buffer index
        windowStart = windowStart + windowSize;
        // If the index is beyond the end of the array
        if(windowStart >= data.length){
            windowStart = windowStart % data.length;
        }

        return source;
    }
}
原文链接:https://www.f2er.com/android/309216.html

猜你在找的Android相关文章