Ant*_*neG 7 android graph android-custom-view android-animation
[更新]
为了结束这个问题,我使用以下两种方法实现了我的图形(见下文). drawCurve()接收一个Canvas和一个数组float.数组已正确填充(时间戳由数组中的值索引假定)并且从0.0到1.0不等.数组被发送到一个循环方式的prepareWindowArray()位置windowStartfor windowSize-values 中取一个数组的块.
GraphView和数据提供者(蓝牙设备)使用的阵列是相同的.中间的类确保GraphView不读取蓝牙设备正在写入的数据.由于GraphView始终循环通过数组并在每次迭代时重绘它,它将根据蓝牙设备写入的数据进行更新,并通过强制蓝牙设备的写入频率到图表的刷新频率,我获得了平滑动画我的信号.
所述GraphView的invalidate()方法是由所谓的Activity,其运行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);
}
Run Code Online (Sandbox Code Playgroud)
在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;
}
Run Code Online (Sandbox Code Playgroud)
[/ 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);
}
Run Code Online (Sandbox Code Playgroud)
它给了我这样的东西.

我的图表上还有一些东西要修复(子轴没有正确缩放),但这些是我稍后可以修复的细节.
现在我想改变这个静态图形(接收一个非动态的矩阵值),用动态的东西每隔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, 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
*/
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, positionOfX, 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, 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);
}
///////////////////////////////////////////////////////////////////////////////
// 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, 0, 0, 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;
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
8314 次 |
| 最近记录: |