为了结束这个问题,我使用以下两种方法实现了我的图形(见下文). 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; } }