In the course of developing an Android application, I'm finding a need to draw
several unfilled concentric circles centered on an arbitrary point, enough that
some of them are only partly visible on the display. However, this does not
appear to work with hardware acceleration. My test rig is a stock Samsung Galaxy
Tab 10.1 running Android 3.2.
The following code comes from a test subclass of View I wrote to isolate the
issue:
private Paint paint = new Paint();
private int count = 0;
private static final int[] COLORS = { 0xffff0000, 0xff00ff00, 0xff0000ff, 0xffff00ff };
public TestCircles(Context context) {
super(context);
paint.setStrokeWidth(1.0f);
paint.setStyle(Paint.Style.STROKE);
}
public TestCircles(Context context, AttributeSet attributes) {
super(context, attributes);
paint.setStrokeWidth(1.0f);
paint.setStyle(Paint.Style.STROKE);
}
public boolean onTouchEvent(MotionEvent e) {
if (e.getAction() == MotionEvent.ACTION_DOWN)
invalidate();
return true;
}
protected void onDraw(Canvas canvas) {
// Pick the color to use, cycling through the colors list repeatedly, so that we can
// see the different redraws.
paint.setColor(COLORS[count++]);
count %= COLORS.length;
// Set up the parameters for the circles; they will be centered at the center of the
// canvas and have a maximum radius equal to the distance between a canvas corner
// point and its center.
final float x = canvas.getWidth() / 2f;
final float y = canvas.getHeight() / 2f;
final float maxRadius = (float) Math.sqrt((x * x) + (y * y));
// Paint the rings until the rings are too large to see.
for (float radius = 20; radius < maxRadius;
radius += 20)
canvas.drawCircle(x, y, radius, paint);
}
I am running TestCircles as the only View in an Activity, laying it out to fill
the available width and height (i.e. it is nearly full-screen). I can tap on
the display (triggering redraws) only a few times before the redraws no longer
occur (i.e. the circles' color doesn't change). Actually, the onDraw() code is
still running in response to each tap -- as proven with diagnostic messages --
but nothing changes onscreen.
When onDraw() first starts to fail to redraw, the debug log includes the
following entry, once for every call to onDraw():
E/OpenGLRenderer(21867): OpenGLRenderer is out of memory!
If I turn off hardware acceleration in the manifest, these problems go away --
not surprising since clearly OpenGL is having problems -- and actually it is
a good deal faster than the few times it actually works under hardware
acceleration.
My questions are:
Am I misusing Canvas, or is this a bug, or both? Is Android allocating large
bitmaps under the hood to draw these circles? It doesn't seem like this should be
this challenging to OpenGL, but I'm new to hardware accelerated app development.
What's a good alternative way to draw large unfilled circles that have portions
extending out of the clipping region of the Canvas? Losing hardware acceleration
is not an option.
Thanks in advance...
I've since learned from others that the problem I described here is the result of a bug in Android 3.2. The workaround for now is of course to use a software layer instead of hardware acceleration. Apparently this problem is fixed in Android 4.0 (Ice Cream Sandwich).
Related
I'm trying to draw a 180° arc within an android view.
In the onDraw method I'm using Canvas::drawArc like this:
canvas.drawArc(arcRect, 180.0f, 180.0f, false, arcBackgroundPaint);
The Paint that is being used has a strokeCap of type BUTT. As you can see in the image below, the ends of the arc do not look quite right. The ends are angled up slightly from the inner diameter to the outer diameter.
Does anyone know why this is happening? Is there a way to fix it other than changing the values passed to drawArc so that it actually draws more than 180 degrees? That seems like a nasty hack that I'd rather avoid.
UPDATE:
As requested, I added code to draw a straight line at the bottom of the arc. I draw the line before I draw the arc so the line is behind. I also made the arc black to get better contrast with the line. Here is the outcome:
Yuck.
Here's one with anti-aliasing turned off for the line and arc...
drawArc incorrect angles problem will occur if hardware acceleration is on.
Reproduced with real phone (Nexus 5, Android 6.0.1).
How to test:
Add android:hardwareAccelerated="false" to application tag in AndroidManifest.xml.
NB:
First supported API level of drawArc() is not found in the document, but scaling function of it is from API17.
Hardware Acceleration > Canvas Scaling
https://developer.android.com/guide/topics/graphics/hardware-accel.html
First supported API level
Simple Shapes: 17
Note: 'Simple' shapes are drawRect(), drawCircle(), drawOval(), drawRoundRect(), and drawArc() (with useCenter=false) ...
Updated:
As PICyourBrain wrote in another answer, drawArc() is a part of reasons this problem occurs.
With drawPath(), this problem does not occur.
For information:
Not a same shape but drawArc() with useCenter=true also seems safe to use, as hardware acceleration is not used for it. (Ends of the arc and center of it are connected with line, and the line seems straight.)
Canvas Scaling (same link above)
First supported API level
Complex Shapes: x
Other Q&As related to drawArc()
Canvas.drawArc() artefacts
What's this weird drawArc() / arcTo() bug (graphics glitch) in Android?
[Old]
I'll leave these for your information.
Here are some test results I've tried.
Updated 1-1:
With emulator Nexus5 API10, with arcRect = new RectF(100, 100, 1500, 1400);.
With emulator Nexus5 API16, this does not happen.
(The difference is existence of (host side) hardware acceleration.)
I thought this seems to be an anti-alias related problem, but this happens no matter arcBackgroundPaint.setAntiAlias(true) or setDither(true) are set or not.
NB: This is caused by typo, sorry. Aspect ratio of arcRect should be 1:1 for this test.
With arcRect = new RectF(100, 100, 1400, 1400); and arcPenWidth = 200f;
With arcRect = new RectF(100, 100, 1500, 1300);
1-2: for comparison
With emulator NexusOne (480x800, HDPI) API10, with arcRect = new RectF(100, 100, 500, 500);
Updated 2: Line extended.
I first thought drawing out of view may cause this, but this is also a emulator bug. (Not a drawArc() specific behavior.)
With emulator API10 in landscape(horizontal) orientation, this occurs.
Calculation of line position seems broken.
Please see the right end of the straight line.
final float lineStartX = arcRect.left - 250f;
final float lineEndX = arcRect.right + 250f;
emulator Nexus5 API10
horizontal (API10)
vertical (API10)
2-2: Just for a information
View odd behavior sample of view position (or drawing position) out of range.
Updated 3: Emulator bug
Please see the bottom of the image.
Blue line is a background image of desktop.
emulator Nexus5 API10
Update 4: The result seems to depend on style.
With title bar
Without title bar
Update 5: The result seems to depend on line width.
With arcPenWidth = 430f (API10, horizontal)
Slight notch on the right side is seen.
With 440f
With 450f
Here's my (first) test code.
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;
final class TestView extends View
{
RectF arcRect;
Paint arcBackgroundPaint;
Paint linePaint;
public TestView(final Context context, final AttributeSet attrs)
{
super(context, attrs);
//arcRect = new RectF(100, 100, 500, 500);
arcRect = new RectF(100, 100, 1500, 1500); // fixed (old: 1500, 1400)
arcBackgroundPaint = new Paint();
arcBackgroundPaint.setColor(0xFFFFFFFF);
arcBackgroundPaint.setStyle(Paint.Style.STROKE);
arcBackgroundPaint.setStrokeCap(Paint.Cap.BUTT);
arcBackgroundPaint.setStrokeWidth(200f);
linePaint = new Paint();
linePaint.setColor(0xFF00FF00);
linePaint.setStyle(Paint.Style.STROKE);
linePaint.setStrokeCap(Paint.Cap.BUTT);
linePaint.setStrokeWidth(2f);
}
#Override
protected void onDraw(final Canvas canvas)
{
super.onDraw(canvas);
canvas.drawArc(arcRect, 180.0f, 180.0f, false, arcBackgroundPaint);
final float lineStartX = arcRect.left - 50f;
final float lineEndX = arcRect.right + 50f;
final float lineY = arcRect.centerY();
canvas.drawLine(lineStartX, lineY, lineEndX, lineY, linePaint);
}
}
For anyone else who comes across this I did find a workaround. Instead of using Canvas::drawArc, create a Path, add an arc to it and then draw the path on the canvas. Like this:
Path arcPath = new Path();
Rect arcRect = ... // Some rect that will contain the arc
path.addArc(arcRect, 180, 180);
canvas.drawPath(arcPath, backgroundArcPaint);
Then the rendering problem highlighted no longer occurs.
Since my question was not "how do I fix the problem" but rather "why is this a problem" I am not marking this as the correct answer.
I need to draw line graph with lakes of points in canvas rendering. I used following ways to optimize the performance,
Avoided to create object in onDraw method.
Using drawLines method in canvas rather than using path.moveTo and path.lineTo, since, my thought is path is always render using CPU not GPU.
Removed line anti-alias.
Not set alpha value for line color.
Set HardwareAcceleration to true.
Using some sampling algorithms.
This is what i actually doing after read some performance tips to get good performance, If you know to make even better please suggest to me.
Instead of drawLine you can use drawRect. And use SurfaceView. (Quite honestly, I don't like SurfaceView for some limitations it has and organise back buffers myself, but most of developers prefer it).
This is what comes to my mind (without testing and thinking much).
Suppose you have a table grpData[] of float data, each in range 0f to 1f. You also have a table of colours (not colour resource Ids) grpColours.
Them your code will look smth like this:
private Paint p = new Paint();
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas); // Draws background
int width = getWidth();
int height = getHeight();
int count = grpData.length;
float xStep = (float) width / count;
float x=0f;
for (int i=0; i<count, i++) {
float nextX = x+xStep;
p.setColor(grpColours[i]);
canvas.drawRect(x, (1f-grpData[i])*height, nextX, height, p);
x = nextX;
}
}
PS. I am not sure that hardware acceleration really helps. On the contrary it may cause problems with some cheap devices.
I'm working on a custom view for an android application, similar to the Analog Gauge sample code available from Mind the Robot.
Running the code from listed site, I get see this on my screen:
(Motorola Droid, 2.2.3), (Emulator, 4.0.3)
(Xoom, 4.0.3)(Other phone, 4.0.3)
The hand is missing!
The drawing calls are being made (I can see them in logcat), but the canvas elements the calls draw are invisible.
It's not API level dependent, though; if I import it the right way into a project, it will hand will show up when I run it on the Xoom.
But, when I move the files to a different project folder (same source code, same layouts) it goes back to missing the dial.
What's going on? How could the same code be producing such different outcomes on different devices?
So, the key clue in my mystery seemed to be that it worked on the emulator, but not on the hardware devices.
Hardware Rendering
I did peruse the hardware rendering page on the Android Developer's website, but apparently not closely enough.
http://developer.android.com/guide/topics/graphics/hardware-accel.html
While it does mention that the API's are available beginning version 11, it does not say that Hardware Rendering is turned on for all applications by default, starting with API Level 14 (ICS).
What does this mean for us?
Almost everything is faster; except for the few things that don't work.
I managed to violate two of these, without realizing it:
Canvas.DrawTextOnPath()
Paint.setShadowLayer()
It's not mentioned in the API reference (or anywhere else I can find, and certainly not checked by Lint), but using any of the listed operations can do weird things.
In my case, Canvas.DrawTextOnPath() seemed to work just fine.
But when Android notice that the paint that I used on the hand had shadow layer set, it silently ignored it.
How do I know if my View is hardware accelerated?
From the documentation link above:
There are two different ways to check whether the application is hardware accelerated:
View.isHardwareAccelerated() returns true if the View is attached to a hardware accelerated window.
Canvas.isHardwareAccelerated() returns true if the Canvas is hardware accelerated
If you must do this check in your drawing code, use Canvas.isHardwareAccelerated() instead >of View.isHardwareAccelerated() when possible. When a view is attached to a hardware >accelerated window, it can still be drawn using a non-hardware accelerated Canvas. This >happens, for instance, when drawing a view into a bitmap for caching purposes.
In my case, the opposite appears to have occurred.
The custom view logs that it is not Hardware-accelerated; however, the canvas reports that it is hardware-accelerated.
Work Arounds and Fixings
The simplest fix is forcing the custom view to do software rendering. Per the documentation this can be accomplished by:
myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
Alternatively, you could remove the offending operations, and keep hardware rendering turned on.
Learn from my misfortune. Good luck, all.
I put it into init() and worked fine after that.
private void init() {
setLayerType(myView.LAYER_TYPE_SOFTWARE, null);
....
}
With myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null); suggestion I can see hand. But I have still a problem: I see scale with only 0 written! As in the picture and two strage zeros out of the schema: (GALAXY NEXUS 4.2.1)
My drawScale() method is as in the example:
private void drawScale(Canvas canvas) {
canvas.drawOval(scaleRect, scalePaint);
canvas.save(Canvas.MATRIX_SAVE_FLAG);
for (int i = 0; i < totalNicks; ++i) {
float y1 = scaleRect.top;
float y2 = y1 - 0.020f;
canvas.drawLine(0.5f, y1, 0.5f, y2, scalePaint);
if ((i % 5) == 0) {
int value = nickToDegree(i);
if ((value >= minDegrees) && (value <= maxDegrees)) {
String valueString = Integer.toString(value);
canvas.drawText(valueString, 0.5f, y2 - 0.015f, scalePaint);
}
}
canvas.rotate(degreesPerNick, 0.5f, 0.5f);
}
canvas.restore();
}
in my case i made this:
AnalogView bar = (AnalogView) findViewById(R.id.AnalogBar);
bar.setLayerType(bar.LAYER_TYPE_SOFTWARE, null);
if (value_list.size()>0) bar.SetData(Double.parseDouble(value_list.get(value_list.size()-1)));
where SetData in AnalogView is
public void SetData(double data) {
setHandTarget((float)data);
invalidate();
}
On Galaxy S4 Android 4.4.2
TYPE_TEMPERATURE is deprecated
use
TYPE_AMBIENT_TEMPERATURE
For anyone having problems with text drawing on scale in the initialisation do this:
scalePaint.setLinearText(true);
Hi I'm trying to develop a Field Test Application and i've to retrieve information like signal strength of neighboring cells.
So my question is:
How can I display a graph with the different neighboring cells on
X-axis and the signal strength on Y-axis in real time? An example
here.
I've already got 5 or 6 neighboring cells and for each one his signal strength.
Rather drawning the graph your self manually using Canvas, You can use Chart Engine Libraries available and that will be much easier to do also.
Like AchartEngine,ChartDroid,aFreeChart,MPAndroidChart
For 3D Chart Charts4J
How can I display a graph with the different neighboring cells on
X-axis and the signal strength on Y-axis in real time?
I have used aChart Engine for the same in one of my application. There is a complete API demo available with the library so it will be pretty easy to understand how to use that.
I don't know which type of graph you want to develop because there are different types at your link. But I've developed a real time line graph in android. I'm using canvas for drawing lines.
public class GraphView extends View
{
...
private final Rect rect = new Rect();
private final Paint linePaint = new Paint();
private final Paint backgroundPaint = new Paint();
private float[] points;
public GraphView(final Context context, final AttributeSet aSet)
{
super(context, aSet);
}
#Override
protected void onDraw(final Canvas canvas)
{
if (points == null)
{
return;
}
canvas.drawLines(points, linePaint);
rect.set((int) (xIndex * xScale), 0, (int) (xIndex * xScale + 5), getHeight());
canvas.drawRect(rect, backgroundPaint);
}
...
}
You can easily position/size your rect according to your needs. I didn't wrote the calculations of xIndex and xScale. The points array is the one which your values will be written.
But beware, in android lines are drawn with pairs, there is no 'point' structure as I know.
I mean [1, 0.25, 2, 0.45] draws a line between x1= 1, y1=0.25 and x2=2, y2= 0.45
Also you can trigger draw by postInvalidate()
postInvalidate()
onDraw (Canvas canvas)
I suggest you use AChartEngine rather than drawing to canvas.
You can download the library, javadocs and a demo application here.
There are tutorials on youtube on getting started with AChartEngine.
You can use line charts with the area below chart filled with a color or not filled for copying the functionality in the first screenshots you provided.
so I'm just starting to learn how to create live wallpapers in eclipse and I'm having trouble getting a simple line to move randomly across the screen after a random amount of time, sort of like a shooting star. I think my stop and start is wrong also... I was trying to set a length limit for the line...
I'm using the CubeLiveWallpaper as a template
/*
* Draw a line
*/
void drawCube(Canvas c) {
c.save();
c.drawColor(0xff000000);
drawLine(c);
c.restore();
}
/*
* Line path
*/
void drawLine(Canvas c) {
// Move line across screen randomly
//
float startX = 0;
float startY = 0;
float stopX = 100;
float stopY = 100;
c.drawLine(startX, startY, stopX, stopY, mPaint);
}
This is a pretty open-ended question. I'll try to give you some pointers. :-)
First of all, with all due respect to our good buddies at Google, the Cube example does not always present "best practice." Most notably, you should "never" use hard-coded constants in your wallpaper...always use a proportion of your screen size. In most cases, it's "good enough" to save the width and height variables from onSurfaceChanged() into class variables. My point is, instead of "100," you should be using things like "mScreenWidth / 4" to indicate one quarter of the width of your device (be it teeny tiny phone or ginormous tablet).
To get random numbers, you can use http://developer.android.com/reference/java/util/Random.html
As for the animation itself, well, you can randomize the rate by randomizing the delay you use to reschedule your runnable in postDelayed().
By now, you're probably wondering about the "tricky" part...drawing the line itself. :-) I suggest starting with something very simple, and adding complexity as you eyeball things. Let's say, fr'instance you generate random start and finish points, so that your final stroke will be
c.drawLine(startX, startY, stopX, stopY, mPaint);
Presumably, you will want to draw a straight line, which means maintaining a constant slope. You could set up a floating point "percentage" variable, initialized to zero, and each time through the runnable, increment it by a random amount, so that at each pass it indicates the "percentage" of the line you wish to draw. So each call in your runnable would look like
c.drawLine(startX, startY, startX + percentage * deltaX, startY + percentage * deltaX * slope, mPaint);
(where deltaX = stopX - startX)
Obviously, you want to stop when you hit 100 percent.
This is really just a start. You can get as heavy-duty with your animation as you wish (easing, etc.), for instance using a library like this one: http://code.google.com/p/java-universal-tween-engine/
Another option, depending on the effect you're trying to achieve, would be to work with a game engine, like AndEngine. Again, pretty heavy duty. :-)
http://code.google.com/p/andenginelivewallpaperextensionexample/source/browse/
Good luck!