Applying a BitmapShader using relative coordinates when using canvas.drawBitmap - android

I'm trying to Use a BitmapShader to draw on a Canvas. I have 2 images of the same dimensions (one for the alfa channel and one for RGB), that I want to draw on a canvas that was created with the same size. The problem is that it seem as though canvas.drawBitmap users absolute coordinates for the paint. is there a way to draw with relative coordinates.
public class MaskedImageDrawable extends Drawable {
private final Bitmap mMask;
private final Paint mPaint = new Paint();
public MaskedImageDrawable(Bitmap mask, Bitmap image) {
mMask = mask;
Shader targetShader =
new BitmapShader(image, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
mPaint.setShader(targetShader);
}
#Override
public void draw(Canvas canvas) {
canvas.drawBitmap(mMask, 0.0f, 0.0f, mPaint);
}
#Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
#Override
public void setAlpha(int alpha) {
mPaint.setAlpha(alpha);
}
#Override
public void setColorFilter(ColorFilter cf) {
mPaint.setColorFilter(cf);
}
}
I can't seem to be able to get the drawable's absolute coordinates on the screen. I know I can use getLocationOnScreen/InWindow from the view that holds the drawable but that doesn't take animations into account.
I tried tried applying relative coordinates by applying a translation on the shader in onBoundsChange but the coordinates here seems to be relative as well.
Matrix mTranslationMatrix = new Matrix()
#Override
protected void onBoundsChange(Rect bounds) {
super.onBoundsChange(bounds);
mTranslationMatrix.setTranslate(bounds.left, bounds.top);
mPaint.getShader().setLocalMatrix(mTranslationMatrix);
}
As a reference - it seems drawRect with the same paint will use relative coordinates. so replacing the draw method with the following will render the RGB image on the drawable correctly (but not the mask)
#Override
public void draw(Canvas canvas) {
// getBounds returns the relative coordinates as well
canvas.drawRect(getBounds, mPaint);
}

It seems that this is a HW acceleration issue. disabling HW acceleration on the view moves the shader back to relative coordinates.
if (android.os.Build.VERSION.SDK_INT >= 11) {
myView.disableHwAcceleration();
}

Related

Canvas doesn't draw circles

I'm trying to make a small pong game, using a custom view that draws the platforms on each side and the ball as a circle. However when i run the app to see if the drawing is done correctly, while the platforms are drawn correctly the circle wont appear. so my question is, why is the ball not drawn at all?
This is the custom view:
public class GameView extends SurfaceView implements Runnable {
private Context mContext; // a private instance of the context
Thread gameThread=null; //the thread on which the game will run
Ball ball; //a ball
Platform lPlat,rPlat; // a platform for the left side and the right
//tools that will draw the view and the objects
Canvas canvas;
Paint paint;
Color color;
SurfaceHolder mHolder;
//variable for frame management and objects movement
long lastFrame;
long fps;
volatile boolean playing;
public GameView(Context context){
super(context);
this.mContext=context;
mHolder=getHolder();
color=new Color();
paint=new Paint();
gameThread=new Thread(this);
lPlat=new Platform(2592,620,50,200);
rPlat=new Platform(120,620,50,200);
ball=new Ball(2400,620,20);
setWillNotDraw(false);
init();
}
public void init() {
render();
playing=true;
gameThread.start();
}
//the game Thread
#Override
public void run(){
while (playing){
long currentTime=System.currentTimeMillis();
render();
lastFrame=System.currentTimeMillis()-currentTime;
//dividing the time difference between the start of the count and the end of the render (which is a frame) by 1000 to get an approximation of the fps;
if (lastFrame>=1)
fps=1000/lastFrame;
}
}
//renders the view, calling update method, and draw method afterwards
public void render(){
update();
draw();
}
public void update(){
ball.updateBallPosition();
rPlat.updatePlatformPosition();
lPlat.updatePlatformPosition();
}
#Override
public void onDraw(Canvas canvas){
super.onDraw(canvas);
Log.i("#CanvasStatus","Canvas onDraw");
canvas.drawColor(Color.argb(255, 100, 120, 70));
//the drawRect methods work properly
canvas.drawRect(lPlat.shape,paint);
canvas.drawRect(rPlat.shape,paint);
canvas.drawCircle(ball.x,ball.y,ball.radius,paint); //this is where I try to draw the circle
}
public void draw(){
if (mHolder.getSurface().isValid()) {
Log.i("#CanvasStatus","valid");
canvas=mHolder.lockCanvas();
paint.setColor(Color.argb(255,10,200,157));
paint.setStyle(Paint.Style.FILL);
this.draw(canvas);
mHolder.unlockCanvasAndPost(canvas);
}
else Log.i("#CanvasStatus","invalid");
}
}
and this is the ball class:
public class Ball {
float x;
float y;
float radius;
double yspeed, xspeed;
public Ball(float x, float y, float radius) {
this.x = x;
this.y = y;
this.radius = radius;
yspeed = 200;
xspeed = 200;
}
public void updateBallPosition() {
x+=xspeed;
y+=yspeed;
//checks for wall collisions
if(x>=GameActivity.screenCoordinates.x) {
x = GameActivity.screenCoordinates.x;
xspeed=-xspeed;
}
else if (x<=0){
x=0;
xspeed=-xspeed;
}
}
}
Not everything is implemented in regards to the game, i'm just trying to get the canvas to draw the objects correctly at the moment.
Thanks in advance
You don't need to override onDraw method and call draw(canvas) in your draw method, all the work with canvas should be done there. Remove onDraw method and change draw method to look like this:
public void draw(){
if (mHolder.getSurface().isValid()) {
Log.i("#CanvasStatus","valid");
canvas=mHolder.lockCanvas();
paint.setColor(Color.argb(255,10,200,157));
paint.setStyle(Paint.Style.FILL);
canvas.drawColor(Color.argb(255, 100, 120, 70));
//the drawRect methods work properly
canvas.drawRect(lPlat.shape,paint);
canvas.drawRect(rPlat.shape,paint);
canvas.drawCircle(ball.x,ball.y,ball.radius,paint);
mHolder.unlockCanvasAndPost(canvas);
}
else Log.i("#CanvasStatus","invalid");
}
By the way, you've set up boundaries for x coordinate only, you should add it for y as well. Besides that, you will possibly need to add some delay to canvas drawing (with Thread.sleep in a simplest case) or else your view will re-draw too often.

Animating a point on Android canvas

Every time onDraw() is called I am drawing a series of points on the canvas. How do I animate one point so that it changes colour or fades in and out? So essentially goes from orange->red and back or opaque->transparent and back?
I am doing the following:
public void onDraw(Canvas canvas) {
drawDots();
}
private void drawDots() {
canvas.drawCircle(xcoord, ycoord, 20, getPaintObj(param));
}
private Paint getPaintObj(int param) {
if (param % 2 == 0) {
ObjectAnimator colorFade = ObjectAnimator.ofObject(paintObj, "color", new ArgbEvaluator(), 0xff00ff00, 0xffff0000, 0xff0000ff);
colorFade.setDuration(2000);
colorFade.setInterpolator(new LinearInterpolator());
colorFade.setRepeatCount(ValueAnimator.INFINITE);
colorFade.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
invalidate();
}
});
colorFade.start();
return paintObj;
} else {
return otherPaintObj;
}
}
The problem is that it doesn't animate. It sticks to the first colour (0xff00ff00). What am I doing wrong?
ObjectAnimator doesn't work very well with the canvas as I was trying to animate a circle drawn on the canvas itself. Works better for actual objects (TextView, ImageView, etc.) rather than points drawn on canvases.
The solution I used is redrawing the canvas every once in a while and modifying the radius of the circle drawn to simulate an animation.

