Ondraw for a custom view is looping infinitely android - android

OnDraw function for my custom View is being called infinitely and is looping !! What could be possible reason??
Here is my custom view:-
public class Balls extends View{
private static final String TAG = "BallsView";
private int mMode = READY;
public static final int PAUSE = 0;
public static final int READY = 1;
public static final int RUNNING = 2;
public static final int LOSE = 3;
private final Paint mPaint = new Paint();
private double mUx = 0.1;
private double mUy = 2;
private double mVy;
private double mVx;
private double mSx;
private double mSy;
private double mRange;
private float mX1;
private float mY1;
private int mX2;
private int mY2;
private int mDx;
private int mDy;
Time t;
float mAngle;
private final double mGravity = -9.8;
private long mLastTime;
private double mT;
private Canvas mCanvas = null;
public Balls(Context context, AttributeSet attrs, int defStyle){
super(context, attrs, defStyle);
setFocusable(true);
setWillNotDraw(false);
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mPaint.setStrokeWidth(10);
mPaint.setAntiAlias(true);
mPaint.setStrokeCap(Cap.ROUND);
//mPaint.setColor(0xff00ffff);
mPaint.setARGB(255, 0, 255, 0);
mLastTime = System.currentTimeMillis();
}
public Balls(Context context, AttributeSet attrs) {
super(context, attrs);
setFocusable(true);
setWillNotDraw(false);
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mPaint.setStrokeWidth(10);
mPaint.setAntiAlias(true);
mPaint.setStrokeCap(Cap.ROUND);
mPaint.setColor(0xff00ffff);
}
#Override
public void onDraw(Canvas canvas) {
Log.w(this.getClass().getName(),"onDraw of Balls called");
super.onDraw(canvas);
mCanvas = canvas;
if(mCanvas!= null)
Log.w(this.getClass().getName(),"Canvas is not null");
}
This view is inflated as follows in another activity:-
mBalls = (Balls) findViewById(R.id.balls);
This view is placed in xml file inside a relative view and the relative view is the child of horizontal scroll view.

onDraw(Canvas) called too often like in an infinite loop, is not normal.
normally it should be called 1 to 3 times, if there is no following invalidate or layout changes.
reasons for infinite loop maybe:
1, you called invalidate or postInvalidate some where.
2, parent or sibling layout is changing all the time.
3, View.LAYER_TYPE_SOFTWARE is used, es. setLayerType(View.LAYER_TYPE_SOFTWARE, null).
it is notable that LAYER_TYPE_SOFTWARE will cause onDraw() to be called, like in a loop.

There isn't an infinite loop here. What is going on is the OS is redrawing your activity as fast as possible. When your activity gets redrawn it redraws all of its children views. As your code has very little computation, from what I can see here, it is running very fast and is probably redrawing at a rate >30 FPS. Your log message makes it appear as if there is an infinite loop when there isn't. In fact there isn't even a loop inside your onDraw method.
To illustrate what is going on try this. Add a protected member drawCount to your Balls class and set it to 0:
protect int drawCount = 0;
Then append drawCount to the end of your onDraw log message
public void onDraw(Canvas canvas){
drawCount++;
Log.w(this.getClass().getName(),"onDraw of Balls called. Total draws:" + Integer.toString(drawCount));
...
}
What you should see is each log message will display a different drawCount.
If you want to get fancy and calculate the framerate of your app you could measure the time since the first draw and then divide the drawCount by how much time has passed which would give you a estimate of your activities framerate.

onDraw() get's call at invalidate. Invalidate() get's called when the view or it's parent feel the need to change and have to change it's state.

For me that was happening because i had some code that was modifying UI inside overridden DispatchDraw..

Related

onDraw and invalidate method

I'm doing a school project. In this project I have to do a program that have one or more ball bouncing in the screen. I did some research on google to help me in this, and I found this code :
public class BouncingBallInside extends View {
private List<Ball> balls = new ArrayList<>();
public BouncingBallInside(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public BouncingBallInside(Context context) {
super(context);
init();
}
private void init(){
//Add a new ball to the view
balls.add(new Ball(50,50,100, Color.RED));
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//Draw the balls
for(Ball ball : balls){
//Move first
ball.move(canvas);
//Draw them
canvas.drawOval(ball.oval,ball.paint);
}
invalidate(); // See note
}
}
The ball class :
public class Ball{
public int x,y,size;
public int velX = 10;
public int velY=7;
public Paint paint;
public RectF oval;
public Ball(int x, int y, int size, int color){
this.x = x;
this.y = y;
this.size = size;
this.paint = new Paint();
this.paint.setColor(color);
}
public void move(Canvas canvas) {
this.x += velX;
this.y += velY;
this.oval = new RectF(x-size/2,y-size/2,x+size/2,y+size/2);
//Do we need to bounce next time?
Rect bounds = new Rect();
this.oval.roundOut(bounds); ///store our int bounds
//This is what you're looking for ▼
if(!canvas.getClipBounds().contains(bounds)){
if(this.x-size<0 || this.x+size > canvas.getWidth()){
velX=-velX;
}
if(this.y-size<0 || this.y+size > canvas.getHeight()){
velY=-velY;
}
}
}
}
The program works perfecly.
I studied it deeply as good as I could. But after it and after watching the documentation I couldn't understand two thing:
Where and when the method onDraw(Canvas canvas) is called the first time.
Why at the end of onDraw there is invalidate()?
I mean the documentation said :
Invalidate the whole view. If the view is visible, onDraw(android.graphics.Canvas) will be called at some point in the future.
so... if this method is used to call onDraw,why don't call it direcly? what's the difference?
1)The onDraw method will be called by the framework, whenever the view is invalid. A view is invalid when it first comes on screen, so when you set your content view for an activity they layout and all views in it will be measured, laid out, then drawn (via onDraw).
After that the UI thread will call onDraw if needed every 16ms or so (so it draws at 60 FPS).
2)Its marking the view as needing to be redrawn, so the next time the the screen is drawn onDraw will be called. Otherwise it would be skipped, as we could assume it isn't needed.
Why you don't call onDraw directly- efficiency. In a very simple drawing system you would- but drawing is time consuming, you don't want to do it more than you have to. So instead of drawing immediately (which wouldn't work anyway, you wouldn't have the right Canvas to pass to onDraw), you call invalidate and the system will call onDraw if needed at a regular interval.
Note that this isn't particularly good code. In particular, having the onDraw trigger the move which updates the balls location instead of using a timer is icky. Having onDraw call invalidate as a result is also kind of icky. A better solution would be to separate the view, model, and timer into more of an MVC or MVP system.

