padding custom view canvas - android

I newbie to android and I have a question(probably dummy question ...)
I am creating a custom view by extending View element.
I start to draw and notice that my drawings is clipped a little bit at the top and left boundaries.
what should I do to solved it out?
protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec){
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int chosenDimention = Math.min(widthSize, heightSize);
setMeasuredDimension(chosenDimention, chosenDimention);
Log.v(TAG, "onMeasure: "+chosenDimention);
}
public void onDraw(Canvas canvas){
drawBackground(canvas);
Log.v(TAG, "onDraw");
Canvas c = new Canvas(background);
drawPie(c);
}
private void drawPie(Canvas canvas){
int width = getWidth();
canvas.save(Canvas.MATRIX_SAVE_FLAG);
canvas.scale(width, width);
// drawing stuff
canvas.restore();
}

Related

Custom View partially drawn in RelativeLayout

I want to use the custom view to draw a square onto the root layout (a RelativeLayout) of my activity. However, when x != 0 or y != 0, the square will only be partially drawn.
When x = 0 and y = 0 (The 3x3 grid is drawn by another custom view)
When x = 100 and y = 0
I also set its onTouchListener, and when I clicked the area surrounded by red line, it still fired. But I don't know why the X and Y of the View are not identical with the X and Y of the square shown.
The code to add my view to the layout
PieceView view = new PieceView(this, x, y, widthOfSquare, 0xFF5DADE2);
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(widthOfSquare, widthOfSquare);
layoutParams.leftMargin = (int)x;
layoutParams.topMargin = (int)y;
view.setLayoutParams(layoutParams);
rootLayout.addView(view);
My custom view
public class PieceView extends View{
private static final int strokeWidth = 5;
private Paint paint;
private RectF rect;
public PieceView(Context context, float x, float y, float size, int color) {
super(context);
init(x, y, size, color);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int desiredWidth = (int)rect.width();
int desiredHeight = (int)rect.height();
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width;
int height;
//Measure Width
if (widthMode == MeasureSpec.EXACTLY) {
//Must be this size
width = widthSize;
} else if (widthMode == MeasureSpec.AT_MOST) {
//Can't be bigger than...
width = Math.min(desiredWidth, widthSize);
} else {
//Be whatever you want
width = desiredWidth;
}
//Measure Height
if (heightMode == MeasureSpec.EXACTLY) {
//Must be this size
height = heightSize;
} else if (heightMode == MeasureSpec.AT_MOST) {
//Can't be bigger than...
height = Math.min(desiredHeight, heightSize);
} else {
//Be whatever you want
height = desiredHeight;
}
setMeasuredDimension(width, height);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawRect(rect, paint);
}
private void init(float x, float y, float size, int color){
paint = new Paint();
rect = new RectF(x, y, x + size, y + size);
paint.setColor(color);
paint.setStrokeWidth(strokeWidth);
}
}
Also, I'm wondering whether I should use
layoutParams.leftMargin = (int)x;
layoutParams.topMargin = (int)y;
or I should use
view.setX(x)
view.setY(y)
to specify the position of my square. If the ViewGroup I'm adding it to is a RelativeLayout with match_parent = true and it is the root layout of the Activity, is it different between two methods?
I've been stuck for hours and really appreciate any advice.
It turns out the problem is about the coordinates. In onDraw(), the coordinate system of canvas is of the View, not the global one. For example, if View.x = 100, View.y = 0, then the code in onDraw() will draw a rectangle whose top-left corner is (100+100, 0) in the global coordinate system.

Irregular shading in Graph

