Restore canvas from previous call of onDraw for ImageView - java

Wow, this is frustrating. I'm really just trying to clear my ImageView before each time I draw on it.
I'm trying to extend Android's ImageView class into a custom StatusBarImageView. I provide a public method setPercentComplete(float percent) so that my activity can update the image accordingly. But every time I try to update the image by invalidating it and triggering it's onDraw(Canvas canvas) method, for some reason it tries to draw the new status bar and it's text on top of the existing image rather than starting fresh. So the captions I draw using canvas.drawText(...) bleed over each other.
Can anyone please tell me why this is happening / how to fix it?
I've tried several different approaches and none of them have worked.
I tried saving before the first time I draw and restoring before each subsequent time, but apparently it's a new canvas each time the onDraw() method fires, so restore() throws an exception that there hasn't been a save().
I've tried using canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR), but not only does that not work, it changes the background to be solid black for some reason.
Here's my code:
class StatusBarImageView extends ImageView {
private float status = 100.0f;
private Canvas lastCanvas;
private Paint imagePainter = new Paint();
private Paint textPainter = new Paint();
public StatusBarImageView(Context context) {
super(context);
initializePainters();
}
public StatusBarImageView(Context context, AttributeSet attributes) {
super(context, attributes);
initializePainters();
}
public StatusBarImageView(Context context, AttributeSet attributes, int defStyle) {
super(context, attributes, defStyle);
initializePainters();
}
private void initializePainters() {
this.status = 100.0f;
this.imagePainter.setStyle(Style.FILL_AND_STROKE);
this.textPainter.setStyle(Style.FILL);
this.textPainter.setFakeBoldText(true);
}
public void setPercentComplete(float status) {
this.status = status;
this.invalidate();
}
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
//Erase previous status bar and its text captions before drawing new one
if(this.lastCanvas != null) {
this.lastCanvas.restore(); //throws exception
//this.lastCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); // Doesn't work and makes background black
}
canvas.save();
this.lastCanvas = canvas;
//this.canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); // Doesn't work and makes the background black
//this.canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.MULTIPLY); // Same problem
this.drawStatusBarAndCaptions(canvas); //Uses canvas.drawRect and canvas.drawText
}
//...
}
And the xml just looks like:
<my.package.name.then.StatusBarImageView
android:id="#+id/status_bar"
android:layout_width="match_parent"
android:layout_height="0dp"
android:padding="0dp"
android:layout_margin="0dp"
android:layout_weight="3"/>

But every time I try to update the image by invalidating it and triggering it's onDraw(Canvas canvas) method.
Well, that's the way it should work. Any invalidate() would eventually cause onDraw() to be called.
In order to achieve the requirement you have, you may keep a boolean value, which will indicate you whether current onDraw() is initiated after setPercentComplete() call:
private boolean flag = false;
public void setPercentComplete(float status) {
this.status = status;
this.flag = true;
this.invalidate();
}
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (flag) {
flag = false;
// perform actions for showing percent status
} else {
// draw other stuff
}
}

Related

customize QR code with rounded corners for QR code edges instead of square and want to change color of code android?

I have generated simple black with square corners for QR code using com.google.zxing:core:3.3.3.
I want to customize QR code appearance like Instagram.
This library is used at many places https://github.com/scola/Qart
How to make square edges rounded? like below image :
You can create custom class like
private static class CustomViewFinderView extends ViewFinderView {
public final Paint PAINT = new Paint();
public CustomViewFinderView(Context context) {
super(context);
init();
}
public CustomViewFinderView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
// paint initialization
}
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
// Here you can draw your view using canvas with edges
}
}
After that while you initialize your ZXingScannerView add your custom view in createViewFinderView method.
ZXingScannerView mScannerView = new ZXingScannerView(this) {
#Override
protected IViewFinder createViewFinderView(Context context) {
return new CustomViewFinderView(context);
}
};

Android - How to call method in custom View from Main Activity?