MpAndroidChart set background between limit lines

I a using MpAndroidChart library. I need to implement a design where I need to color the area between two limit lines. I have attached an image for reference. I have tried multiple ways but I have failed to achieve it. I am using this library for the first time. Can anyone help me about how this could be achieved.
As you can see the green shade behind the line graph. Which is the limit. I need to get that green shade
Thanks in advance,
Anudeep Reddy.
I don't think that there is a direct way to achieve this, but this workaround should help you:
LimitLine ll = new LimitLine(lowerLimit, "Systolic range");
ll.setLineColor(Color.GREEN);
ll.setLineWidth(upperLimit - lowerLimit);
ll.setTextColor(Color.WHITE);
ll.setTextSize(12f);
chart.getAxisLeft().setDrawLimitLinesBehindData(true);
The important thing here is the method setDrawLimitLinesBehindData(true).
As always, all the information is available in the documentation.
I had the same problem but reached a different workaround without having to subclass the LineChart. Using canvas to draw the rectangle works, but you have to translate your charts coordinates to the canvas coordinates. You cannot use a single limit line as there is a limit to the width of the line. The workaround I used was to simply loop through limit lines to create a rectangle within my range.
float increment = (rangeHigh - rangeLow) / 20;
float metricLine = rangeLow;
for(int i = 0; i < 20; i++) {
LimitLine llRange = new LimitLine(metricLine, "");
llRange.setLineColor(Color.parseColor("#b5eb45"));
llRange.setLineWidth(10f);
leftAxis.addLimitLine(llRange);
metricLine = metricLine + increment;
}
As this is still an issue I throw in my two cents.
I tried the solution of #HouseOfHufflepuff but I got the error message that I use too much limit lines in the plot. It seems to work anyway but I guess the performance is not optimal.
So I implemented a subclass for drawing zones in the background. Maybe it's helpful for someone:
public class TargetZoneCombinedChart extends CombinedChart {
protected Paint mYAxisSafeZonePaint;
private List<TargetZone> mTargetZones;
public TargetZoneCombinedChart(Context context) {
super(context);
}
public TargetZoneCombinedChart(Context context, AttributeSet attrs) {
super(context, attrs);
}
public TargetZoneCombinedChart(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
protected void init() {
super.init();
mYAxisSafeZonePaint = new Paint();
mYAxisSafeZonePaint.setStyle(Paint.Style.FILL);
// mGridBackgroundPaint.setColor(Color.rgb(240, 240, 240));
mTargetZones = new ArrayList<>();
}
#Override
protected void onDraw(Canvas canvas) {
for (TargetZone targetZone : mTargetZones) {
// prepare coordinates
float[] pts = new float[4];
pts[1] = targetZone.lowerLimit;
pts[3] = targetZone.upperLimit;
mLeftAxisTransformer.pointValuesToPixel(pts);
// draw
mYAxisSafeZonePaint.setColor(targetZone.color);
canvas.drawRect(mViewPortHandler.contentLeft(), pts[1], mViewPortHandler.contentRight(),
pts[3], mYAxisSafeZonePaint);
}
super.onDraw(canvas);
}
public void addTargetZone(TargetZone targetZone){
mTargetZones.add(targetZone);
}
public List<TargetZone> getTargetZones(){
return mTargetZones;
}
public void clearTargetZones(){
mTargetZones = new ArrayList<>();
}
public static class TargetZone {
public final int color;
public final float lowerLimit;
public final float upperLimit;
public TargetZone(int color, float lowerLimit, float upperLimit) {
this.color = color;
this.lowerLimit = lowerLimit;
this.upperLimit = upperLimit;
}
}
}
To add a zone you just need to add a target zone object:
float rangeHigh = 180f;
float rangeLow = 80f;
chart.addTargetZone(new TargetZoneCombinedChart.TargetZone( Color.parseColor("#33b5eb45"),rangeLow,rangeHigh));
whereby the ranges are y values of the left axis.
This can be done by sub-classing the chart class (e.g. LineChart) and then overriding the onDraw() method. In the overridden onDraw() you can draw the rectangle(s) you need directly onto the canvas and then call super.onDraw() to complete the rendering of the chart.
There is an example of how to do this on the MP Android Github (see below). I followed the code in the example and it worked well for me.
https://github.com/PhilJay/MPAndroidChart/issues/485

Drawing over a view and all it's children

I'm trying to apply a visual effect to a viewgroup. My idea is to grab a bitmap of the viewgroup, shrink it down, expand it back up, and draw it over the viewgroup to give it a blocky, low quality effect.
I've got most of the way there using this code:
public class Blocker {
private static final float RESAMPLE_QUALITY = 0.66f; // less than 1, lower = worse quality
public static void block(Canvas canvas, Bitmap bitmap_old) {
block(canvas, bitmap_old, RESAMPLE_QUALITY);
}
public static void block(Canvas canvas, Bitmap bitmap_old, float quality) {
Bitmap bitmap_new = Bitmap.createScaledBitmap(bitmap_old, Math.round(bitmap_old.getWidth() * RESAMPLE_QUALITY), Math.round(bitmap_old.getHeight() * RESAMPLE_QUALITY), true);
Rect from = new Rect(0, 0, bitmap_new.getWidth(), bitmap_new.getHeight());
RectF to = new RectF(0, 0, bitmap_old.getWidth(), bitmap_old.getHeight());
canvas.drawBitmap(bitmap_new, from, to, null);
}
}
I simply pass in the canvas to draw on and a bitmap of what needs to be scaled down+up and it works well.
public class BlockedLinearLayout extends LinearLayout {
private static final String TAG = BlockedLinearLayout.class.getSimpleName();
public BlockedLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
applyCustomAttributes(context, attrs);
setup();
}
public BlockedLinearLayout(Context context) {
super(context);
setup();
}
private void setup() {
this.setDrawingCacheEnabled(true);
}
#Override
public void draw(Canvas canvas) {
super.draw(canvas);
// block(canvas); If I call this here, it works but no updates
}
#Override
public void onDraw(Canvas canvas) {
// block(canvas); If I call this here, draws behind children, still no updates
}
private void block(Canvas canvas) {
Blocker.block(canvas, this.getDrawingCache());
}
}
The problem I'm having is in my viewgroup. If I run the block method in the viewgroup's draw, it draws over everything but doesn't ever update when child views change. I've traced function calls with Log, and the draw method seems to be running, but nothing changes.
I've also tried implementing this in onDraw. This draws the bitmap behind all the children views, and again they aren't updating.
Can anyone explain how I would go about fixing this?
Try this:
#Override
protected void dispatchDraw(Canvas canvas) {
// call block() here if you want to draw behind children
super.dispatchDraw(canvas);
// call block() here if you want to draw over children
}
And call destroyDrawingCache() and then, buildDrawingCache() each time you change a child.
Draw() method will work well for you.
I'm now trying to make a count time view in a circle shape, when time is passing, the view will reducing his angle. It's used to cover profile photo(a circle shape photo).
Starting with Android API 23, you can use onDrawForeground(Canvas) to draw on top of child views: https://developer.android.com/reference/android/view/View#onDrawForeground(android.graphics.Canvas)
Unlike onDraw() though, be sure to call through to the super class:
#Override
public void onDrawForeground(final Canvas canvas) {
super.onDrawForeground(canvas);
// Your code here
}

