I want to create a rounded graph that will display a range of values from my app. The values can be classified to 3 categories: low, mid, high - that are represented by 3 colors: blue, green and red (respectively).
我想创建一个圆形图表,它将显示我的应用程序中的一系列值。这些值可以分为3类:低,中,高 - 由3种颜色表示:蓝色,绿色和红色(分别)。
Above this range, I want to show the actually measured values - in a form of a "thumb" over the relevant range part:
在此范围之上,我想显示实际测量值 - 在相关范围部分上以“拇指”的形式显示:
The location of the white thumb over the range arc may change, according to the measured values.
根据测量值,白色拇指在范围弧上的位置可以改变。
Currently, I'm able to draw the 3-colored range by drawing 3 arcs over the same center, inside the view's onDraw method:
目前,我可以通过在视图的onDraw方法内的相同中心绘制3个弧来绘制3色范围:
width = (float) getWidth();
height = (float) getHeight();
float radius;
if (width > height) {
radius = height / 3;
} else {
radius = width / 3;
}
paint.setAntiAlias(true);
paint.setStrokeWidth(arcLineWidth);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setStyle(Paint.Style.STROKE);
center_x = width / 2;
center_y = height / 1.6f;
left = center_x - radius;
float top = center_y - radius;
right = center_x + radius;
float bottom = center_y + radius;
oval.set(left, top, right, bottom);
//blue arc
paint.setColor(colorLow);
canvas.drawArc(oval, 135, 55, false, paint);
//red arc
paint.setColor(colorHigh);
canvas.drawArc(oval, 350, 55, false, paint);
//green arc
paint.setColor(colorNormal);
canvas.drawArc(oval, 190, 160, false, paint);
And this is the result arc:
这是结果弧:
My question is, how do I:
我的问题是,我该怎么做:
SweepGradient
but it didn't give me the correct result). 在这3种颜色之间创建一个平滑的渐变(我尝试使用SweepGradient,但它没有给我正确的结果)。
Create the overlay white thumb as shown in the picture, so that I'll be able to control where to display it.
创建如图所示的叠加白拇指,以便我能够控制显示它的位置。
Animate this white thumb over my range arc.
在我的范围弧上为此白色拇指设置动画。
Note: the 3-colored range is static - so another solution can be to just take the drawable and paint the white thumb over it (and animate it), so I'm open to hear such a solution as well :)
注意:3色范围是静态的 - 所以另一种解决方案可以是将drawable和白色拇指涂在它上面(并为其设置动画),所以我也可以听到这样的解决方案:)
27
I would use masks for your first two problems.
我会为你的前两个问题使用面具。
The very first step would be drawing two rectangles with a linear gradient. The first rectangle contains the colors blue and green while the second rectangle contains green and red as seen in the following picture. I marked the line where both rectangles touch each other black to clarify that they are infact two different rectangles.
第一步是绘制两个具有线性渐变的矩形。第一个矩形包含蓝色和绿色,而第二个矩形包含绿色和红色,如下图所示。我标记了两个矩形相互接触黑色的线条,以澄清它们实际上是两个不同的矩形。
This can be achieved using the following code (excerpt):
这可以使用以下代码(摘录)来实现:
// Both color gradients
private Shader shader1 = new LinearGradient(0, 400, 0, 500, Color.rgb(59, 242, 174), Color.rgb(101, 172, 242), Shader.TileMode.CLAMP);
private Shader shader2 = new LinearGradient(0, 400, 0, 500, Color.rgb(59, 242, 174), Color.rgb(255, 31, 101), Shader.TileMode.CLAMP);
private Paint paint = new Paint();
// ...
@Override
protected void onDraw(Canvas canvas) {
float width = 800;
float height = 800;
float radius = width / 3;
// Arc Image
Bitmap.Config cOnf= Bitmap.Config.ARGB_8888; // See other config types
Bitmap mImage = Bitmap.createBitmap(800, 800, conf); // This creates a mutable bitmap
Canvas imageCanvas = new Canvas(mImage);
// Draw both rectangles
paint.setShader(shader1);
imageCanvas.drawRect(0, 0, 400, 800, paint);
paint.setShader(shader2);
imageCanvas.drawRect(400, 0, 800, 800, paint);
// /Arc Image
// Draw the rectangle image
canvas.save();
canvas.drawBitmap(mImage, 0, 0, null);
canvas.restore();
}
As your goal is having a colored arc with rounded caps, we next need to define the area of both rectangles that should be visible to the user. This means that most of both rectangles will be masked away and thus not visible. Instead the only thing to remain is the arc area.
由于您的目标是使用带圆角的彩色圆弧,我们接下来需要定义应该对用户可见的两个矩形区域。这意味着两个矩形的大部分将被遮盖掉,因此不可见。相反,唯一要保留的是弧形区域。
The result should look like this:
结果应如下所示:
In order to achieve the needed behavior we define a mask that only reveals the arc area within the rectangles. For this we make heavy use of the setXfermode
method of Paint
. As argument we use different instances of a PorterDuffXfermode
.
为了实现所需的行为,我们定义了一个只显示矩形内弧面的掩模。为此,我们大量使用Paint的setXfermode方法。作为参数,我们使用PorterDuffXfermode的不同实例。
private Paint maskPaint;
private Paint imagePaint;
// ...
// To be called within all constructors
private void init() {
// I encourage you to research what this does in detail for a better understanding
maskPaint = new Paint();
maskPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
imagePaint = new Paint();
imagePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OVER));
}
@Override
protected void onDraw(Canvas canvas) {
// @step1
// Mask
Bitmap mMask = Bitmap.createBitmap(800, 800, conf);
Canvas maskCanvas = new Canvas(mMask);
paint.setColor(Color.WHITE);
paint.setShader(null);
paint.setStrokeWidth(70);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setAntiAlias(true);
final RectF oval = new RectF();
center_x = 400;
center_y = 400;
oval.set(center_x - radius,
center_y - radius,
center_x + radius,
center_y + radius);
maskCanvas.drawArc(oval, 135, 270, false, paint);
// /Mask
canvas.save();
// This is new compared to step 1
canvas.drawBitmap(mMask, 0, 0, maskPaint);
canvas.drawBitmap(mImage, 0, 0, imagePaint); // Notice the imagePaint instead of null
canvas.restore();
}
This solves your first problem. The second one can be achieved using masks again, though this time we want to achieve something different. Before, we wanted to show only a specific area (the arc) of the background image (being the two rectangles). This time we want to do the opposite: We define a background image (the thumb) and mask away its inner content, so that only the stroke seems to remain. Applied to the arc image the thumb overlays the colored arc with a transparent content area.
这解决了您的第一个问题。第二个可以再次使用蒙版实现,但这次我们想要实现不同的东西。之前,我们只想显示背景图像的特定区域(弧形)(是两个矩形)。这次我们想要做相反的事情:我们定义一个背景图像(拇指)并掩盖它的内部内容,这样只留下笔画。应用于弧形图像的拇指覆盖着色弧,具有透明的内容区域。
So the first step would be drawing the thumb. We use an arc for this with the same radius as the background arc but different angles, resulting in a much smaller arc. But becaus the thumb should "surround" the background arc, its stroke width has to be bigger than the background arc.
所以第一步就是画拇指。我们使用弧形,其半径与背景弧相同但角度不同,因此弧度更小。但是因为拇指应该“环绕”背景弧,它的笔划宽度必须大于背景弧。
@Override
protected void onDraw(Canvas canvas) {
// @step1
// @step2
// Thumb Image
mImage = Bitmap.createBitmap(800, 800, conf);
imageCanvas = new Canvas(mImage);
paint.setColor(Color.WHITE);
paint.setStrokeWidth(120);
final RectF oval2 = new RectF();
center_x = 400;
center_y = 400;
oval2.set(center_x - radius,
center_y - radius,
center_x + radius,
center_y + radius);
imageCanvas.drawArc(oval2, 270, 45, false, paint);
// /Thumb Image
canvas.save();
canvas.drawBitmap(RotateBitmap(mImage, 90f), 0, 0, null);
canvas.restore();
}
public static Bitmap RotateBitmap(Bitmap source, float angle)
{
Matrix matrix = new Matrix();
matrix.postRotate(angle);
return Bitmap.createBitmap(source, 0, 0, source.getWidth(), source.getHeight(), matrix, true);
}
The result of the code is shown below.
代码的结果如下所示。
So now that we have a thumb that is overlaying the background arc, we need to define the mask that removes the inner part of the thumb, so that the background arc becomes visible again.
所以现在我们有一个覆盖背景弧的拇指,我们需要定义去除拇指内部的遮罩,以便背景弧再次可见。
To achieve this we basically use the same parameters as before to create another arc, but this time the stroke width has to be identical to the width used for the background arc as this marks the area we want to remove inside the thumb.
为了实现这一点,我们基本上使用与之前相同的参数来创建另一个弧,但这次笔划宽度必须与用于背景弧的宽度相同,因为这标记了我们想要在拇指内移除的区域。
Using the following code, the resulting image is shown in picture 4.
使用以下代码,生成的图像如图4所示。
@Override
protected void onDraw(Canvas canvas) {
// @step1
// @step2
// Thumb Image
// ...
// /Thumb Image
// Thumb Mask
mMask = Bitmap.createBitmap(800, 800, conf);
maskCanvas = new Canvas(mMask);
paint.setColor(Color.WHITE);
paint.setStrokeWidth(70);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
final RectF oval3 = new RectF();
center_x = 400;
center_y = 400;
oval3.set(center_x - radius,
center_y - radius,
center_x + radius,
center_y + radius);
maskCanvas.drawBitmap(mImage, 0, 0, null);
maskCanvas.drawArc(oval3, 270, 45, false, paint);
// /Thumb Mask
canvas.save();
canvas.drawBitmap(RotateBitmap(mMask, 90f), 0, 0, null); // Notice mImage changed to mMask
canvas.restore();
}
The last part of your question would be animating the movement of the arc. I have no solid solution for this, but maybe can guide you in a useful direction. I would try the following:
问题的最后一部分是动画弧的运动。我没有可靠的解决方案,但也许可以引导你朝着有用的方向前进。我会尝试以下方法:
First define the thumb as a ImageView
that is part of your whole arc graph. When changing the selected values of your graph, you rotate the thumb image around the center of the background arc. Because we want to animate the movement, just setting the rotation of the thumb image would not be adequate. Instead we use a RotateAnimation
kind of like so:
首先将拇指定义为ImageView,它是整个弧形图的一部分。更改图形的选定值时,可以围绕背景弧的中心旋转拇指图像。因为我们想要为运动设置动画,所以仅设置拇指图像的旋转是不够的。相反,我们使用RotateAnimation类似:
final RotateAnimation animRotate = new RotateAnimation(0.0f, -90.0f, // You have to replace these values with your calculated angles
RotateAnimation.RELATIVE_TO_SELF, // This may be a tricky part. You probably have to change this to RELATIVE_TO_PARENT
0.5f, // x pivot
RotateAnimation.RELATIVE_TO_SELF,
0.5f); // y pivot
animRotate.setDuration(1500);
animRotate.setFillAfter(true);
animSet.addAnimation(animRotate);
thumbView.startAnimation(animSet);
This is far from final I guess, but it very well may aid you in your search for the needed solution. It is very important that your pivot values have to refer to the center of your background arc as this is the point your thumb image should rotate around.
这远非最终我想,但它可以帮助您寻找所需的解决方案。非常重要的是,您的枢轴值必须引用背景弧的中心,因为这是拇指图像应该旋转的点。
I have tested my (full) code with API Level 16 and 22, 23, so I hope that this answer at least gives you new ideas on how to solve your problems.
我已经使用API级别16和22,23测试了我的(完整)代码,所以我希望这个答案至少可以为您提供有关如何解决问题的新想法。
Please note that allocation operations within the onDraw
method are a bad idea and should be avoided. For simplicity I failed to follow this advise. Also the code is to be used as a guide in the right direction and not to be simply copy & pasted, because it makes heavy use of magic numbers and generally does not follow good coding standards.
请注意,onDraw方法中的分配操作是个坏主意,应该避免。为简单起见,我没有遵循这个建议。此外,代码将被用作正确方向的指南,而不是简单地复制和粘贴,因为它大量使用魔术数字,并且通常不遵循良好的编码标准。
1
I would change a bit of the way you draw your view, by looking on the original design, instead of drawing 3 caps I would draw just 1 line, that way the SweepGradient
will work.
我会改变你绘制视图的方式,通过查看原始设计,而不是绘制3个大写,我只绘制1行,这样SweepGradient将起作用。
This migth be a bit tricky, you have 2 options:
这个迁移有点棘手,你有两个选择:
Path
with 4 arcs创建一个包含4个弧的路径
Paint.Style.STROKE
) and another on top of that make it fill transparent, you can achieve it with PorterDuff xfermode, it probably take you couple of tries until you get that without clearing the green circle too.绘制2个弧 - 一个是大白色(填充白色所以你仍然想要使用Paint.Style.STROKE)而另一个在它之上使它填充透明,你可以用PorterDuff xfermode实现它,它可能需要你几个尝试直到你得到它而不清除绿色圆圈。
I imagine you want to animate thumb position, so just use simple Animation
that invalidate the view and draw the thumb view position accordingly.
我想你想要为拇指位置设置动画,所以只需使用简单的动画来使视图无效并相应地绘制拇指视图位置。
Hopes this helps
希望这会有所帮助
1
Create a gradient than follow a path is not so simple. So I can suggest you to use some libraries than already did it.
创建渐变而不是遵循路径并不是那么简单。所以我建议你使用一些库而不是它。
Include the library:
包括图书馆:
dependencies {
...
compile 'com.github.paroca72:sc-gauges:3.0.7'
}
Create the gauge in XML:
用XML创建仪表:
Your code:
ScArcGauge gauge = this.findViewById(R.id.gauge);
gauge.setAngleSweep(270);
gauge.setAngleStart(135);
gauge.setHighValue(90);
int lineWidth = 50;
ScCopier baseLine = gauge.getBase();
baseLine.setWidths(lineWidth);
baseLine.setColors(Color.parseColor("#dddddd"));
baseLine.getPainter().setStrokeCap(Paint.Cap.ROUND);
ScCopier progressLine = gauge.getProgress();
progressLine.setWidths(lineWidth);
progressLine.setColors(
Color.parseColor("#65AAF2"),
Color.parseColor("#3EF2AD"),
Color.parseColor("#FF2465")
);
progressLine.getPainter().setStrokeCap(Paint.Cap.ROUND);
Your result:
You can find something more complex on this site: ScComponents
您可以在此站点上找到更复杂的内容:ScComponents