I'm trying to draw a circumference that would represent the battery life in a activity.
My parent layout is a relative layout.
This is the inner class that draws the view:
public class DrawView extends View {
Paint mPaint = new Paint();
public DrawView(Context context) {
super(context);
}
#Override
public void onDraw(Canvas canvas) {
Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG |
Paint.DITHER_FLAG |
Paint.ANTI_ALIAS_FLAG);
mPaint.setDither(true);
mPaint.setColor(Color.GRAY);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(1);
int size = 200;
int radius = 190;
int delta = size - radius;
int arcSize = (size - (delta / 2)) * 2;
int percent = 42;
//Thin circle
canvas.drawCircle(size, size, radius, mPaint);
//Arc
mPaint.setColor(getResources().getColor(R.color.eCarBlue));
mPaint.setStrokeWidth(15);
RectF box = new RectF(delta,delta,arcSize,arcSize);
float sweep = 360 * percent * 0.01f;
canvas.drawArc(box, 0, sweep, false, mPaint);
}
}
And in onCreate() I start the view this way:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
...
ViewGroup myLayout = (ViewGroup) findViewById(R.id.mainlayout);
drawing = new DrawView(this);
myLayout.addView(drawing);
}
But I need to locate this view in the layout, especifically in the center of it. To achieve this, I have modified onCreate()'s code this way:
ViewGroup myLayout = (ViewGroup) findViewById(R.id.mainlayout);
drawing = new DrawView(this);
RelativeLayout.LayoutParams layoutParams= new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT,
RelativeLayout.LayoutParams.WRAP_CONTENT);
layoutParams.addRule(RelativeLayout.CENTER_HORIZONTAL, RelativeLayout.TRUE);
drawing.setLayoutParams(layoutParams);
myLayout.addView(drawing);
But it isn't having effect on the view. What would be then the correct way to define the params for the view?
You're adding DrawView with LayoutParams.WRAP_CONTENT, which means the system needs to ask the view its width and height. You should implement onMeasure() for that. But I've never done that so don't know the details.
Another way is to just use LayoutParams.MATCH_PARENT and draw your stuff in onDraw() centered yourself. In this case you will need to know the width and height of the canvas in order to calculate the coordinates of your draw calls. Calling getWidth() in onDraw() does not give you the expected results. You need to override onSizeChanged() and record the new width and height, like this:
private int canvasWidth;
private int canvasHeight;
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
canvasWidth = w;
canvasHeight = h;
}
Then in onDraw() you can use canvasWidth and canvasHeight because the onSizeChanged() has already happened.
Related
I'm trying to draw 4 circles horizontally in a view. But the problem is I see only 3 half circles. Circles are not being drawn properly. What is wrong with below code?
CircleView.java
public class CircleView extends View
{
private int radius;
private Paint blackPaint = new Paint();
float eachDotWidth;
float x = 0;
float circleRadius;
float width, height;
public CircleView(Context context)
{
this(context, null);
}
public CircleView(Context context, AttributeSet attrs)
{
super(context, attrs);
blackPaint.setStyle(Paint.Style.STROKE);
blackPaint.setColor(Color.BLACK);
blackPaint.setStrokeWidth(5f);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh)
{
super.onSizeChanged(w, h, oldw, oldh);
calculateDimensions();
}
private void calculateDimensions()
{
width = getWidth() ;
height = getHeight();
invalidate();
}
#Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
width = getWidth();
height = getHeight();
eachDotWidth = getWidth()/5;
circleRadius = getHeight()/2;
canvas.drawColor(Color.WHITE);
for(int i=0; i < 4; i++) {
x=(i*eachDotWidth)-circleRadius;
canvas.drawCircle(x, 2*circleRadius, circleRadius/2, blackPaint);
}
}
}
MainActivity.java
public class MainActivity extends AppCompatActivity {
FrameLayout circleLayout;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
circleLayout = (FrameLayout) findViewById(R.id.circle_layout);
CircleView circleView = new CircleView(this);
circleLayout.addView(circleView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
}
}
Please help me find and resolve the issue. Thanks.
You can create vertically circles:
#Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
width = getWidth();
height = getHeight();
eachDotWidth = getWidth()/8;
circleRadius = getHeight()/2;
canvas.drawColor(Color.WHITE);
for(int i=0; i < 4; i++) {
x=(2*i*eachDotWidth)+eachDotWidth;
canvas.drawCircle(x, eachDotWidth, eachDotWidth, blackPaint);
}
}
Also you can create horizontally circles:
#Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
width = getWidth();
height = getHeight();
eachDotWidth = getWidth()/8;
circleRadius = getHeight()/8;
canvas.drawColor(Color.WHITE);
for(int i=0; i < 4; i++) {
x=(2*i*circleRadius)+circleRadius;
canvas.drawCircle(circleRadius, x, circleRadius, blackPaint);
}
}
And also you can create diagonal circles (create y variable):
#Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
width = getWidth();
height = getHeight();
eachDotWidth = getWidth()/8;
circleRadius = getHeight()/8;
canvas.drawColor(Color.WHITE);
for(int i=0; i < 4; i++) {
y=(2*i*circleRadius)+circleRadius;
x=(2*i*eachDotWidth)+eachDotWidth;
canvas.drawCircle(x, y, circleRadius, blackPaint);
}
}
I think you need to revise the code drawing the circles. I don't know why you, for example, have separate values for a circle's radius and a "dot's width". I suggest that you count only with an each circle's (~ dot's) radius and add some spacing in between them to compound the width to suit your needs.
Try something like this (it draws four equal circles at zero coordinates across the canvas' width, but it doesn't account for the stroke's thickness):
// we want four equal circles across the whole width, so each circle's radius will be equal to 'width/(2*4)'
circleRadius = width/8;
// zero spacing for this example, but it can be defined
int spacingPx = 0;
canvas.drawColor(Color.WHITE);
for(int i=0; i < 4; i++) {
// the x-coordinate of each circle's middle will be a circle's radius offset by the width of the circles previously drawn plus a single spacing width multiplied by the number of the circles already drawn
x=(i*2*circleRadius) + circleRadius + i*spacingPx;
// we just simply pass the values – calculated x coordinate, y coordinate (here we want to have the circle's top at 0, so we need to set its middle y-coordinate to the value of its radius), the circle's radius and the Paint object that you already defined
canvas.drawCircle(x, circleRadius, circleRadius, blackPaint);
}
Here's how it looks like
If you want to customize it more to your needs, please specify how exactly would you like the circles to be drawn.
Using a Path and a RectF is more flexible if you want to draw shapes, you can take a look at this tutorial :
http://raphaelfavero.github.io/Creating_Animated_Custom_View/
I'm using RelativeLayout to absolutely position some standard views (like TextView).
What I'd like to do is to draw a custom line on this RelativeLayout's Canvas using Canvas.drawLine that is drawn behind all its other subviews.
These other subviews are added with explicitely defining RelativeLayout.LayoutParams, but I'd like to leave the decision of where to paint itself to my custom line.
I tried wrapping this line in a CustomView with overloaded View.onDraw(Canvas canvas) method and simply adding the view without specifying any LayoutParams, so:
public class CustomView extends View {
public CustomView(Context context, int x0, int y0, int x1, int y1) {
super(context);
setClickable(false);
setBackgroundColor(Color.TRANSPARENT);
}
public void onDraw(Canvas canvas) {
Log.i("myapp", "i'm not called! :(")
Paint p = new Paint();
p.setColor(Color.BLACK);
canvas.drawLine(x0, y0, x1, y1, p);
}
}
And usage:
CustomView v = new CustomView(MyActivity.this, 0, 0, 100, 100);
relativeLayout.addView(v);
... but this onDraw method is never called.
Is there a way to make this work?
Edit: works if I substitute:
relativeLayout.addView(v)
with
relativeLayout.addView(v,
new RelativeLayout.LayoutParams(SOME_WIDTH, SOME_HEIGHT));
The point is, I know neither SOME_WIDTH, nor SOME-HEIGHT at that point.
try this custom RelativeLayout:
class RL extends RelativeLayout {
private Paint mPaint;
public RL(Context context) {
super(context);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setStrokeWidth(5);
mPaint.setColor(0xffffffff);
}
#Override
protected void dispatchDraw(Canvas canvas) {
int cnt = getChildCount();
for (int i = 0; i < cnt; i++) {
View child = getChildAt(i);
int l = child.getLeft();
int t = child.getTop();
int r = child.getRight();
int b = child.getBottom();
if (i % 2 == 0) {
canvas.drawLine(l, t, r, b, mPaint);
} else {
canvas.drawLine(l, b, r, t, mPaint);
}
}
super.dispatchDraw(canvas);
}
}
and test it ba adding the following in onCreate() method:
RelativeLayout rl = new RL(this);
TextView tv;
List<String> list = Arrays.asList("one", " two ", "three", " four ", "fife");
int i = 0;
for (String string : list) {
int id = 1000 + i;
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
if (i != 0) {
params.addRule(RL.BELOW, id - 1);
}
tv = new TextView(this);
tv.setTextSize(48);
tv.setTextColor(0xffff0000);
tv.setText(string);
rl.addView(tv, params);
tv.setId(id);
i++;
}
setContentView(rl);
So.
I ended up creating a CustomController which has some methods to calculate position/size and using this controller when creating RelativeLayout.LayoutParams for each CustomView(context, controller).
I guess you cannot have a subview in a RelativeLayout without specifying its RelativeLayout.LayoutParams.
Easiest way is to call the super.draw(Canvas) method after you finished your background in the onDraw() method.
That will cause it to draw the children last.
I'm trying to draw multiple rectangles inside of a linear layout which is sitting inside of a scrollview. This is my code for the rectangle view:
private class RectView extends View{
int leftX, rightX, topY, bottomY;
boolean isAppt;
private Paint rectPaint;
private Rect rectangle;
public RectView(Context context, int _leftX, int _rightX, int _topY, int _bottomY, boolean _isAppt){
super(context);
leftX = _leftX;
rightX = _rightX;
topY = _topY;
bottomY = _bottomY;
isAppt = _isAppt;
init();
}
private void init(){
rectPaint = new Paint();
if(isAppt){
rectPaint.setARGB(255, 0, 0, 255);
rectPaint.setColor(Color.BLUE);
}
else{
rectPaint.setARGB(255, 0, 0, 0);
rectPaint.setColor(Color.WHITE);
}
rectPaint.setStrokeWidth(2);
rectPaint.setStyle(Style.STROKE);
rectangle = new Rect(leftX, topY, rightX, bottomY);
}
protected void onDraw(Canvas canvas){
super.onDraw(canvas);
canvas.drawRect(rectangle, rectPaint);
}
}
And this is how I'm currently trying to display the rectangles:
RectView rv = new RectView(context, 0, 100, 0, 100, true);
firstDemarc.addView(rv);
firstDemarc.postInvalidate();
firstDemarc is the linear layout inside of my scrollview. Currently I'm not seeing any rectangles. The onDraw function is not being called. How do I properly display the rectangles inside of my scrollview?
You need to give the view some layout parameters:
int width = right - left;
int height = bottom - top;
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(width, height);
params.leftMargin = left;
params.topMargin = top;
setLayoutParams(params);
When you draw the rect, you need to draw from 0,0, until the width / height of the rect. The linear layout will handle the positioning according to the margins / layout params.
I'm looking for help with the following scaling problem.
I have built my own View class (custom view) which is a child of a Dialog.
In this custom view I plot a graph in vertical direction from bottom to top.
The graph can be greater or less than the size of the View.
Therefore I would like to draw the graph onto the Canvas (which is greater than the View) using the onDraw() method (see code below) and later scale the Canvas to fit into the View.
Things I've tried already include using a ScaleAnimation with duration=0 or calling the canvas.scale() method. Everytime the same result; the graph is not scaled.
I already read different threads like:
How to resize a custom view programmatically?
Android scale view
Thank you for your help.
VerticalGraphView Code:
public class VerticalGraphView extends View {
private static final String TAG = "VerticalGraphView";
private int[] ch1_data = new int[1000];
private int[] ch2_data = new int[1000];
private int mCanvasHeight = 1000;
private int mCanvasWidth = 242;
private Paint ch1_color = new Paint();
private Paint ch2_color = new Paint();
private Paint zero_color = new Paint();
private Paint grid_paint = new Paint();
private Paint outline_paint = new Paint();
public VerticalGraphView(Context context, AttributeSet attrs) {
super(context, attrs);
Log.d(TAG, "VerticalGraphView-Constructor called!");
}
protected void onFinishInflate() {
super.onFinishInflate();
Log.d(TAG, "onFinishInflate()-called!");
// Log.i(TAG, "New Size set for height = " + setSizeHeight);
}
protected void onDraw(Canvas canvas) {
Log.d(TAG, "onDraw()-called!");
// // RESIZE TO FIT THE DATA
// Bitmap b = Bitmap.createBitmap(mCanvasWidth, ch1_data.length, Bitmap.Config.ARGB_8888);
// Canvas canvas = new Canvas(b);
// canvas.setBitmap(b);
ch1_color.setColor(Color.BLUE);
ch2_color.setColor(Color.RED);
zero_color.setColor(Color.argb(80,0,0,00));
zero_color.setStrokeWidth(3f);
grid_paint.setColor(Color.rgb(200, 200, 200));
outline_paint.setColor(Color.BLACK);
outline_paint.setStrokeWidth(2f);
if (canvas != null) {
// Redraw the background
canvas.drawRGB(255, 255, 255);
// Draw vertical grey lines
for (int vertical = 1; vertical<6; vertical++) {
if (vertical == 3) { // Draw line in the middle
canvas.drawLine( vertical*(mCanvasWidth/6)+1, 1,
vertical*(mCanvasWidth/6)+1,
mCanvasHeight+1,
zero_color);
} else {
canvas.drawLine( vertical*(mCanvasWidth/6)+1, 1,
vertical*(mCanvasWidth/6)+1,
mCanvasHeight+1,
grid_paint);
}
}
// Draw horizontal grey lines
for (int horizontal = 1; horizontal<10; horizontal++) {
canvas.drawLine(1, horizontal*(mCanvasHeight/10)+1,
mCanvasWidth+1,
horizontal*(mCanvasHeight/10)+1,
grid_paint);
}
// draw outline
canvas.drawLine(0, 0, (mCanvasWidth+1), 0, outline_paint); // top
canvas.drawLine((mCanvasWidth), 0, (mCanvasWidth), (mCanvasHeight+1),
outline_paint); //right
canvas.drawLine(0, (mCanvasHeight), (mCanvasWidth), (mCanvasHeight),
outline_paint); // bottom
canvas.drawLine(0, 0, 0, (mCanvasHeight+1), outline_paint); //left
// plot data
int middle = mCanvasWidth / 2;
for (int x=0; x<(ch2_data.length-1); x++) {
canvas.drawLine((middle + ch2_data[x]),(mCanvasHeight - x),
(middle + ch2_data[x+1]),
(mCanvasHeight - x+1),
ch2_color);
canvas.drawLine((middle + ch1_data[x]),(mCanvasHeight - x),
(middle + ch1_data[x+1]),
(mCanvasHeight - x+1),
ch1_color);
}
}
Log.e(TAG, "canvas.Height = " + canvas.getHeight());
Log.e(TAG, "canvas.Width = " + canvas.getWidth());
// RESIZE TO FIT THE VIEW, only in Y-Direction
// Fits the canvas onto the view
float ratio = ((float) canvas.getHeight()) / (float) mCanvasHeight;
Log.e(TAG, "SCALE: ratio = " + ratio);
// ScaleAnimation anim = new ScaleAnimation(1f,1f,1f,ratio, 0.5f, 0.5f);
// anim.setDuration(1000);
// this.startAnimation(anim);
// canvas.scale(0f, ratio, canvas.getWidth() * 0.5f , canvas.getHeight() * 0.5f);
// canvas.save(Canvas.MATRIX_SAVE_FLAG);
// canvas.scale(0f, ratio, mCanvasWidth * 0.5f , mCanvasHeight * 0.5f);
// canvas.restore();
// canvas.scale(0f, 0.5f, mCanvasWidth * 0.5f , mCanvasHeight * 0.5f);
// canvas.scale(100, 100);
// canvas.getMatrix().postScale(0.5f, 0.5f);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(242, 500);
// params.addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE);
params.addRule(RelativeLayout.CENTER_IN_PARENT);
this.setLayoutParams(params);
// RelativeLayout layout = (RelativeLayout) findViewById(R.id.relativeLayoutRight);
// ViewGroup.LayoutParams params = layout.getLayoutParams();
// params.height = 500;
// params.width = canvas.getWidth();
// layout.setLayoutParams(params);
// invalidate();
// DRAW THE CANVAS
super.onDraw(canvas);
}
public void setData(int[] data1, int[] data2 ) {
Log.d(TAG, "setData()-called!");
ch1_data = data1;
ch2_data = data2;
}
/**
* This method sets the height of the View.</br>
* <b><u>NOTE:</u></b> The method call deletes all data stored for the graph.
* #param newHeight the new height of the view
*/
public void setHeight(int newHeight) {
mCanvasHeight = newHeight;
ch1_data = new int[newHeight];
ch2_data = new int[newHeight];
}
}
layout.xml which is used in the Dialog:
<com.android.Ui.VerticalGraphView
android:id="#+id/verticalGraphView"
android:layout_width="242dp"
android:layout_height="1000dp"
android:layout_centerInParent="true" />
you are not supposed to change the layout in the onDraw() method. in the onDraw method you have to take the current layout state and deal with it (and draw inside its boundaries).
try to calculate the needed size of the view , and then set the layout params . upon each time you need to scale , do it again . if an update is needed , call invalidate() .
I need to be able to access the size of the view's canvas to perform some calculations. For some reason, the size of the view passed to onSizeChanged is different than the size of the canvas passed to onDraw. My current workaround uses a boolean flag to determine when I need to do the calculations.
The ideal solution would allow me to do these calculations in the onSizeChanged method, so I'm wondering... is there any way I can get ahold of the Canvas object (or at least it's dimensions) outside of the onDraw method?
My code is below. It is draws the radius of a circle at a given angle. When I use canvas.centerX() to determine the start points and end points for the radius, everything works perfectly. If I use the parameters passed into onSizeChanged, it isn't even remotely close to correct.
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mSizeChanged = true;
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mSizeChanged) {
RectF bounds = new RectF(canvas.getClipBounds());
float centerX = bounds.centerX();
float centerY = bounds.centerY();
float radianAngle = (float) Math.toRadians(mStartAngle);
mRadius[0] = center;
mRadius[1] = center;
mRadius[2] = center + center * FloatMath.cos(radianAngle);
mRadius[3] = center + center * FloatMath.sin(radianAngle);
mSizeChanged = false;
}
mPaint.setColor(0xFF330000);
mPaint.setStrokeWidth(1);
canvas.drawLines(mRadius, mPaint);
}
For drawing purposes, you should not really use the dimensions of the Canvas object.
Just use the dimensions provided to you in the onSizeChanged method. You can either store the dimensions for use in the onDraw method or resize/draw to a backing bitmap that you can draw with later.
Update:
Quickly whipped up some code, it looks like this works:
public class CustomView extends View{
private Paint paint;
private int w;
private int h;
public CustomView(Context context, AttributeSet attr) {
super(context, attr);
paint = new Paint();
paint.setTextAlign(Align.CENTER);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
this.w = w;
this.h = h;
super.onSizeChanged(w, h, oldw, oldh);
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.WHITE);
canvas.drawText("TEST", w/2, h/2, paint);
}
}
Update 2
Following the circle code update.
We can do this:
#Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.WHITE);
float centerX = (float) w/2;
float centerY = (float) h/2;
float radianAngle = (float) Math.toRadians(startAngle);
radius[0] = centerX;
radius[1] = centerY;
radius[2] = centerX + centerX * FloatMath.cos(radianAngle);
radius[3] = centerY + centerY * FloatMath.sin(radianAngle);
paint.setColor(0xFF330000);
paint.setStrokeWidth(1);
canvas.drawLines(radius, paint);
}
You'll see that this now works on any sized view.