custom component - manipulate canvas animation or use something other

I want to develop my application with a simple animation. I have used a lot of source from: http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/graphics/Arcs.html
My code:
public class Animation extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
private static class AnimView extends View {
private Paint myPaint;
private Paint myFramePaint;
private RectF bigOval;
private float myStart;
private float mySweep;
private static final float SWEEP_INC = 1;
public AnimView(Context context) {
super(context);
init();
}
public AnimView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
myPaint = new Paint();
myPaint.setAntiAlias(true);
myPaint.setStyle(Paint.Style.FILL);
myPaint.setColor(Color.RED);
bigOval = new RectF(40, 10, 280, 250);
myFramePaint = new Paint();
myFramePaint.setAntiAlias(true);
myFramePaint.setColor(Color.BLACK);
}
private void drawArcs(Canvas canvas, RectF oval, boolean useCenter, Paint paint) {
canvas.drawRect(oval, myFramePaint);
canvas.drawArc(oval, myStart, mySweep, useCenter, paint);
}
#Override
protected void onDraw(Canvas canvas) {
drawArcs(canvas, bigOval, true, myPaint);
myStart = -90;
mySweep -= SWEEP_INC;
invalidate();
}
}}
I'm using my view in this way in my xml file:
<view
class="go.android.Animation$AnimView"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
It works properly. I know that it isn't custom animation. But is this possible to set speed of this animation (in ms or seconds)? Or to stop this animation (for example in the OnClick or OnTouch Listener), and get to know when this animation has finished?
I also want to get first whole circle, and on the end of animation - lack of circle. Simply to change direction of this animation. Is this possible?
I don't want to use frame-by-frame animation. I want to get continuous animation. Is there any possibilities to get similar animation (with setting speed, etc...)
I also want to animate not only a color but rather a round drawable.
Thank you in advance. Sorry for my English skill.
There might be better and more precise ways to control your animation than this (you could look into using OpenGL), but you can change the speed and direction of the animation relatively easily given your existing code.
The speed is controlled by the SWEEP_INC field (which stands for Sweep Increment I would guess). Every time the onDraw() method is called, it increases the size of the wedge by 1 degree. If you want the animation to go 5 times as fast, set SWEEP_INC to 5 instead of 1. If you want the animation to go half as fast, set SWEEP_INC to 0.5.
You can also just set a flag to reverse the animation each time it finishes. I've modified your code to reverse each time it reaches the end, using the addToCircle boolean.
public class Animation extends Activity
{
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
private static class AnimView extends View {
private Paint myPaint;
private Paint myFramePaint;
private RectF bigOval;
private float myStart;
private float mySweep;
private float SWEEP_INC = 1;
//Use this flag to control the direction of the arc's movement
private boolean addToCircle = true;
public AnimView(Context context) {
super(context);
init();
}
public AnimView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
myPaint = new Paint();
myPaint.setAntiAlias(true);
myPaint.setStyle(Paint.Style.FILL);
myPaint.setColor(Color.RED);
bigOval = new RectF(40, 10, 280, 250);
myFramePaint = new Paint();
myFramePaint.setAntiAlias(true);
myFramePaint.setColor(Color.BLACK);
}
private void drawArcs(Canvas canvas, RectF oval, boolean useCenter, Paint paint) {
canvas.drawRect(oval, myFramePaint);
canvas.drawArc(oval, myStart, mySweep, useCenter, paint);
}
public void setIncrement(float newIncrement)
{
SWEEP_INC = newIncrement;
}
#Override
protected void onDraw(Canvas canvas) {
drawArcs(canvas, bigOval, true, myPaint);
myStart = -90;
//If the arc is currently getting bigger, decrease the value of mySweep
if(addToCircle)
{
mySweep -= SWEEP_INC;
}
//If the arc is currently getting smaller, increase the value of mySweep
else
{
mySweep += SWEEP_INC;
}
//If the animation has reached the end, reverse it
if(mySweep%360 == 0)
{
addToCircle = !addToCircle;
}
invalidate();
}
}
}
EDIT
If you remove the final and static modifiers from SWEEP_INC you can change the speed of the animation at runtime using the setIncrement(float newIncrement) function I've added.
You can stop the animation by calling setIncrement(0).
However, I don't think there is a constant rate at which onDraw is called. So I don't know of any way to assign a duration in seconds to this animation. For that you may want to look into more sophisticated methods of animation.