Animating ActionBar's icon

I have an ActionBar icon (the main one on the left, not an action item) that I would like to animate.
I am setting my ActionBar's icon in my Activity like this:
getSupportActionBar().setIcon(icon)
where icon is a Drawable produced by a library that converts a custom XML view into a bitmap. This XML view is a RelativeLayout with a background image and a TextView on top.
Today, when I have to update the TextView I simply re-generate the icon and call setIcon again. Instead, I would like to get a hold of my TextView and apply some animation effect on it, like fade-out and then fade-in after updating it (maybe never having to call setIcon, just re-use the same one).
Not sure how to go about this. Can someone recommend an approach?
EDIT: trying this approach:
In MyActivity:
Drawable myDrawable = new MyDrawable();
supportActionBar.setIcon(myDrawable);
and:
public class MyDrawable extends Drawable {
private Paint paint;
private RectF rect;
public MyDrawable() {
this.paint = new Paint();
this.rect = new RectF();
}
#Override
public void draw(Canvas canvas) {
paint.setARGB(255, 0, 255, 0);
paint.setStrokeWidth(2);
paint.setStyle(Paint.Style.FILL);
rect.right = 20f;
rect.bottom = 20f;
canvas.drawRoundRect(rect, 0.5f, 0.5f, paint);
}
#Override
public void setAlpha(int alpha) {
paint.setAlpha(alpha);
}
#Override
public void setColorFilter(ColorFilter cf) {
paint.setColorFilter(cf);
}
#Override
public int getOpacity() {
return PixelFormat.OPAQUE;
}
}
Nothing shows up. I verified that onDraw gets called. Something suspicious to me is that canvas has both height & width set to 1.
A proper approach for it would be to forget the XML layout and create a custom Drawable.
An instance of this custom drawable will be set to the icon on the ActionBar and call invalidateSelf() whenever necessary to redraw (due to animation, for example).
The drawable can hold reference to other drawables (e.g. BitmapDrawable to have something from the /res/ folder or a Color or Gradient drawable for a background shade) and call (for example) bgDraw.draw(canvas) during the onDraw callback.
It can also draw stuff directly on the canvas that is given to it during onDraw callback. With the canvas you can draw circle, lines, areas, path and text directly on it.
edit:
very simple animation example (didn't check the code, likely typos):
private long animationTime;
public void doAnimation(){
animationTime = System.currentTimeMilis() + 3000; // 3 seconds
invalidateSelf();
}
public void onDraw(Canvas canvas){
// do your drawing.
// You can use difference between
// currentTimeMilis and animationTime for status/position
...
// at the end
if(animationTime > System.currentTimeMilis())
invalidateSelf();
}

Rounded corners drawable via code

I have a drawable that will change colors sometimes, but it must ALWAYS have rounded corners. It's for a UI library, so I can't know what colors will it have. XML is not an option, I have to achieve this with pure java.
Is there a way to achieve this programatically WITHOUT using XML?
Create a custom Drawable (i.e. extend Drawable) and in its onDraw use Canvas.drawRoundRect(RectF rect, float rx, float ry, Paint paint), setting the Paint to the desired colour.
If you draw the drawable by yourself, you could set a clip path with Canvas.clipPath. The path would consist of one or more rectangles and some circles, which clip the rounded corners. You probably have to play around with the arrangement of the path components until you get the desired output.
Based on the answer from #nmw, here's some code that works for this:
public class RRDrawable extends Drawable {
private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
public RRDrawable(int color) {
paint.setColor(color);
paint.setStyle(Paint.Style.FILL);
}
#Override
public void draw(Canvas canvas) {
int radius = 10; // note this is actual pixels
canvas.drawRoundRect(new RectF(0,0,canvas.getWidth(), canvas.getHeight()), radius, radius, paint);
}
#Override
public void setAlpha(int i) {
//.. not supported
}
#Override
public void setColorFilter(ColorFilter colorFilter) {
//.. not supported
}
#Override
public int getOpacity() {
return 1;
}
}
EDIT: added anti-aliasing to the edges.
(source)

Android graphics outside of onDraw method

Okay, so I'm trying to draw to a canvas on Android from outside of the onDraw method.
It's just easiest to show my code:
public class TestActivity extends Activity {
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Paint p = new Paint();
p.setColor(Color.GREEN);
Panel a = new Panel(this,150,150,50,p);
a.drawThing();
setContentView(a);
}
class Panel extends View{
private float radius, x, y;
private Canvas CAN;
private Paint p;
public Panel(Context context, float x, float y, float radius, Paint p){
super(context);
this.x = x;
this.y = y;
this.radius = radius;
this.p = p;
}
#Override
public void onDraw(Canvas canvas){
super.onDraw(canvas);
CAN = canvas;
}
public void drawThing(){
CAN.drawCircle(x, y, radius, p);
}
}
}
Do you see what I'm trying to do? But for some reason it throws a NullPointerException
Many of the graphics resources are explicitly freed/released after they've been used. I'm not exactly sure why they do this, but whatever the reason, they don't you to do what you're trying.
Instead of drawing outside of the onDraw method, use some kind of flag to change what the onDraw method is doing. When you want to draw some specific thing, you can set the right flag, and call invalidate().
#Override
public void onDraw(Canvas canvas){
super.onDraw(canvas);
if (doThing) {
canvas.drawCircle(x, y, radius, p);
}
}
EDIT
Something else to consider is drawing to and "off-scrren" source. This means using some kind of graphics representation like a bitmap as a buffer that you can draw to in other code. This won't update your gui, but it will give you the chance to do some heavy duty drawing without locking up the user's device. Once you are done drawing to the bitmap (or whatever) you can invalidate your view and draw it to the screen in the onDraw(Canvas) method.
I'm pretty sure that the null pointer happens because you're calling drawSomething before onDraw ever gets called. So CAN is null.
You can draw onto canvas outside of the onDraw. See this Can we have two canvases in an activity ? (OR) Having a canvas outside the onDraw() is not working for more info.

Categories

Resources