I tried to replicate a donut chart code that I found in the net. The code is as follows
public class DonutChart extends View{
private float radius;
SharedPreferences prefs;
Paint paint;
Paint shadowPaint;
int a,b,c;
Path myPath;
Path shadowPath;
RectF outterCircle;
RectF innerCircle;
RectF shadowRectF;
public DonutChart(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.DonutChart,
0, 0
);
try {
radius = a.getDimension(R.styleable.DonutChart_radius, 20.0f);
} finally {
a.recycle();
}
paint = new Paint();
paint.setDither(true);
paint.setStyle(Paint.Style.FILL);
paint.setStrokeJoin(Paint.Join.ROUND);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setAntiAlias(true);
paint.setStrokeWidth(radius / 14.0f);
shadowPaint = new Paint();
shadowPaint.setColor(0xf0000000);
shadowPaint.setStyle(Paint.Style.STROKE);
shadowPaint.setAntiAlias(true);
shadowPaint.setStrokeWidth(6.0f);
shadowPaint.setMaskFilter(new BlurMaskFilter(4, BlurMaskFilter.Blur.SOLID));
myPath = new Path();
shadowPath = new Path();
outterCircle = new RectF();
innerCircle = new RectF();
shadowRectF = new RectF();
float adjust = (.019f*radius);
shadowRectF.set(adjust, adjust, radius*2-adjust, radius*2-adjust);
adjust = .038f * radius;
outterCircle.set(adjust, adjust, radius*2-adjust, radius*2-adjust);
adjust = .276f * radius;
innerCircle.set(adjust, adjust, radius * 2 - adjust, radius * 2 - adjust);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// draw shadow
paint.setShader(null);
float adjust = (.0095f*radius);
paint.setShadowLayer(8, adjust, -adjust, 0xaa000000);
drawDonut(canvas, paint, 0, 359.9f);
//Orange
setGradient(0xffEF6632,0xffEF6632);
drawDonut(canvas,paint, 0,b);
//Blue
setGradient(0xff00CCDA,0xff00CCDA);
drawDonut(canvas, paint, 60,a);
// blue
// setGradient(0xff4AB6C1,0xff2182AD);
// drawDonut(canvas, paint, 120, 60);
// Grey
setGradient(0xff557687,0xff557687);
drawDonut(canvas, paint, 180,c);
}
public void drawDonut(Canvas canvas, Paint paint, float start,float sweep){
myPath.reset();
myPath.arcTo(outterCircle, start, sweep, false);
myPath.arcTo(innerCircle, start+sweep, -sweep, false);
myPath.close();
canvas.drawPath(myPath, paint);
}
public void setGradient(int sColor, int eColor){
paint.setShader(new RadialGradient(radius, radius, radius - 5,
new int[]{sColor, eColor},
new float[]{.6f, .95f}, TileMode.CLAMP));
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int desiredWidth = (int) radius*2;
int desiredHeight = (int) radius*2;
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width;
int height;
//70dp exact
if (widthMode == MeasureSpec.EXACTLY) {
width = widthSize;
}else if (widthMode == MeasureSpec.AT_MOST) {
//wrap content
width = Math.min(desiredWidth, widthSize);
} else {
width = desiredWidth;
}
//Measure Height
if (heightMode == MeasureSpec.EXACTLY) {
height = heightSize;
} else if (heightMode == MeasureSpec.AT_MOST) {
height = Math.min(desiredHeight, heightSize);
} else {
height = desiredHeight;
}
//MUST CALL THIS
setMeasuredDimension(width, height);
}
public void getData(int x,int y){
invalidate();
a=((x*360)/10);
b=(y*360)/10;
c=((10-(x+y))*360)/10;
String s1,s2,s3;
s1=String.valueOf(a);
s2=String.valueOf(b);
s3=String.valueOf(c);
Toast.makeText(getContext(),"Inside Chart "+s1+" "+s2+" "+s3 +" "+String.valueOf(x),Toast.LENGTH_SHORT).show();
}
}
The problem is when I render the graph on my device it gives me a weird shadow like this:
or like this:
What is causing this and how to rectify it?
This is actually caused because only the value of the line length is changed whereas the starting point is the same so sometimes they tend to overlap.
This can be solved by changing the code as follows
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// draw shadow
paint.setShader(null);
float adjust = (.0095f*radius);
paint.setShadowLayer(8, adjust, -adjust, 0xaa000000);
drawDonut(canvas, paint, 0, 359.9f);
//Orange
setGradient(0xffEF6632,0xffEF6632);
drawDonut(canvas,paint, 0,b);
//Blue
setGradient(0xff00CCDA,0xff00CCDA);
drawDonut(canvas, paint, b,a);
// Grey
setGradient(0xff557687,0xff557687);
drawDonut(canvas, paint, a+b,c);
}

How to make the contents of a custom view match its size when the parent has initial scale of 0?

