在 Android 上使用画图绘制自定义折线图
Drawing a custom line graph with Paint on Android
我正在尝试在 Android 应用程序
上使用 Canvas and Paint 绘制折线图
首先,我使用 generateData()
生成一些数据点,它为 Y
数据点创建随机值,并为 X
数据点创建 i
50 倍的随机值。
我希望每个 X
点被 50 个像素分隔(作为一个比例),因此绘制了一个类似的图形,如下所示:
申请class
public class Plotter extends View {
private List<Float> xPosList, yPosList;
private List<Path> pathList;
private Path path;
private Paint paint;
private ConstraintLayout cl;
private TextView stockPriceView;
public Plotter(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
this.xPosList = new ArrayList<>();
this.yPosList = new ArrayList<>();
this.pathList = new ArrayList<>();
this.paint = new Paint();
this.paint.setStrokeWidth(20);
this.paint.setColor(Color.GREEN);
generateData();
}
/***
* Generates random float data points from 5 to 100 and creates a path to plot
*/
private void generateData() {
int min = 5;
int max = 100;
double random = 0;
float xPos = 0;
float yPos = 0;
for (int i = 1; i <= 20; i++) {
random = min + Math.random() * (max - min);
xPos = 50 * i; //50 pixels
yPos = (float)random;
this.xPosList.add(xPos);
this.yPosList.add(yPos);
path = new Path(); //Create path
path.moveTo(xPos, yPos); //Add values to path
this.pathList.add(path); //Add path to pathList
}
}
/***
* Clears the points list
*/
private void clearData() {
this.xPosList.clear();
this.yPosList.clear();
invalidate();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
clearData();
generateData();
break;
case MotionEvent.ACTION_UP:
invalidate(); //Refresh canvas
break;
}
return true; //Activate event
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint p = new Paint();
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
p.setColor(Color.GREEN);
p.setStrokeWidth(10);
p.setStyle(Paint.Style.FILL);
/***
* Better use 50 by 50 pixels
*/
float startX = 0; //Start graph at bottom left
float startY = canvas.getHeight(); //Start at the bottom (max height)
float nextX = 0;
float nextY = 0;
for (int i = 0; i < this.xPosList.size(); i++){
nextX = this.xPosList.get(i);
//TODO: Find a better function
nextY = (canvas.getHeight() - this.yPosList.get(i));
canvas.drawLine(startX, startY, nextX, nextY, p); //Draw segment
startX = nextX; //Save previous X point
startY = nextY; //Save previous Y point
}
//TODO: Find a better way to manage this
cl = (ConstraintLayout) ((ViewGroup)this.getParent()); //get parent Layout
this.stockPriceView = cl.findViewById(R.id.stockPriceText); //access the sibling
if (this.stockPriceView != null) {
this.stockPriceView.setText((nextY)+""); //Write number
}
}
}
虽然我目前的输出与期望相差不远,但它是不正确的。
看起来您的最大 y 值将是 100 像素,与 1000 像素的最大 x 值相比,这不是很多而且相形见绌。您需要将 y 值转换为 dps 或其他一些缩放值以填充更多视图。
具体来说,在 generateData()
内,x 将设置为 50..1,000 的范围,增量为 50,而 y 值将随机分配 5 到 100 之间的值。在绘图代码中,您使用drawLine()
的这些值以像素作为参数。 x-direction 中的 1,000 像素将使您在大多数设备上保持一定距离(在密度为 3 pixels/dp 的设备上为 333.3 dps),但 y-value 的最大值为 100您在同一台设备上最多 33.3 dps - 即距离的 1/10。
假设您希望 x 值跨越 Plotter 视图的宽度,同样对于 y-axis,这些值应该跨越高度。所以,当 x == 0 时,drawLine()
的 x 值也应为 0。当 x-value 为 1,000 时,drawLine()
的 x 值应为 [=38] 的宽度=]绘图仪视图或
xview = viewWidth * x/1,000
同样,对于 drawLine()
的 y 值:
yview = viewHeight * y/100
将循环绘图更改为如下所示:
float viewWidth = getWidth();
float viewHeight = getHeight();
for (int i = 0; i < this.xPosList.size(); i++) {
nextX = this.xPosList.get(i);
nextY = this.yPosList.get(i);
canvas.drawLine(viewWidth * startX / 1000, viewHeight - (viewHeight * startY / 100),
viewWidth * nextX / 1000, viewHeight - (viewHeight * nextY / 100), p); //Draw segment
startX = nextX; //Save previous X point
startY = nextY; //Save previous Y point
}
你会得到一个看起来像这样的情节:
这是使用 canvas translate()
方法的另一种方法:
float viewWidth = getWidth();
float viewHeight = getHeight();
canvas.save();
// Flip the canvas vertically.
canvas.scale(1f, -1f, canvas.getWidth() / 2f, canvas.getHeight() / 2f);
for (int i = 0; i < this.xPosList.size(); i++) {
nextX = this.xPosList.get(i);
nextY = this.yPosList.get(i);
canvas.drawLine(viewWidth * startX / 1000, viewHeight * startY / 100,
viewWidth * nextX / 1000, viewHeight * nextY / 100, p); //Draw segment
startX = nextX; //Save previous X point
startY = nextY; //Save previous Y point
}
canvas.restore();
我正在尝试在 Android 应用程序
上使用 Canvas and Paint 绘制折线图首先,我使用 generateData()
生成一些数据点,它为 Y
数据点创建随机值,并为 X
数据点创建 i
50 倍的随机值。
我希望每个 X
点被 50 个像素分隔(作为一个比例),因此绘制了一个类似的图形,如下所示:
申请class
public class Plotter extends View {
private List<Float> xPosList, yPosList;
private List<Path> pathList;
private Path path;
private Paint paint;
private ConstraintLayout cl;
private TextView stockPriceView;
public Plotter(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
this.xPosList = new ArrayList<>();
this.yPosList = new ArrayList<>();
this.pathList = new ArrayList<>();
this.paint = new Paint();
this.paint.setStrokeWidth(20);
this.paint.setColor(Color.GREEN);
generateData();
}
/***
* Generates random float data points from 5 to 100 and creates a path to plot
*/
private void generateData() {
int min = 5;
int max = 100;
double random = 0;
float xPos = 0;
float yPos = 0;
for (int i = 1; i <= 20; i++) {
random = min + Math.random() * (max - min);
xPos = 50 * i; //50 pixels
yPos = (float)random;
this.xPosList.add(xPos);
this.yPosList.add(yPos);
path = new Path(); //Create path
path.moveTo(xPos, yPos); //Add values to path
this.pathList.add(path); //Add path to pathList
}
}
/***
* Clears the points list
*/
private void clearData() {
this.xPosList.clear();
this.yPosList.clear();
invalidate();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
clearData();
generateData();
break;
case MotionEvent.ACTION_UP:
invalidate(); //Refresh canvas
break;
}
return true; //Activate event
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint p = new Paint();
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
p.setColor(Color.GREEN);
p.setStrokeWidth(10);
p.setStyle(Paint.Style.FILL);
/***
* Better use 50 by 50 pixels
*/
float startX = 0; //Start graph at bottom left
float startY = canvas.getHeight(); //Start at the bottom (max height)
float nextX = 0;
float nextY = 0;
for (int i = 0; i < this.xPosList.size(); i++){
nextX = this.xPosList.get(i);
//TODO: Find a better function
nextY = (canvas.getHeight() - this.yPosList.get(i));
canvas.drawLine(startX, startY, nextX, nextY, p); //Draw segment
startX = nextX; //Save previous X point
startY = nextY; //Save previous Y point
}
//TODO: Find a better way to manage this
cl = (ConstraintLayout) ((ViewGroup)this.getParent()); //get parent Layout
this.stockPriceView = cl.findViewById(R.id.stockPriceText); //access the sibling
if (this.stockPriceView != null) {
this.stockPriceView.setText((nextY)+""); //Write number
}
}
}
虽然我目前的输出与期望相差不远,但它是不正确的。
看起来您的最大 y 值将是 100 像素,与 1000 像素的最大 x 值相比,这不是很多而且相形见绌。您需要将 y 值转换为 dps 或其他一些缩放值以填充更多视图。
具体来说,在 generateData()
内,x 将设置为 50..1,000 的范围,增量为 50,而 y 值将随机分配 5 到 100 之间的值。在绘图代码中,您使用drawLine()
的这些值以像素作为参数。 x-direction 中的 1,000 像素将使您在大多数设备上保持一定距离(在密度为 3 pixels/dp 的设备上为 333.3 dps),但 y-value 的最大值为 100您在同一台设备上最多 33.3 dps - 即距离的 1/10。
假设您希望 x 值跨越 Plotter 视图的宽度,同样对于 y-axis,这些值应该跨越高度。所以,当 x == 0 时,drawLine()
的 x 值也应为 0。当 x-value 为 1,000 时,drawLine()
的 x 值应为 [=38] 的宽度=]绘图仪视图或
xview = viewWidth * x/1,000
同样,对于 drawLine()
的 y 值:
yview = viewHeight * y/100
将循环绘图更改为如下所示:
float viewWidth = getWidth();
float viewHeight = getHeight();
for (int i = 0; i < this.xPosList.size(); i++) {
nextX = this.xPosList.get(i);
nextY = this.yPosList.get(i);
canvas.drawLine(viewWidth * startX / 1000, viewHeight - (viewHeight * startY / 100),
viewWidth * nextX / 1000, viewHeight - (viewHeight * nextY / 100), p); //Draw segment
startX = nextX; //Save previous X point
startY = nextY; //Save previous Y point
}
你会得到一个看起来像这样的情节:
这是使用 canvas translate()
方法的另一种方法:
float viewWidth = getWidth();
float viewHeight = getHeight();
canvas.save();
// Flip the canvas vertically.
canvas.scale(1f, -1f, canvas.getWidth() / 2f, canvas.getHeight() / 2f);
for (int i = 0; i < this.xPosList.size(); i++) {
nextX = this.xPosList.get(i);
nextY = this.yPosList.get(i);
canvas.drawLine(viewWidth * startX / 1000, viewHeight * startY / 100,
viewWidth * nextX / 1000, viewHeight * nextY / 100, p); //Draw segment
startX = nextX; //Save previous X point
startY = nextY; //Save previous Y point
}
canvas.restore();