I have created a custom View that will display a circle (the idea is that the user will be able to interact with this "ball" in various ways)
From my main activity class, I want to adjust some of the "ball's" properties, in this case change its color.
My problem is that nothing happens (no errors either, the app runs but doesn't do what I want) when I try to call the various methods from my MainActivity class, but if I do it from CircleView class, it works (for example changing the color upon touch)
Here is my custom View class (CircleView.java):
public class CircleView extends View {
private int circleColor = Color.GREEN;
private Paint paint;
public CircleView(Context context) {
super(context);
init(context, null);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_SCROLL:
this.circleColor = setRandomColor();
invalidate();
break;
case MotionEvent.ACTION_DOWN:
this.circleColor = setRandomColor();
invalidate();
break;
}
return super.onTouchEvent(event);
}
public CircleView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
paint = new Paint();
paint.setAntiAlias(true);
}
public void setCircleColor(int circleColor) {
this.circleColor = circleColor;
invalidate();
}
public int setRandomColor() {
Random random = new Random();
int randomColor = Color.argb(255, random.nextInt(), random.nextInt(), random.nextInt());
return randomColor;
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//...
//someXvalue, someYvalue, someRadius are being set here
//...
paint.setColor(circleColor);
canvas.drawCircle(someXvalue, someYvalue, someRadius, paint);
}
}
And here is my MainActivity.java class:
public class MainActivity extends Activity implements
GestureDetector.OnGestureListener {
private GestureDetectorCompat mDetector;
CircleView circle;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mDetector = new GestureDetectorCompat(this, this);
circle = new CircleView(this);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
this.mDetector.onTouchEvent(event);
return super.onTouchEvent(event);
}
#Override
public boolean onDown(MotionEvent event) {
return true;
}
#Override
public boolean onSingleTapConfirmed(MotionEvent event) {
circle.setCircleColor(circle.setRandomColor(0));
circle.invalidate();
return true;
}
}
I am new to Android development, and Java as well. I realize it could be something with the Context, which is something I have not fully understood yet. Could also be something with the TouchEvents. I am sure that someone out there can see my mistake. Any help is appreciated.
your circle view is not a part of activity's layout , it's just a object in memory which has no link to your activity screen so solutions
1.) Either set circle as Activity's view
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mDetector = new GestureDetectorCompat(this, this);
circle = new CircleView(this);
setContentView(circle);
}
2.) you can create your <yourpackagename.CircleView ...attributes .../> tag in your activity_main.xml and then use findViewById to initialize it in your activity.
1)If all you want to do with gestures is on tap, just implement an onClickListener on your View instead.
2)You aren't actually using the GestureDetector anywhere. The way it works is you set an onTouchListener for the view you want to get gestures on, and send the events to the gesture detector. You aren't ever sending data for any view to the detector, so it will never do anything.
3)Not a bug just an oddness- why circle.setColor(circle.setRandomColor())? I would expect a function named setXXX to actually set XXX, rather than having to do it yourself later. Not following that convention will work, but make debugging and maintenance hard.
Edit: Also what #Pavneet_Singh said- your circle isn't in your layout anywhere, so it won't be on screen.

Using onSizeChanged in Drawable class

I have created a custom class which extends Drawable. I'm trying to get the dimensions of the Drawable by using onSizeChanged() as follow
public class Circle extends Drawable {
...
#Override
protected void onSizeChanged(int xNew, int yNew, int xOld, int yOld) {
super.onSizeChanged(xNew, yNew, xOld, yOld);
}
}
But I'm getting an error saying that "Method does not override from the superclass". What do I do to fix it?
Thanks a lot to Michael Spitsin for this answer. To get the dimensions of the layout which the drawable is attached to, use the following code
#Override
protected void onBoundsChange(Rect bounds) {
mHeight = bounds.height();
mWidth = bounds.width();
}
If we go to View source code and in Drawable source code, we will see next things:
In View.setBackgroundDrawable(Drawable) if we pass not null, than field mBackground (is is responsible to store background drawable) is updated.
If we will try to find usages of method Drawable.onBoundsChanged() then we will see that it is mainly used in Drawable.setBounds method. And if we will find usages of it, we will see next snippet in View.class:
private void drawBackground(Canvas canvas) {
final Drawable background = mBackground;
if (background == null) {
return;
}
if (mBackgroundSizeChanged) {
background.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
mBackgroundSizeChanged = false;
rebuildOutline();
}
//draw background through background.draw(canvas)
}
Thus, using of onBoundsChanged callback is accepted for your task.

