Custom View not drawing itself - android

I read different questions here about this topic, but I still can't find an answer. Feel free to close this question for any reason.
I have a simple Circle class that extends View.
The code for this class is:
public class ProgressCircle extends View {
Paint mCirclePaint;
float extRadius;
float viewWidth, viewHeight;
float centerX, centerY;
public ProgressCircle(Context context, AttributeSet attrs) {
super(context, attrs);
setWillNotDraw(false);
init();
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
float xpad = (float) getPaddingLeft() + getPaddingRight();
float ypad = (float) getPaddingTop() + getPaddingBottom();
float ww = (float)w - xpad; float hh = (float)h - ypad;
extRadius = Math.min(ww, hh) / 2;
viewWidth = this.getWidth();
viewHeight = this.getHeight();
centerX = viewWidth / 2; centerY = viewHeight / 2;
super.onSizeChanged(w, h, oldw, oldh);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawCircle(centerX, centerY, extRadius, mCirclePaint);
canvas.drawText("ciao", 0, 0, mCirclePaint);
}
private void init() {
mCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mCirclePaint.setColor(0x666666);
mCirclePaint.setStyle(Paint.Style.FILL_AND_STROKE);
}
I confirmed that every method in this classed is called when the main activity is created (through the use of some Log.d()s). I added a <com.mypackage.Circle> element in my main activity's LinearLayout and after that I added a sample testing button.
What I achieved is that the button is shown while my Circle isn't, but still the button (which in the LinearLayout comes after the Circle) is not the first element of the layout: that makes me think that something actually happens, but nothing gets drawn.

It was just a silly problem: the color in mCirclePaint.setColor(0x666666) was an invalid one. It worked with mCirclePaint.setColor(Color.RED) or with any other color defined in the res folder.
If you need to specify a color value, you'll have to include the transparency byte (otherwise it is not a 32bit integer that you are specifying, but a 24bit). So 0x666666 is invalid, but 0xff666666 is a valid color and will draw.

After reading the documentation(http://developer.android.com/guide/topics/graphics/2d-graphics.html):
The Android framework will only call onDraw() as necessary. Each time
that your application is prepared to be drawn, you must request your
View be invalidated by calling invalidate(). This indicates that you'd
like your View to be drawn and Android will then call your onDraw()
method (though is not guaranteed that the callback will be
instantaneous).
Also another thing worth checking is the dimensions you're drawing insure nothing is invalid like a height of 0 etc..

I notice you're not overriding View.onMeasure().
Because you're not overriding this method onsizeChanged() might be passed size 0. You can check this by putting a breakpoint in the onSizechanged() method or printing to Logcat.

Related

Android Custom View: Is there a setup method called before onDraw that has access to Width

I have some code in an overwritten OnDraw(Canvas canvas) method in a custom view class which is called repetitively to draw circles using a caclulated radius. The radius only needs to be calculated once, but the view width is needed for the calculation so it cannot be done in the constructor.
circleRadius = Width / CIRCLES_TO_FIT_AT_ONCE / 2;
canvas.DrawCircle(Width / 2 + (i - startIndex) * circleRadius * 2, Height / 2, circleRadius, paint);
Is there somewhere I can move the circleRadius calculation so that it is not unnecessarally recalculated on every draw call? Perhaps another method to override?
Thanks to Mike M.
protected override void OnSizeChanged(int w, int h, int oldw, int oldh)
{
// Calculate circle size
circleRadius = w / CIRCLES_TO_FIT_AT_ONCE / 2;
}

Android 6.0 incorrectly handles drawCircle method

In my app I need to draw circles using bitmap and the drawCircle() method.
Everything was working fine and exactly as it should up until Android 6.0.
It still draws circles on all the previous versions, but draws rectangles when I use the app on 6.0. But if I change it to be filled, it draws a circle both in api 22 and api 23.
Anyone has the same problem or any idea why this happens?
Here is the source code and a screenshot (app running on API 23 on the left, and API 22 on the right). same app on different api's
public final class Circle1View extends View {
private float xCenter, yCenter;
private Bitmap grid = null;
public Circle1View (Context context) {
super(context);
init();
}
private void init() {
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
#Override
protected void onDraw(Canvas canvas) {
int w = getWidth();
int h = getHeight();
xCenter = w / 2;
yCenter = h / 2;
drawBitmaps(w, h);
canvas.translate(xCenter, yCenter);
canvas.scale(xCenter, yCenter);
canvas.drawBitmap(grid, null, new RectF(-1, -1, 1, 1), null);
}
private void drawBitmaps(int w, int h) {
grid = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas();
canvas.translate(xCenter, yCenter);
canvas.scale(xCenter, yCenter);
Paint gridPaint = new Paint();
gridPaint.setStrokeWidth(0.01f);
// Works with FILL
// gridPaint.setStyle(Paint.Style.FILL);
gridPaint.setStyle(Paint.Style.STROKE);
canvas.setBitmap(grid);
canvas.drawCircle(0, 0, 0.5f, gridPaint);
}
}
I think it has something to do with the scaling and translation you do. Imagine the circle that is drawn is so small, it only takes 4 pixels. When enlarging this back to the full size, you are left with 4 straight lines between these pixels.
When I change the stroke width to 0.04f, the issue is gone. I would suggest you simplify your code by drawing on the supplied Canvas directly:
#Override
protected void onDraw(Canvas canvas) {
int w = getWidth();
int h = getHeight();
xCenter = w / 2;
yCenter = h / 2;
Paint gridPaint = new Paint();
gridPaint.setStrokeWidth(1f);
gridPaint.setStyle(Paint.Style.STROKE);
canvas.drawCircle(xCenter, yCenter, w/4, gridPaint);
}
As for your question about the difference between API levels: Marshmallow introduced changes for drawBitmap(). You can have a look at the respective source code for Lollipop and Marshmallow.

Anti aliasing on rotated image with background?

I created an rotated imageview with background rotation but the result is "aliased".
The usual way is to create a Paint with anti-alias flag then use it to draw bitmap.
However, the background is draw by the view's draw(Canvas canvas) method, so i can't use a paint with canvas.drawBitmap method.
Here my code to rotate the imageview including the background :
#Override
public void draw(Canvas canvas)
{
canvas.save();
canvas.rotate(mAngle%360, canvas.getClipBounds().exactCenterX(), canvas.getClipBounds().exactCenterY());
canvas.scale(scaleWidth, scaleHeight, canvas.getClipBounds().exactCenterX(), canvas.getClipBounds().exactCenterY());
super.draw(canvas);
canvas.restore();
}
and my onMeasure method which adapts the height and the width depending of the angle
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int w = getDrawable().getIntrinsicWidth();
int h = getDrawable().getIntrinsicHeight();
double a = Math.toRadians(mAngle);
int width = (int) (Math.abs(w * Math.cos(a)) + Math.abs(h * Math.sin(a)));
int height = (int) (Math.abs(w * Math.sin(a)) + Math.abs(h * Math.cos(a)));
scaleWidth = (width != 0) ? ((float) w) / width : 1;
scaleHeight = (height != 0) ? ((float) h) / height : 1;
setMeasuredDimension(width, height);
}
So, How can i "enable" anti aliasing on this rotation?
Maybe use setDrawFilter on the canvas?
A DrawFilter subclass can be installed in a Canvas. When it is present, it can modify the paint that is used to draw (temporarily). With this, a filter can disable/enable antialiasing, or change the color for everything this is drawn.
edit: see here: https://stackoverflow.com/a/13191948/1954497

Custom Layout that rounds the corners of its content

I would like to create a generic ViewGroup which can then be reused in XML layouts to round the corners of anything that is put into it.
For some reason canvas.clipPath() doesn't seem to have an effect. What am I doing wrong?
Here is the Java code:
package rounded;
import static android.graphics.Path.Direction.CCW;
public class RoundedView extends FrameLayout {
private float radius;
private Path path = new Path();
private RectF rect = new RectF();
public RoundedView(Context context, AttributeSet attrs) {
super(context, attrs);
this.radius = attrs.getAttributeFloatValue(null, "corner_radius", 0f);
}
#Override
protected void onDraw(Canvas canvas) {
int savedState = canvas.save();
float w = getWidth();
float h = getHeight();
path.reset();
rect.set(0, 0, w, h);
path.addRoundRect(rect, radius, radius, CCW);
path.close();
boolean debug = canvas.clipPath(path);
super.onDraw(canvas);
canvas.restoreToCount(savedState);
}
}
Usage in XML:
<?xml version="1.0" encoding="utf-8"?>
<rounded.RoundedView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
corner_radius="40.0" >
<RelativeLayout
android:id="#+id/RelativeLayout1"
android:layout_width="match_parent"
android:layout_height="match_parent" >
...
</RelativeLayout>
</rounded.RoundedView>
The right way to create a ViewGroup that clip its children is to do it in the dispatchDraw(Canvas) method.
This is an example on how you can clip any children of a ViewGroup with a circle:
private Path path = new Path();
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
// compute the path
float halfWidth = w / 2f;
float halfHeight = h / 2f;
float centerX = halfWidth;
float centerY = halfHeight;
path.reset();
path.addCircle(centerX, centerY, Math.min(halfWidth, halfHeight), Path.Direction.CW);
path.close();
}
#Override
protected void dispatchDraw(Canvas canvas) {
int save = canvas.save();
canvas.clipPath(circlePath);
super.dispatchDraw(canvas);
canvas.restoreToCount(save);
}
the dispatchDraw method is the one called to clip children. No need to setWillNotDraw(false) if your layout just clip its children.
This image is obtained with the code above, I just extended Facebook ProfilePictureView (which is a FrameLayout including a square ImageView with the facebook profile picture):
So to achieve a round border you do something like this:
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
// compute the path
path.reset();
rect.set(0, 0, w, h);
path.addRoundRect(rect, radius, radius, Path.Direction.CW);
path.close();
}
#Override
protected void dispatchDraw(Canvas canvas) {
int save = canvas.save();
canvas.clipPath(path);
super.dispatchDraw(canvas);
canvas.restoreToCount(save);
}
You can actually create any complex path :)
Remember you can call clipPath multiple times with the "Op" operation you please to intersect multiple clipping in the way you like.
NOTE: I created the Path in the onSizeChanged because doing so in the onDraw is bad for performance.
NOTE2: clipping a Path is done without anti-aliasing :/ so if you want smooth borders you'll need to do it in some other way. I'm not aware of any way of making clipping use anti-aliasing right now.
UPDATE (Outline)
Since Android Lollipop (API 21) elevation and shadows can be applied to views. A new concept called Outline has been introduced. This is a path that tells the framework the shape of the view to be used to compute the shadow and other things (like ripple effects).
The default Outline of the view is a rectangular of the size of the view but can be easily made an oval/circle or a rounded rectangular. To define a custom Outline you have to use the method setOutlineProvider() on the view, if it's a custom View you may want to set it in the constructor with your custom ViewOutlineProvider defined as inner class of your custom View. You can define your own Outline provider using a Path of your choice, as long as it is a convex path (mathematical concept meaning a closed path with no recess and no holes, as an example neither a star shape nor a gear shape are convex).
You can also use the method setClipToOutline(true) to make the Outline also clip (and I think this also works with anti-aliasing, can someone confirm/refute in comments?), but this is only supported for non-Path Outline.
Good luck
You can override the draw(Canvas canvas) method:
public class RoundedLinearLayout extends LinearLayout {
Path mPath;
float mCornerRadius;
public RoundedLinearLayout(Context context) {
super(context);
}
public RoundedLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public RoundedLinearLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
public void draw(Canvas canvas) {
canvas.save();
canvas.clipPath(mPath);
super.draw(canvas);
canvas.restore();
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
RectF r = new RectF(0, 0, w, h);
mPath = new Path();
mPath.addRoundRect(r, mCornerRadius, mCornerRadius, Direction.CW);
mPath.close();
}
public void setCornerRadius(int radius) {
mCornerRadius = radius;
invalidate();
}
}
onDraw of FrameLayout is not called if Ur Layout's background not set;
You should override dispatchDraw;
ViewGroup (and hence its subclasses) sets a flag indicating that it doesn't do any drawing by default. In source it looks somewhat like this:
// ViewGroup doesn't draw by default
if (!debugDraw()) {
setFlags(WILL_NOT_DRAW, DRAW_MASK);
}
So your onDraw(...) probably doesn't get hit at all right now. If you want to do any manual drawing, call setWillNotDraw(false).
Don't forget to ask for onDraw to be called with setWillNotDraw(false); and set a value to mRadius then just do something like that :
#Override
protected void onDraw(Canvas canvas) {
mPath.reset();
mRect.set(0, 0, canvas.getWidth(), canvas.getHeight());
mPath.addRoundRect(mRect, mRadius, mRadius, Direction.CCW);
mPath.close();
canvas.clipPath(mPath);
}
U need to override the drawChild() method to clip childViews.
#Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
int flag = canvas.save();
canvas.clipPath(pathToClip);
boolean result=super.drawChild(canvas, child, drawingTime);
canvas.restoreToCount(flag);
return result;
}
If u want to clip the background of the ViewGroup too,override draw() instead.like this
#Override
public void draw(Canvas canvas) {
int flag = canvas.save();
canvas.clipPath(pathToClip);
super.draw(canvas);
canvas.restoreToCount(flag);
}
Why not just define a ShapeDrawable as the background of your layout and just change the corner radius of the drawable at runtime.

