i'm attempting to change the background image of a custom View with some success. the image will change but the problem is that i still see traces of the old image. when i attempt to clear the canvas before drawing the new image, it doesn't appear to work. i create a bitmap to store the image. when changing the image, i call Canvas.drawColor() before drawing the new image but the old image persists. i've tried drawColor(0), drawColor(Color.BLACK), c.drawColor(0, PorterDuff.Mode.CLEAR), and none of the above works. as such, i had to post this for review from more experienced minds than mine.
the actual code is as follows:
private int bgnd;
private boolean switching;
public void setBgnd(int incoming){
switching = true;
switch (incoming){
case R.drawable.image1:
bgnd = incoming;
this.invalidate();
break;
case R.drawable.image2:
bgnd = incoming;
this.invalidate();
break;
}
}
protected void onDraw(Canvas c){
if(switching == true){
Bitmap b = BitmapFactory.decodeResource(getResources(), bgnd);
c.drawColor(0, PorterDuff.Mode.CLEAR);
c.drawBitmap(b, 0, 0, null);
switching = false;
}else{
Bitmap b = BitmapFactory.decodeResource(getResources(), bgnd);
c.drawBitmap(b, 0, 0, null);
}
}
Just like you, I struggled how to clear a top layer/surfaceview in my multiple layer/surfaceview app. After 2 days searching and coding, I found out my own way and this is how I cleared a canvas before drawing, you can use it when having multiple layers/surfaceviews. The background layer will not be covered with black, that is the trick.
Paint paint = new Paint();
paint.setXfermode(new PorterDuffXfermode(Mode.CLEAR));
canvas.drawPaint(paint);
paint.setXfermode(new PorterDuffXfermode(Mode.SRC));
// start your own drawing
You can use Canvas's method drawRGB.
Do you not need to also call invalidate() from within your onDraw method, so that changes made in that onDraw are updated on the screen?
The invalidate() in your switch will invoke your onDraw after you call setBgnd, but there's nothing saying to redraw after you've made changes to the Canvas.
I just meet this problem.
I solve it by repeating “..Drawxxx();Post();” for at least 3 times,it works well.
I guess ,“double buffering” is the reason,sometimes it is “trible buffering”.we need to repeat our drawing to make sure that each “buffer” is updated..
Related
Here is the scenario of the issue .
A custom view which is a canvas on which I need to draw .
2 buttons beneath it ,lets call them A and B
When A is clicked -> Image A is drawn to the above canvas .
When B is clicked -> Image B is drawn to the above canvas .
The issue demands that the previously drawn image on canvas must be preserved . That is if you click button A followed by button B then the canvas must contain two images . So the previous images need to be preserved.
Problem : How do you achieve this ?
Possible solution 1 : Create an ArrayList and keep adding images to this arrayList . Pass the updated arrayList to canvas onDraw method and redraw every single image for every button click .
Possible solution 2 : There has to be some method to preserve the state of the canvas so that on each button click you draw on canvas's last preserved state and draw only the new image .
Further Requirements : The Images drawn to canvas could be dragged so need to keep track of updated positions .
I am at an impasse and couldn't find a good tutorial or a book tackling such requirement , any help is appreciated .
You can create the Bitmap, and draw what you want on it.
Bitmap mBitmap;
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (changed) {
if (mBitmap != null) {
mBitmap.recycle();
mBitmap = null;
}
mBitmap = Bitmap.createBitmap(right - left, bottom - top, Bitmap.Config.ARGB_8888);
redrawAllYourStuffTo(mBitmap);
}
}
when button is pressed, you can draw directly to bitmap, like this:
Canvas canvas = new Canvas(mBitmap);
canvas. ... // draw operations.
// after the bitmap canvas drawing finished, call
invalidate();
in onDraw just paint your bitmap
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(mBitmap, 0, 0, null);
}
I'm writing a custom view that displays signals. In order to shorten my onDraw() time I cache everything I've drawn so far in a Bitmap and just append to that in every onDraw() call. By doing this I can save huge amounts of time since I only need to draw a few fixels at a time instead of redoing the whole thing.
There is on thing bothering me though - it appears as drawing directly to the provided canvas provides a more "accurate" drawing than drawing on the bitmap first and then drawing the bitmap on the canvas. By looking at the lower part of the following picture you can see the difference:
I uploaded a demo project displaying the discrepancy at https://github.com/gardarh/android-uglybitmapdrawing/ but the relevant code is as follows:
#Override
public void onDraw(Canvas canvas) {
if(cachedBitmap == null) {
cachedBitmap = Bitmap.createBitmap(getWidth(), 200, Config.ARGB_8888);
cachedCanvas = new Canvas(cachedBitmap);
}
for(int i = 0; i < COORDS.length; i++) {
float[] curCoords = COORDS[i];
canvas.drawLine(curCoords[0], curCoords[1], curCoords[2], curCoords[3], linePaint);
cachedCanvas.drawLine(curCoords[0], curCoords[1], curCoords[2], curCoords[3], linePaint);
}
canvas.drawBitmap(cachedBitmap, 0, 120, null);
}
Why are the two traces not the same and more importantly, how can I make the lower trace look like the upper one?
The reason for the differences is that the canvas drawing is done by hardware acceleration (GPU), and the bitmap drawing is done by software (CPU). If you disable hardware acceleration, they become the exact same.
If you multiply the X coordinates by 10, you will see that the difference is in the way lines are joined. These are minor one pixel difference and I wouldn't bother with them. I am not sure which one is the more accurate, they seem like just slightly different implementations.
Android framework API provides 2D drawing APIs for simple animation that does not require major dynamic changes.
There are two ways of implementation using these API.
1. Drawing to a View
2. Drawing on a Canvas
1.Drawing a circle to View
Drawing to view is a better option when your UI does not require dynamic changes in the application.
This can be achieved simply by extending the View class and define an onDraw() callback method.
Use the Canvas given to you for all your drawing,
using various Canvas.draw...() methods (Ex: canvas.drawCircle(x / 2, y / 2, radius, paint);). onDraw() is a callback method invoked when the view is initially drawn.
Below is a simple example code to draw a circle:-
MainActivity.java
public class MainActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new MyView(this));
}
public class MyView extends View {
public MyView(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
#Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
super.onDraw(canvas);
int x = getWidth();
int y = getHeight();
int radius;
radius = 100;
Paint paint = new Paint();
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.WHITE);
canvas.drawPaint(paint);
// Use Color.parseColor to define HTML colors
paint.setColor(Color.parseColor("#FB9J2F"));
canvas.drawCircle(x / 2, y / 2, radius, paint);
}
} }
2. Drawing rectangle on a canvas
To draw dynamic 2D graphics where in your application needs to regularly re draw itself, drawing on a canvas is a better option. A Canvas works for you as an interface, to the actual surface upon which your graphics will be drawn.
If you need to create a new Canvas, then you must define the bitmap upon which drawing will actually be performed. The Bitmap is always required for a Canvas.
The below example explains to draw a rectangle:-
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/mylayout"> </LinearLayout>
MainActivity.java
public class MainActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Paint paint = new Paint();
paint.setColor(Color.parseColor("#DD4N5C"));
Bitmap bitmap = Bitmap.createBitmap(512, 800, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
canvas.drawRect(150, 150, 250, 250, paint);
LinearLayout layout = (LinearLayout) findViewById(R.id.mylayout);
layout.setBackgroundDrawable(new BitmapDrawable(bitmap));
}
}
I disagree that you are saving much by going the bitmap route. You might consider a data structure that stores the drawn signal and converting this to a JSON object that can be serialized/deserialized.
As for your question, this is an educated guess but it has more to do with rescaling of the drawn signal, since you are not using getHeight() when you create the bitmap.
I have a LinearLayout whose background color is set to black. In this LinearLayout, I have a View in which I draw using Canvas. Because the onDraw() method will be called multiple times, I want to clear what I drew previously when I call onDraw() method, thus I use Canvas.drawColor(Color.BLACK) to clear the canvas.
But what I get is a black screen without anything even when I draw something new. I can already draw something before I add Canvas.drawColor(Color.BLACK) within onDraw() method.
EDIT: codes of my onDraw() method
String value = "";
static Bitmap bitmap;
static Canvas canvas;
public void init(){// this is called by constructor method
this.setWillNotDraw(false);
bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
canvas = new Canvas();
canvas.setBitmap(bitmap);
}
public void onDraw(Canvas canvas){
canvas.drawBitmap(bitmap, 0, 0, null);
drawGrid();
}
public void drawGrid(){
Paint paint = new Paint();
paint.setColor(Color.GRAY);
paint.setStrokeWidth(1);
canvas.drawText(value, somex, somey, paint);
}
public void changeData(String value){
this.value = value;
this.postInvalidate();
}
Another question, where is the right place I call Canvas.drawColor(Color.BLACK)?
I used the below code and it works for me. Clearing the draw on canvas.
public void resetBitmapCanvasAndPath() {
// TODO Auto-generated method stub
mDrawingUtilities.mBitmap = Bitmap.createBitmap(Constants.SCREEN_WIDTH,Constants.SCREEN_HEIGHT ,
Bitmap.Config.ARGB_8888);
mDrawingUtilities.mCanvas = new Canvas(mDrawingUtilities.mBitmap);
mDrawingUtilities.mPath = new Path();
}
Here's a link similar to your question and have a look at the accepted answer.
How to clear finger paint?
use Color.TRANSPARENT instead of using Color.BLACK
Now I need to draw a new Canvas in an existent Canvas (the parent Canvas), like Bitmap's works.
The logic is this:
Each Sprite can have others Sprite nodes (LinkedList<Sprite> nodes);
If don't have nodes (nodes = null) then, will draw the (Bitmap) Sprite.image directly to parent canvas, received on update method, like (currently working):
public void update(Canvas canvas){
if(this.nodes == null){
canvas.drawBitmap(this.image, ...);
}
...
}
If it have nodes, I need create a new Canvas, draw nodes bitmap on this, and draw the new Canvas on parent Canvas. This don't works, but the idea is like this:
public void update(Canvas canvas){
...
else { // this.nodes != null
Canvas newCanvas = new Canvas();
for(Sprite node: this.nodes){
node.update(newCanvas);
}
// Canvas working like Bitmap! (?)
canvas.drawCanvas(newCanvas, this.x, this.y, this.paint);
}
}
Real example usage: well, I have a Sprite called CarSprite. The CarSprite have a private class called WheelSprite extends Sprite. The CarSprite implements two nodes (two dimensional example) of WheelSprite, that will be positioned in the CarSprite to seems the whell of car. So far, no problem.
But if CarSprite is affected by a devil one object in the level, this will be turn transparent. I will do, for instance, objectOfCarSprite.paint.setAlpha(127);. The problem is, if I do it (currently working mode), only the car bitmap will turn transparent, but not the well, because it is drawed directly to global Canvas object.
This part does not influence much: currently, I'm getting the parent (of parentN...) Paint object alpha, and with some math, I get a transparent solution to wheel that works almost as well I need, but if, for instance, the WheelSprite object is over car bitmap, is possible to see the ground of car, like picture.
Exists a better way to do like I want, or only like work currently?
I do it. But is very slow, but works fine.
final Bitmap split = Bitmap.createBitmap(GameActivity.screenWidth,
GameActivity.screenHeight,
Bitmap.Config.ARGB_8888);
final Canvas subCanvas = new Canvas(split);
for(final Sprite sprite: this.nodes) {
sprite.update(subCanvas);
}
canvas.drawBitmap(split, this.x, this.y, this.paint);
First we need have a Canvas object (here just canvas);
After, we need make a Bitmap where we will draw. This need get a width and height from current canvas. I used a shortcut on this case;
Next, we need make a new Canvas, it will be passed to Sprite.nodes;
After all, we need draw the bitmap on global canvas;
Note: this works very well, but affect to much the speed.
I created a layout in my myviewlay.xml but this is not working, what am i missing?
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
imageBack = BitmapFactory.decodeResource(getResources(), R.layout.myviewlay, null);
}
Add this to do the actual drawing:
canvas.drawBitmap(imageBack, x, y, mPaint); //replace x,y,and mPaint with whatever you need to.
However, if you're trying to display an entire layout use setContentView(imageBack) or something similar. It's recommended to do your layout in XML.
Update: Sorry I misunderstood you at first. It looks like you ARE trying to inflate a layout from XML. In that case, in your onCreate(), call setContentView(R.layout.myviewlay);