How to animate a Drawable in Android?

I have a Drawable object in my application which is moving on the screen:
public class MyDrawable extends Drawable {
#Override
public void draw(Canvas canvas) {
// ...
}
// ...
}
It worked fine so far but now I have to animate this Drawable. I checked out the documentation and it looks like that the best option for me is using AnimationDrawable.
I did something like this:
public void addAnimation() {
animation = new AnimationDrawable();
animation.addFrame(resources.getDrawable(R.drawable.anim_0), 100);
animation.addFrame(resources.getDrawable(R.drawable.anim_1), 100);
animation.addFrame(resources.getDrawable(R.drawable.anim_2), 100);
animation.addFrame(resources.getDrawable(R.drawable.anim_3), 100);
animation.setOneShot(false);
refreshAnimationBounds();
animation.start();
}
#Override
public void draw(Canvas canvas) {
refreshAnimationBounds();
animation.draw(canvas);
}
What happens is that my original Drawable is moving as intended but I only see the first frame. What could be the problem?
refreshAnimationBounds(); is just refreshing the bounds of my Drawable object (repositions it).
Edit:
The common:
public void onWindowFocusChanged(boolean hasFocus) {
if (hasFocus) {
animation.start();
} else {
animation.stop();
}
}
solution is not working.
In general Animation package is quite buggy. I recall I had same problem with animation not starting and the solution I came with was to start it not in onCreate() as I formerly did but onWindowFocusChanged() once all view stuff was set. I wasn't extending AnimationDrawable myself but it may be the same origin in your case.

Android SurfaceView overlay on camera view does not show when preview is on

I have a FrameLayout containing two SurfaceViews. One of them will show the camera preview, and the other is a custom surface view I use for drawing. Problem is the surface view does not show up. If I comment out the line where the camera starts preview, it does though, on the black background. This indicates me that the Surface view is positioned well, but I suspect that when the camera preview draws, something goes wrong.
The camera view:
public CameraView(Context context, AttributeSet a) {
super(context, a);
mHolder = getHolder();
mHolder.addCallback(this);
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
The custom view:
public InclinaisonGauge(Context context, AttributeSet attrs) {
super(context, attrs);
getHolder().addCallback(this);
getHolder().setFormat(PixelFormat.TRANSLUCENT);
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
run = true;
updateThread = new Thread()
{
public void run()
{
Canvas c = null;
final SurfaceHolder inclHolder = getHolder();
while (run) {
try {
c = inclHolder.lockCanvas();
if(c!=null)
{
synchronized (inclHolder) {
onDraw(c);
}
}
} finally {
// do this in a finally so that if an exception is thrown
// during the above, we don't leave the Surface in an
// inconsistent state
if (c != null) {
inclHolder.unlockCanvasAndPost(c);
}
}
}
}
};
And I use this very simple draw method on the custom view to make sure it is not a framerate issue
public void doDraw(final Canvas canvas, final boolean bigSize, float daDirection)
{
canvas.drawARGB(255, 255, 255, 255);
}
Has anyone experienced this?
Thanks
Ok, if anyone else gets this, solution is to call this on the surfaceView
this.setZOrderMediaOverlay(true);
I don't think SurfaceViews are intended to be used as an overlay, but it is possible to achieve this behavior. What you have to do is when the SurfaceView class is created:
setZOrderMediaOverlay(true);
getHolder().setFormat(PixelFormat.TRANSPARENT);

Categories