The event onDraw() is emitted every time the View need to be repainted cause some graphic changes inside.
Unfortunately if the View is hidden (invisible) this event is not emitted since, obviously, there is no need to repaind anything. However I would to know if there is some trick to "cheat" the View to emit the onDraw() event and redraw itself exactly like it would really showed inside the screen.
Basically I need to capture screenshot status of a View component in all its changes but whitout show it (running it in background).
I guess it would be very hard to get such result but, just in case, I'll try to ask.
Thank you
I believe a View's visibility is checked by its parent and not the View itself. You can pass in a Canvas backed by a Bitmap straight in to View#draw(Canvas canvas) and it will draw itself on to the Bitmap. However, the based on the source code of View#setVisibility(), the View's background will still be invisible.
public void setVisibility(int visibility) {
setFlags(visibility, VISIBILITY_MASK);
if (mBGDrawable != null) mBGDrawable.setVisible(visibility == VISIBLE, false);
}
Everything else should appear in the View as is (unless it's children are also set to invisible of course).
EDIT:
Converting a view to Bitmap without displaying it in Android?
There are examples there on how to do that.
public static Bitmap getBitmapFromView(View view) {
Bitmap returnedBitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(),Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(returnedBitmap);
view.draw(canvas);
return returnedBitmap;
}
EDIT 2:
Since setVisibility() is not part of the View, you could override it and simply not set the View to be invisible. Something like:
boolean isInvisible = false;
#Override
public void setVisibility(int visibility) {
if (visibility == View.INVISIBLE) {
invisible = true;
} else {
invisible = false;
super.setVisibility();
}
}
#Override
public void onDraw(Canvas canvas) {
// change state code
if (!invisible) {
// draw code
super.onDraw(canvas);
}
}
#Override
public void draw(Canvas canvas) {
if (!invisible) {
// draw code
super.draw(canvas);
}
}
I have no idea what side-effects this would cause so be extremely careful and weary. Perhaps someone else would have a better solution. Another solution is you can simply call onDraw() on a different canvas whenever you want to draw it. This would require you to create a super class that is the parent layout View of the View you want to draw. Then in the parent's onDraw() method, call the child's onDraw() method separately if it's visibility is set to INVISIBLE.
If someone is interested I managed to solve this problem by override the invalidate() method. Instead of onDraw() that is called by the system only if the view is currently visible the invalidate() function is called "internally" and can be used to check if the view need to be reapinted in the same way.
Related
By default, onDraw() isn't called for ViewGroup.
So I have a question: What happen when we set background drawable for the ViewGroup
onDraw() is inherited from View class since ViewGroup is subclass of View. So when you set Background on a ViewGroup, it works the same way as any other view. When you call setBackground on a view, it sets requestLayout flag to true and invalidates the view.
Also, Official documentation for View class states that:
If you set a background drawable for a View, then the View will draw
it before calling back to its onDraw() method.
You can see how it works in the source code of Android's View class
public void setBackgroundDrawable(Drawable d) {
...
{
requestLayout = true;
}
...
computeOpaqueFlags();
if (requestLayout) {
requestLayout();
}
mBackgroundSizeChanged = true;
invalidate(true);
}
If you look at documentation for invalidate(), you will find
public void invalidate () Invalidate the whole view. If the view is
visible, onDraw(android.graphics.Canvas) will be called at some point
in the future.
setBackgroundColor() and setBackgroundResource() use setBackgroundDrawable() internally so they work the same way.
So to sum it up, onDraw is called at some point in future when you do setBackground().
I've created a custom view that draws via onDraw() overridden method some shapes. This view is scrollable so every time user navigates in the Activity, onDraw() method has called and all the canvas is drawn. In the onDraw() method there are some statements making some hard calculations so my intent is to draw, when user scrolls the view, only the part that were invisible and now, for the scrolling, they are visible.
How can I draw only the part that are visible in my custom view?
#Override
protected void onDraw(Canvas sysCanvas)
{
super.onDraw(sysCanvas);
if(!giaDisegnato) //If I've never drawn before, let's draw
{
if(!listaTl.isEmpty())
{
toDisk= Bitmap.createBitmap(w,h,Bitmap.Config.RGB );
canvas = new Canvas(toDisk);
canvas.drawColor(Color.WHITE);
p.setStyle(Paint.Style.FILL_AND_STROKE);
p.setAntiAlias(true);
p.setStrokeWidth(1);
for(TimelineGrafica t : listaTl)
{
if(inseritaLaPrima)
y = ySalvata + this.yAngoloDestroGiu + DISTANZA_FRA_TIMELINE;
p.setColor(t.getColor());
disegnaPunta(canvas,p,t);
disegnaRettangolo(canvas,p,t);
disegnaGrain(canvas,p,t);
disegnaFatti(canvas,p,t);
inseritaLaPrima = true;
}
y = ySalvata;
inseritaLaPrima = false;
sysCanvas.drawBitmap(toDisk,0,0,p);
}
requestLayout();
giaDisegnato = true;
}
else
{
//Here I've already drawn. So I'd like to redrawn the part of the view that now
//is visible.
sysCanvas.drawBitmap(toDisk,0,0,p);
}
}
Due to the language, it is difficult to know precisely what you are doing.
However, you can check the canvas to know whether you should draw or not using quickReject.
Example:
if(canvas.quickReject(boundingRect, EdgeType.BW)) {
return;
}
My project actually has quite a few lines of code right now, so I'll save you by only including here what matters. I hope it's enough information to make the issue clear.
I have two main classes: a 'GraphView' class, and then the main Activity's code. Within GraphView, I've created a function which, essentially, draws a rectangle on a canvas. It's called drawPixel. In GraphView's onDraw method, I call drawPixel a bunch of times, and it draws rectangles to the screen. Now, in the main code, I've programmatically created a layout and a button. What I want is that instead of calling drawPixel in GraphView's onDraw method, I want to draw those rectangles when I click a button. I tried this by doing:
someButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Canvas canvas = new Canvas();
// TODO Auto-generated method stub
for (int i=1; i<50; i++) {
someGraphView.drawPixel(canvas, i, i);
}
}
});
It didn't work. The rectangles draw correctly when i call then via onDraw(), but they don't draw at all when I call them from inside a setOnClickListener method. Does anyone know what I'm doing wrong?
Once that's written call invalidate() on your View.
I have a code where I draw image:
class Panel extends View {
public Panel(Context context) {
super(context);
}
#Override
public void onDraw(Canvas canvas) {
Bitmap _scratch = BitmapFactory.decodeResource(getResources(), R.drawable.calvin_logo_small);
canvas.drawColor(Color.WHITE);
canvas.drawBitmap(_scratch, x-point, y-point, null);
}
}
How can I draw this image in my activity, but I don't want to change may layout. I have layout: setContentView(R.layout.main); This is possible to draw in this lauout with canvas? I have this activity and this layout have a lot of components. I only want image in place where I click with canvas. This is idea. I start application where start activity with my setContentView(R.layout.main);. After that I click on the screen and canvas draw picture in place where I clicked. This is possible to do?
Your code should work. Just override the onTouchEvent() for the view, store the co-ordinates of the touch event in field variables and invalidate() the view so that onDraw() would be called. Use the co-ordinates in onDraw() to render the image as you've already done.
To improve performance, you can cache the bitmap if it's not going to change.
For my activity i use 3 custom views stacked.
the lower one is a SurfaceView fullscreen, the middle one is derived from GridView and overrides onDraw to add custom graphic.
The top one is derived directly from View, sits in a corner of the screen and act as a knob to control the others two views (this is the problematic view).
to add a custom animation to this view, i used this pattern:
public class StubKnobView extends View{
private Drawable knob;
private Point knobPos;
private float rotation;
private boolean needsToMove;
public void moveKnob(){
/* ...various calculations... */
this.needsToMove=true;
invalidate(); //to notify the intention to start animation
}
private void updateAnimation(){
/* ........... */
this.needsToMove= !animationComplete;
invalidate();
}
#Override
protected void onDraw(Canvas canvas) {
int saveCount=canvas.getSaveCount();
canvas.save();
canvas.rotate(this.rotation, this.knobPos.x, this.knobPos.y );
this.knob.draw(canvas);
canvas.restoreToCount(saveCount);
if(this.needsToMove){
updateAnimation();
}
}
}
ideally, if there is an animation pending, after each drawing cycle the view should auto invalidate.
right now this doesn't work, to force the animation i have to touch the screen to cause a onDraw cycle.
Using "show screen updates" of Dev tools I see that no screen invalidate/update cycle happen , apart when i click the screen.
specifying the dirty rect also ha no effect.
So, any idea where to look to know why this invalidate/draw cycle does not work the way is intended?
I encontered this situation, and also doubted that invalidate() doesn't make onDraw() called again.
After simplified the business codes, it's turn out that there is a dead loop - exactly too much iterations, which blocks the UI thread.
So, my advice is that make sure the UI thread going smoothly first.
Try something like this with a private class in your View. The idea is not to call invalidate() from within onDraw(). Instead, post it on the running queue. Instantiate the private class in your View constructor and then just call animateKnob.start() when you want the animation. The onDraw() can then just focus on drawing.
public void moveKnob(){
// update the state to draw... KnobPos, rotation
invalidate()
}
private class AnimateKnob{
public void start(){
// Do some calculations if necessary
// Start the animation
MyView.this.post( new Runnable() {
stepAnimation()
});
}
public void stepAnimation(){
// update the animation, maybe use an interpolator.
// Here you could also determine if the animation is complete or not
MyView.this.moveKnob();
if( !animationComplete ){
// Call it again
MyView.this.post( new Runnable() {
stepAnimation()
});
}
}
}