Howto Resize an android ShapeDrawable Directly Via Code without creating another

I am able to resize a drawn ShapeDrawable but it Creates another shape with the following code how can I just resize the original ShapeDrawable
I am Writing a drawing app and have been stuck on this for a couple of days
any help would be greatly appreciated
even a small nudge in the right direction please.
public void Resize(Context context){
setFocusable(true);
setFocusableInTouchMode(true);
this.setOnTouchListener(this);
//this.setOnClickListener(this.mDrawablecircle);
int x =Integer.parseInt("0");
int y =Integer.parseInt("0");
int cirw =Integer.parseInt("20");
int cirh =Integer.parseInt("20");
mDrawablecircle.getPaint().setStyle(Style.valueOf(Shapestoollistactivity.mf));
mDrawablecircle.getPaint().setColor(ColorPickerActivity.thecolor);
mDrawablecircle.getCurrent().setBounds(x, y, x + cirw, y + cirh);
mDrawablecircle.draw(mCanvas);}
// Update this is what I ended up doing still not 100%
// but somewhat working may help someone else
public void Resize(int newWidth){
setFocusable(true);
setFocusableInTouchMode(true);
this.setOnTouchListener(this);
int x = (int) mPosX;
int y = (int) mPosY;
width = mDrawablecircle.getBounds().width();
height = mDrawablecircle.getBounds().height();
float scaleWidth = ((float) newWidth) / width;
float ratio = ((float) mDrawablecircle.getBounds().width()) / newWidth;
int newHeight = (int) (height / ratio);
float scaleHeight = ((float) newHeight) / height;
matrix.setTranslate(mPosX+40,mPosY+40);
matrix.postScale(scaleWidth, scaleHeight);
postInvalidate();}
// and in In onDraw
// I Changed this: mCanvas.drawBitmap(mBitmap,0,0, mPaint);
// to: mCanvas.drawBitmap(mBitmap,matrix, mPaint);
hope this helps some one else Im sure there is a better solution
but I cant seem to find a better one as of yet feel free to chime in and
correct me Please this at least will do the resize of a shape drawable without
creating a new one but it resizes the whole canvas NOT WHAT I WAS AFTER
I would like to be able to resize individuale shapedrawables
Which are created from java code not xml or from a resource.
Instead of calling draw on mDrawableCircle like that do the following. I assume that your class is a custom view.
public void Resize(Context context) {
.....
invalidate();
}
public void onDraw(Canvas canvas) {
super.onDraw(paint);
//Do the drawing of the drawable here.
}

Categories

Resources