Animating onDraw() section

I have the following class:
public class MainActivity extends Activity {
/** Called when the activity is first created. */
String value = "0";
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(new GaugeAnimation(this));
}
}
My GuageAnimation class is as follows:
public class GaugeAnimation extends View{
private Path p;
private Paint cPaint = new Paint();
private int width = 200;
private int angleStart = 135;
private int sweep = 90;
private int value=0;
Bitmap bottom = BitmapFactory.decodeResource(getResources(), R.drawable.dashboard_rpm_bottom);
Bitmap top= BitmapFactory.decodeResource(getResources(), R.drawable.dashboard_rpm_active);
public GaugeAnimation(Context context){
super (context);
//Arc Equations & etc....
}
#Override
public void onDraw(Canvas c){
Paint paint = new Paint();
paint.setFilterBitmap(false);
paint.setColor(Color.BLUE);
c.translate(55,320);
//Draw bottom image on canvas
c.save();
p.addArc(new RectF(0,0,width,width), angleStart, sweep);
p.lineTo(width/2, width/2);
c.clipPath(p);
//draw Image on top
c.restore();
invalidate()
}
}
so basically this crops a circular image one piece at a time based on arc equations. I want to show an animation like the circle's pieces all being filled in (so showing each clipped path which demonstrates a circle being built). So i was thinking of doing a for loop like:
for (int i=0; i<100; i++) {
sweep=i;
p.addArc(new RectF(0,0,width,width), angleStart, sweep);
p.lineTo(width/2, width/2);
c.clipPath(p);
invalidate();
}
but this doesn't work it just draws the end result when i=100, anyone have an idea as to how i can do this?
Currently your loop is performed on the main thread and keeps it busy, so it will not actually update the UI until you finish.
You should do the loop in a background thread (using Timer or AsyncTask) and perform the paint on main thread. Note that without a short sleep, it will probably would look like an animation too much, it will be pretty fast.

Categories

Resources