I'm painting text over a background image on a canvas. I move the image interactively (like a Ouija board pointer). I've set the canvas to black, the pointer is red and I want to write white text over it so that the pointer has a player's name on it.
In Android 2.3.4 it appears as solid white text on top of the red pointer which is pretty clear, but I'd like to use any color. In Android 4.1.2 I can barely see the white text. Here's my code:
public Pointer(Context context) {
super(context);
paintBg = new Paint();
paintBg.setColor(Color.BLACK);
paintName = new Paint();
paintName.setColor(Color.WHITE);
paintName.setTextSize(50); // set text size
paintName.setStrokeWidth(5);
paintName.setTextAlign(Paint.Align.CENTER);
this.setImageResource(res); // pointer.png in res/drawable folder
Drawable d = getResources().getDrawable(res);
h = d.getIntrinsicHeight();
w = d.getIntrinsicWidth();
canvas = new Canvas();
canvas.drawPaint(paintBg);//make background black
// float imageScale = width / w; // how image size scales with screen
}
#Override
public void onDraw(Canvas canvas) {
y = this.getHeight() / 2; // center of screen
x = this.getWidth() / 2;
int left = Math.round(x - 0.8f * w);
int right = Math.round(x + 0.8f * w);
canvas.save();
canvas.rotate((direction + 180) % 360, x, y); // rotate to normal
canvas.drawText(s, x, y + 20, paintName); // draw name
canvas.restore();
canvas.rotate(direction, x, y); // rotate back
super.onDraw(canvas);
}
What changed in 4.1.2 that would affect this, or am I doning something incorrectly? Thanks for your help with this as it's driving me crazy.
Edit to include screen shots:
Android 2.3.4
Android 4.1.2
Note how the white text appears to be on top in 2.3.4 while it appears below or muddy in 4.1.2.
As free3dom pointes out it is related to alpha. I do change alpha because if I don't, the text does not appear on top of the arrow. It appears that the ImageView having the pointer image is always on top - could this be what's going on?
Here is how I handle setting alpha:
public static void setAlpha(View view, float alpha, int duration) {
if (Build.VERSION.SDK_INT < 11) {
final AlphaAnimation animation = new AlphaAnimation(alpha, alpha);
animation.setDuration(duration);
animation.setFillAfter(true);
view.startAnimation(animation);
} else //for 11 and above
view.setAlpha(alpha);
}
Maybe it has something to do with using this.setImageResource(res) to set the image resource? According to android developer guide, I can only set alpha to the single view and everything in the view is changed. Yet if I lower the alpha, the arrow image seems to become transparent enough to allow me to see the text.
You set a stroke width, but never indicate that stroke should be used for the Paint.
Try adding
paintName.setStyle( FILL_AND_STROKE );
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).
I'm trying to draw a text to the screen and then have a rectangle in the same spot to account for knowing when the user clicks within it (and clicks the text). Problem is when I put the text in one spot on the screen and the rectangle in the same spot, it apparently isn't in the same spot. Is there some setting I need to set somewhere?
private final String[] options = {"Start", "High Scores", "Help", "Quit"};
public void draw(final Canvas g)
{
for (int i = 0; i < options.length; i++)
{
width = HORIZONTALOFFSET * 3 + spaceInvadersTitle.getWidth();
height = GamePanel.getScreenHeight() / 4 - (75 * 2 + TEXT_SIZE) / 2 + i * 75;
rect[i] = new Rect(width, height, width + 325, height + TEXT_SIZE);
g.drawRect(rect[i], paint);
g.drawText(options[i], width, height, paint);
}
}
FYI "width" and "height" are more like x- and y-coords. The horizontal alignment is not the problem - just the vertical. If you'll notice from the code, I'm setting the starting x- and y-coordinates for the drawText and drawRect the same exact position, but that's not how they're showing on the screen. Instead it seems the drawText is using that position as a lowerleft anchorpoint or something like that. Is that how it works? If so, how do I change that?
Also, if you have any suggestions on how to approach listening for when the user clicks on a drawn text, I'm all ears. This is the easiest way I could think of, and how I do it in regular desktop Java.
I need to draw something like this:
I was hoping that this guy posted some code of how he drew his segmented circle to begin with, but alas he didn't.
I also need to know which segment is where after interaction with the wheel - for instance if the wheel is rotated, I need to know where the original segments are after the rotation action.
Two questions:
Do I draw this segmented circle (with varying colours and content placed on the segment) with OpenGL or using Android Canvas?
Using either of the options, how do I register which segment is where?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
EDIT:
Ok, so I've figured out how to draw the segmented circle using Canvas (I'll post the code as an answer). And I'm sure I'll figure out how to rotate the circle soon. But I'm still unsure how I'll recognize a separate segment of the drawn wheel after the rotation action.
Because, what I'm thinking of doing is drawing the segmented circle with these wedges, and the sort of handling the entire Canvas as an ImageView when I want to rotate it as if it's spinning. But when the spinning stops, how do I differentiate between the original segments drawn on the Canvas?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
I've read about how to draw a segment on its own (here also), OpenGL, Canvas and even drawing shapes and layering them, but I've yet to see someone explaining how to recognize the separate segments.
Can drawBitmap() or createBitmap() perhaps be used?
If I go with OpenGL, I'll probably be able to rotate the segmented wheel using OpenGL's rotation, right?
I've also read that OpenGL might be too powerful for what I'd like to do, so should I rather consider "the graphic components of a game library built on top of OpenGL"?
This kind of answers my first question above - how to draw the segmented circle using Android Canvas:
Using the code found here, I do this in the onDraw function:
// Starting values
private int startAngle = 0;
private int numberOfSegments = 11;
private int sweepAngle = 360 / numberOfSegments;
#Override
protected void onDraw(Canvas canvas) {
setUpPaint();
setUpDrawingArea();
colours = getColours();
Log.d(TAG, "Draw the segmented circle");
for (int i = 0; i < numberOfSegments; i++) {
// pick a colour that is not the previous colour
paint.setColor(colours.get(pickRandomColour()));
// Draw arc
canvas.drawArc(rectF, startAngle, sweepAngle, true, paint);
// Set variable values
startAngle -= sweepAngle;
}
}
This is how I set up the drawing area based on the device's screen size:
private void setUpDrawingArea() {
Log.d(TAG, "Set up drawing area.");
// First get the screen dimensions
Point size = new Point();
Display display = DrawArcActivity.this.getWindowManager().getDefaultDisplay();
display.getSize(size);
int width = size.x;
int height = size.y;
Log.d(TAG, "Screen size = "+width+" x "+height);
// Set up the padding
int paddingLeft = (int) DrawArcActivity.this.getResources().getDimension(R.dimen.padding_large);
int paddingTop = (int) DrawArcActivity.this.getResources().getDimension(R.dimen.padding_large);
int paddingRight = (int) DrawArcActivity.this.getResources().getDimension(R.dimen.padding_large);
int paddingBottom = (int) DrawArcActivity.this.getResources().getDimension(R.dimen.padding_large);
// Then get the left, top, right and bottom Xs and Ys for the rectangle we're going to draw in
int left = 0 + paddingLeft;
int top = 0 + paddingTop;
int right = width - paddingRight;
int bottom = width - paddingBottom;
Log.d(TAG, "Rectangle placement -> left = "+left+", top = "+top+", right = "+right+", bottom = "+bottom);
rectF = new RectF(left, top, right, bottom);
}
That (and the other functions which are pretty straight forward, so I'm not going to paste the code here) draws this:
The segments are different colours with every run.
I'm trying to make a wheel of colours which allows the user to select between Solid color or Gradients.
The user can also move those colours around the wheel.
The problem is with the gradient. When the angle is over 360° the colour displayed is going back to the first colour instead of continue the gradient.
I'm using a custom view and the code I will paste is called from the onDraw method.
int start = Color.rgb((int)previous.startRed, (int)previous.startGreen, (int)previous.startBlue);
int end = Color.rgb((int)previous.endRed, (int)previous.endGreen, (int)previous.endBlue);
int[] colors = {start, end, end};
float from = from_angle / 360.0f;
float to = (from_angle + to_angle) / 360.0f;
float[] positions = {from,to, to};
Log.v("Print", "print positions " + positions[0] + " " + positions[1]);
Shader gradient = new SweepGradient (oval.centerX(), oval.centerY(),colors, positions);
paint.setShader(gradient);
canvas.drawArc(oval, from_angle, to_angle + 1, false, paint);
Here is a picture to show the problem I face.
Any hint would be appreciated. Thank you.
[UPDATED with additional code]
I'm having a major problem with Android correctly rendering some bitmaps in my custom view's onDraw() method on some (Nexus 7 and 10 that I know about) but not all devices. It renders properly on the Android phones I have for testing. Here is the snippet of consequence:
/* set up mImagePaint earlier */
mImagePaint.setAntiAlias(true);
mImagePaint.setFilterBitmap(true);
mImagePaint.setDither(true);
mImagePaint.setStyle(Paint.Style.FILL_AND_STROKE);
mImagePaint.setStrokeWidth(0);
mImagePaint.setColor(Color.WHITE);
protected void onDraw(Canvas canvas) {
final float vw = getWidth();
final float vh = getHeight();
final float bw = mBitmap.getWidth();
final float bh = mBitmap.getHeight();
final float ba = bw / bh;
final float va = vw / vh;
if (va > ba) {
final float top = (bh - ba / va * bh) / 2;
mSrcRect.set(0, (int) top, (int) bw, (int) (bh - top));
} else {
final float left = (bw - va / ba * bw) / 2;
mSrcRect.set((int) left, 0, (int) (bw - left), (int) bh);
}
mContentRect.set(0, 0, vw, vh);
canvas.drawBitmap(mBitmap, mSrcRect, mContentRect, mImagePaint);
}
Results on Nexus 7 and 10 are incorrect and renders a wide white border as shown below. This is part of the bitmap rendering but not part of the original bitmap or rect.
The correct (desired) result on a Samsung Galaxy phone:
The image and code shown in both examples above are exactly the same. I've tried variations already using null paint, null srcRect, and even using alternate method drawBitmap(bitmap, 0, 0, null) and get the same results. Looking at the framework code, of course drawBitmap() calls directly to native methods whose source code I can't view.
Only a small number of images seem to exhibit this problem and seems as though mostly square images exhibit it. But here is only one other non-square image that exhibits this problem:
Most of these images are slightly rotated given the desired custom view, and it now occurs to me that rotation might be part of the problem, but maybe not since the canvas isn't rotated, just when it's copied to the parent's canvas bitmap backing by Android.
Any ideas? This is nuts!
Are you sure the pictures are large enough to display without you having to scale up on those devices?