I'm working on some kind of circular view where the Arc is drawn on every onDraw() call. Whole onDraw method looks like this
#Override
public void onDraw(Canvas canvas) {
float beg = 0;
float end;
if (items != null && items.size() > 0 && getSum() != 0)
for (Map.Entry<Object, ItemDescriptor> item : items.entrySet()) {
end = (item.getValue().getScore() / getSum()) * 360;
Log.d("jano", "drawing from " + beg + " to " + (beg + end));
canvas.drawArc(mBounds, beg, end, false, item.getValue().getPaint());
beg += end;
}
else {
canvas.drawArc(mBounds, beg, 360, false, defalutPaint);
}
}
Paint for drawing:
Paint paint = new Paint();
paint.setStyle(Paint.Style.STROKE);
paint.setColor(color);
paint.setStrokeWidth(outerWidth);
ItemDescriptor holds score based on which i can compute how to display circle (composed of some Arcs). Log output is always OK, but Arcs are most of time but not always correct. For example see green part on image below.
Can somebody of you give me some inspiration how to display it always correct? Whole project is on GitHub as Android Studio poject
Hmm I'm sorry, it just looks like the Genymotion not displaying this correctly. Real device and Android emulator displays the view without errors.
Related
I'm drawing 3 things in my custom view in the onDraw() method: a vector drawable, a simple line and a triangle (made from 4 Points and a Path). This custom view is displayed in a tab.
If I swipe to go to another tab I see that the system calls onDraw(). When I return to the tab holding my custom view the vector drawable and simple line are still visible but the triangle has disappeared. If I now swipe to another tab, onDraw() runs again and back in the tab with the custom view, all items (including the triangle) are now visible. This disappearing/appearing continues to happen as I swipe back and forth. Why is my triangle disappearing?
UPDATE 1 (hacky fix):
I've tried experimenting and notice that when I move my triangle Path object creation out of my init() method and put it directly in the onDraw() method - then all works well, nothing disappears. But, I now get the 'Avoid object allocations during draw' warning as I'm creating this object in onDraw();
UPDATE 2 (better fix?):
After more experimenting, it's definitely the Path causing this problem. Another solution to this - which doesn't incur the 'Avoid object allocations during draw' warning is: keep Path creation in init() and remove the line of code 'myPath.setFillType(Path.FillType.EVEN_ODD)'. It solves my problem, but I've no idea why.
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// Co-ordinates
int width = getWidth();
int halfWidth = width/2;
int left = 0;
int top = 0;
int centreX = left + halfWidth;
int centreY = top + halfWidth;
int baseSize = Math.round((float)(halfWidth * 0.05));
// Vector drawable - always draws fine!
myVectorDrawable.setBounds(left, top, left + width, top + width);
myVectorDrawable.draw(canvas);
// Simple line - always draws fine!
canvas.drawLine(left, top, 20, 20, paint);
// Triangle - sometimes visible, sometimes disappears!
Point myTriangleBottomMiddle = new Point(centreX, centreY);
Point myTriangleBottomLeft = new Point(centreX, centreY + baseSize);
Point myTriangleBottomRight = new Point(centreX, centreY - baseSize);
Point myTriangleTopMiddle = new Point(centreX + halfWidth, centreY);
myPath.moveTo(myTriangleBottomMiddle.x, myTriangleBottomMiddle.y);
myPath.lineTo(myTriangleBottomLeft.x, mTriangleBottomLeft.y);
myPath.lineTo(myTriangleTopMiddle.x, myTriangleTopMiddle.y);
myPath.lineTo(myTriangleBottomRight.x, myTriangleBottomRight.y);
mPath.close();
canvas.drawPath(myPath, myPaint);
}
Below the code where I set up stuff so as not to burden the onDraw() method.
private void init() {
// Vector drawable
myVectorDrawable = r.getDrawable(R.drawable.gauge_dial);
// Triangle path - ** THIS BEING HERE SEEMS TO BE THE PROBLEM **
myPath = new Path();
myPath.setFillType(Path.FillType.EVEN_ODD);
// Triangle Paint
myPaint = new Paint();
myPaint.setColor(r.getColor(R.color.black));
myPaint.setStrokeWidth(2);
myPaint.setAntiAlias(true);
myPaint.setStyle(Paint.Style.FILL);
// Simple line paint
Paint paint = new Paint();
paint.setColor(Color.BLACK);
}
Add a call to reset() on the path.
// Triangle - sometimes visible, sometimes disappears!
Point myTriangleBottomMiddle = new Point(centreX, centreY);
Point myTriangleBottomLeft = new Point(centreX, centreY + baseSize);
Point myTriangleBottomRight = new Point(centreX, centreY - baseSize);
Point myTriangleTopMiddle = new Point(centreX + halfWidth, centreY);
myPath.reset();
myPath.moveTo(myTriangleBottomMiddle.x, myTriangleBottomMiddle.y);
myPath.lineTo(myTriangleBottomLeft.x, mTriangleBottomLeft.y);
myPath.lineTo(myTriangleTopMiddle.x, myTriangleTopMiddle.y);
myPath.lineTo(myTriangleBottomRight.x, myTriangleBottomRight.y);
mPath.close();
canvas.drawPath(myPath, myPaint);
Also, I would recommend putting the vector drawable into a separate view so it's not redrawn everytime you need to animate the triangle (assuming this is going to be an animated guage dial).
Ok, I am developing a sidescrolling game and my problem is on how to properly draw and update the screen. I am drawing on a SurfaceView and I use Path to make the contourns, currently the algorithm only draws this:
And I am sidescrolling by using Path.offSet() and then canvas.drawPath(), later on I update the last X position on the path by using Path.addRect() (and thats basically how I am drawing everything: using Path.addRect())
So here is the thread that updates the screen:
#Override
public void run() {
int x = LibraryLoader.getTerrainSizeX();
int y = LibraryLoader.getTerrainSizeY();
int count = 0;
Paint paint = new Paint();
paint.setStyle(Paint.Style.FILL);
Path path = new Path();
makePath(path, x, y, 0, LibraryLoader.getTerrainThickness());
Path path2 = new Path();
makePath(path2, x, y, LibraryLoader.getTerrainThickness(), y);
while (run) {
Canvas c = null;
try {
c = surfaceHolder.lockCanvas(null);
synchronized (surfaceHolder) {
fps = fps();
drawMyData(c, path, path2, paint, fps);
LibraryLoader.updateOffSet();
updatePaths(path, path2, x, y);
if ((count++) == (x / 2) - 1) {
LibraryLoader.updateOffSetArray();
count = 0;
}
}
} finally {
if (c != null) {surfaceHolder.unlockCanvasAndPost(c);}
}
}
and its respective methods:
public void updatePaths(Path path, Path path2, int x, int y) {
path.offset(-1f, 0);
path.addRect(x-3, topValue, x-2, bottomValue, Path.Direction.CW);
path2.offset(-1f, 0);
path2.addRect(x-3, topValue, x-2, y, Path.Direction.CW);
}
So, in my phone it works perfectly at 60fps, the problem is I tested in a lower end device and it begins at 40fps then drops every update until it gets below 10fps...(and keeps dropping). I guess I need to clean the state of the path, or I shouldn't even be using the Path class to begin with. So my question is how should I update the screen with the best performance? Obs: The canvas is not hardware accelerated.
Well folks I figured out that I was wrong about everything I did. The answer is simple: If your android application updates the whole screen every frame, use Opengl. Canvas is for app design for what I've seen, hope I am not mistaken. For example, if you want to make a custom animation for a LOGO or a button, so you use canvas, I guess. If anyone stumbles in this post do watch the videos Morrison Chang mentioned, they are very helpful to put you on the right track. Cheers.
I am working on a project in Android that builds Abelian Sandpiles (a type of 2D cellular Automaton). I have a grid of cells (that starts out small but later grows) and I'm drawing square (or circles) on the grid to show the state of each cell. At each step, I update usually less than 30% of the cells.
My basic approach is taken from this post: Android: How to get a custom view to redraw partially?
I draw all the shapes to the canvas and cache it as a bitmap, then at each step I update only the cells that need updating, then cache the result and repeat. This works well enough when the grid is fairly small (less than 50 x 50), but becomes increasingly unsatisfactory as the grid size increase. The problems are that
1) it is too slow when the number of updates becomes high
2) even when it runs smoothly, the drawing doesn't look clean - e.g., a line of small rectangles looks quite choppy and inconsistent (see image).
I am sure that there must be a better way to approach this problem. With a larger grid (e.g., 150 x 150), I'm drawing rects or circles with a width of ~2.33, and this can't be optimal. Any advice for improving performance and/or image quality?
Simplified drawing code is here:
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (ready) {
if (needsCompleteRedraw) {
this.cachedBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
cacheCanvas = new Canvas(this.cachedBitmap);
doInitialDrawing(cacheCanvas);
canvas.drawBitmap(this.cachedBitmap, 0, 0, null);
needsCompleteRedraw = false;
} else {
canvas.drawBitmap(this.cachedBitmap, 0, 0, null);
doPartialRedraws(cacheCanvas);
}
}
}
private void doInitialDrawing(Canvas clean) {
for (int i = 0; i < pile.gridHeight; i++) {
for (int j = 0; j < pile.gridWidth; j++) {
int state = pile.getGridValueAtPoint(new Point(j, i ));
clean.drawRect(j * cellSize, i * cellSize, (j + 1 )* cellSize, (i + 1) * cellSize, paints[state]);
}
}
}
private void doPartialRedraws(Canvas cached) {
for (Point p : pile.needsUpdateSet) {
int state = pile.getGridValueAtPoint(p);
cached.drawRect(p.x * cellSize, p.y * cellSize, (p.x + 1 )* cellSize, (p.y + 1) * cellSize, paints[state]);
}
pile.needsUpdateSet = new HashSet<Point>();
}
and my paint objects are set with antialias as true, and I've tried setting the style to both FILL and FILL_AND_STROKE.
Any suggestions would be much appreciated.
Ok. Several problems here.
1)Don't ever create a canvas in onDraw. If you think you need to, you haven't architected your drawing code correctly.
2)The point of doing draws to a cache bitmap is NOT to draw the cache in onDraw, but to do it on its own thread- or at least not at draw time.
You should have a second thread that draws to the cached bitmap on demand, then calls postInvalidate() on the view. The onDraw function should only be a call to drawBitmap, drawing the cached bitmap to the screen.
I current have a custom view that I override the onDraw and draw an arc. I want to draw text within this arc. To do this, I use drawTextOnPath and this display curved text at the top of the arc. However, sometimes the text is quite long, so I want to allow it to go on to multiple lines.
I currently use code like this to draw on to multiple lines: -
textView.getPaint().getTextBounds(s, 0,
s.length(), r);
int yOffset=r.height() + textSpacing;
int textStart=0;
int numberOfLines= (int) (r.width()/arcWidth) + 1;
for (int i=0; i < numberOfLines; i ++) {
canvas.drawTextOnPath(s.substring(textStart, textStart + s.length() / numberOfLines),
childHolder.path, 0, yOffset, paint);
yOffset+=r.height() +textSpacing;
textStart=s.length()/numberOfLines;
}
However, this obviously doesn't take into account how wide the text is further down the arc. Is there a way of doing this with using something like staticlayout/dynamiclayout (text does change a lot).
If anyone could point me in either something in android SDK I can use, or the maths to calculate the available width
This bit of code solved my issue: -
PathMeasure pm = new PathMeasure(path, false);
layout = new DynamicLayout(spannableText, spannableText, paint, (int) arcPathMeasure.getLength(), Layout.Alignment.ALIGN_CENTER, 1, 0, true);
Thanks pskink!
I'm working on a painting application for Android and I'd like to use raw data from the device's touch screen to adjust the user's paint brush as they draw. I've seen other apps for Android (iSteam, for example) where the size of the brush is based on the size of your fingerprint on the screen. As far as painting apps go, that would be a huge feature.
Is there a way to get this data? I've googled for quite a while, but I haven't found any source demonstrating it. I know it's possible, because Dolphin Browser adds multi-touch support to the Hero without any changes beneath the application level. You must be able to get a 2D matrix of raw data or something...
I'd really appreciate any help I can get!
There are some properties in the Motion Event class. You can use the getSize() method to find the size of the object. The Motion Event class also gives access to pressure, coordinates etc...
If you check the APIDemos in the SDK there's a simple paitning app called TouchPaint
package com.example.android.apis.graphics;
It uses the following to draw on the canvas
#Override public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
mCurDown = action == MotionEvent.ACTION_DOWN
|| action == MotionEvent.ACTION_MOVE;
int N = event.getHistorySize();
for (int i=0; i<N; i++) {
//Log.i("TouchPaint", "Intermediate pointer #" + i);
drawPoint(event.getHistoricalX(i), event.getHistoricalY(i),
event.getHistoricalPressure(i),
event.getHistoricalSize(i));
}
drawPoint(event.getX(), event.getY(), event.getPressure(),
event.getSize());
return true;
}
private void drawPoint(float x, float y, float pressure, float size) {
//Log.i("TouchPaint", "Drawing: " + x + "x" + y + " p="
// + pressure + " s=" + size);
mCurX = (int)x;
mCurY = (int)y;
mCurPressure = pressure;
mCurSize = size;
mCurWidth = (int)(mCurSize*(getWidth()/3));
if (mCurWidth < 1) mCurWidth = 1;
if (mCurDown && mBitmap != null) {
int pressureLevel = (int)(mCurPressure*255);
mPaint.setARGB(pressureLevel, 255, 255, 255);
mCanvas.drawCircle(mCurX, mCurY, mCurWidth, mPaint);
mRect.set(mCurX-mCurWidth-2, mCurY-mCurWidth-2,
mCurX+mCurWidth+2, mCurY+mCurWidth+2);
invalidate(mRect);
}
mFadeSteps = 0;
}
Hope that helps :)
I'm working on something similar, and I'd suggest looking at the Canvas and Paint classes as well. Looking at getHistorySize() in Motion Event might also be helpful for figuring out how long a particular stroke has been in play.