Im developing an Android game and I have a problem that I've been trying to solve for some time now.
I have a custom view and I have overrided its onDraw and onMeasure. This custom view is inside either a LinearLayout or a RelativeLayout as follows
<LinearLayout
android:layout_width="0dip"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:layout_weight="2">
<com.leftcorner.craftersofwar.images.BitmapView
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
Now the problem is, that in the onMeasure function the parent has a size of 0. The only thing I can do is to scale my view according to the desired content size. However, if the bitmap is larger than the final size of the view, this will result in a misplaced canvas when I enter the onDraw function. The (0, 0) coordinates of the canvas are somewhere over the edge of the view itself.
I can get the parent size just before drawing with ((View) this.getParent()).getWidth();, compare the bitmap size to it and draw the bitmap in the correct scale, but drawing it to the canvas fails as the bitmap is out of the view bounds in a location unknown to me.
So, how could I draw this bitmap of mine to the canvas so that it would actually be visible? It would be better if it was also centered in the view.
Here is the code I currently have:
#Override
protected void onDraw(Canvas canvas) {
pWidth = ((View) this.getParent()).getWidth();
pHeight = ((View) this.getParent()).getHeight();
fWidth = Utility.smaller(pWidth, bWidth);
fHeight = Utility.smaller(pHeight, bHeight);
if(bitmap != null){
// As you can see, I'm not using a custom bitmap class.
// This function draws the bitmap to the canvas.
// (x, y, width, height, canvas)
bitmap.getStorageBitmap().drawScaledBitmap(0, 0, fWidth, fHeight, canvas);
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
bWidth = Math.round(bitmap.getWidth() * scale);
bHeight = Math.round(bitmap.getHeight() * scale);
if(bitmap != null) setMeasuredDimension(bWidth, bHeight);
else setMeasuredDimension(0, 0);
}
To get the dimension of the parent you can do as follows:
// set the id of your LinearLayout to parentLayout
LinearLayout parent = (LinearLayout)findViewById(R.id.parentLayout);
final ViewTreeObserver observer = parent.getViewTreeObserver();
observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#SuppressLint("NewApi")
#SuppressWarnings("deprecation")
#Override
public void onGlobalLayout() {
parent.measure(MeasureSpec.UNSPECIFIED,
MeasureSpec.UNSPECIFIED);
int parentHeight = parent.getMeasuredHeight();
int parentWidth = parent.getMeasuredWidth();
// set dimensions to bitmap
bitmap.getLayoutParams().height = parentHeight * scale;
bitmap.getLayoutParams().width= parentWidth * scale;
bitmap.requestLayout();
ViewTreeObserver obs = parent.getViewTreeObserver();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
obs.removeOnGlobalLayoutListener(this);
} else {
obs.removeGlobalOnLayoutListener(this);
}
}
});
You can put this code directly in onCreate
The answer given by cYrixmorten is correct, but I solved this issue in a bit different way. Please note that the height of my LinearLayout was set to wrap_content but the width of it comes from the weight.
Here is my code:
#Override
protected void onDraw(Canvas canvas) {
if(getWidth() < 1 || getHeight() < 1){
requestLayout();
} else if(bitmap != null){
bitmap.getStorageBitmap().drawScaledBitmap(0, 0, getWidth(), getHeight(), canvas);
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if(bitmap != null) {
bitmapWidth = Math.round(bitmap.getWidth() * scale);
bitmapHeight = Math.round(bitmap.getHeight() * scale);
} else {
setMeasuredDimension(0, 0);
return;
}
parentWidth = MeasureSpec.getSize(widthMeasureSpec);
if(parentWidth < bitmapWidth){
bitmapHeight = bitmapHeight * parentWidth / bitmapWidth;
bitmapWidth = parentWidth;
}
setMeasuredDimension(bitmapWidth, bitmapHeight);
}

Custom view measuring

I have made a custom view - it extends View. In the onDraw() method I create a circle with a set radius. At the moment in my xml, I have the layout_width and layout_height set to wrap_content. The circle is the right size, but when I set an onClickListener I don't have to touch the circle for it to register. I can tap anywhere where there is no other view.
I think I need to do something with onMeasure or LayoutParams but I don't know what exactly.
The aim is for the onClickListener to only be called when I click the circle with the layout_width and height still being set to wrap_content.
EDIT:
This creates a square not a circle as I wanted.
Here is my code:
protected void onDraw(Canvas canvas) {
canvas.drawCircle(canvas.getWidth() /2 , canvas.getHeight() /2,
RADIUS, paint);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// TODO Auto-generated method stub
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
widthMeasureSpec = RADIUS;
heightMeasureSpec = RADIUS;
setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
}
Try this:
float mTranslateX;
float mTranslateY;
public void onDraw(Canvas canvas){
super.onDraw(canvas);
canvas.save();
canvas.translate(mTranslateX, mTranslateY);
canvas.drawCircle(0, 0, RADIUS, paint);
canvas.restore();
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
final int dia = RADIUS * 2;
int w = resolveSize(dia, widthMeasureSpec);
int h = resolveSize(dia, heightMeasureSpec);
setMeasuredDimension(w, h);
float radius = Math.min(w, h)/2F;
mTranslateX = radius;
mTranslateY = radius;
}

Android GUI : trying to draw on a canvas

public class Player extends ViewGroup {
private RectF rect = new RectF();
private Paint paint;
public Player(Context context,String pname) {
super(context);
setWillNotDraw(false);
paint=new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setStyle(Style.FILL);
paint.setColor(getResources().getColor(R.color.red));
}
public void onDraw(Canvas canvas) {
canvas.drawRoundRect(rect, 10, 10, paint);
canvas.drawCircle(rect.centerX(), rect.centerY(), 10, paint);
//canvas.drawColor(Color.RED);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int wspec = MeasureSpec.makeMeasureSpec(
getMeasuredWidth(), MeasureSpec.EXACTLY);
int hspec = MeasureSpec.makeMeasureSpec(
getMeasuredHeight(), MeasureSpec.EXACTLY);
for(int i=0; i<getChildCount(); i++){
View v = getChildAt(i);
v.measure(wspec, hspec);
}
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
rect.set(l, t,r, b);
}
}
the third command does draw a red rectangle which bounds are the rect (l,t,r,b) = (412,415,735,754) which is given by the param rect, and for some reason, the two first commands do not do any effect on the canvas!
I have made sure the rect is an actual rectangle, as i mentioned its values were (412,415,735,754) which does make a valid rectangle, and you see how i defined the paint so why the hell wouldnt it draw?
been spending 2 hours trying to figure it out, seriously...
thanks!
BTW, the class extends ViewGroup cause it eventually meant to implement a view container..
Try this for your onLayout routine:
protected void onLayout(boolean changed, int l, int t, int r, int b) {
rect.set(0, 0, r-l, b-t);
}
This way you will create a rect with the width and height of the full layout, but whose top left point (relative to the canvas) is 0, 0.

Categories

Resources