I'm trying to implement a custom view that is created by some data - specifically I am trying to create a revenue projection graph via a custom view.
The way i'm doing this is by having 10 data points, and drawing lines between each data point. (and then also drawing the X and Y axis lines).
I was thinking about creating a view that encapsulates the 10 coordinates, and draw them via the canvas in onDraw(). something like this:
class RevView extends View {
private List<Point> mPoints;
public void configurePoints(float[] revenues) {
// convert revenues to points on the graph.
}
// ... constructor... etc.
protected void onDraw(Canvas canvas) {
// use canvas.drawLine to draw line between the points
}
}
but in order to do so, i think i need the width/height of the view, which isn't rendered until onDraw.
is this the right approach? or am i even supposed to pass in a Points list to the view? Or what's a better way to build this?
You should override the onMeasure() method which will be called before onDraw().
https://developer.android.com/reference/android/view/View.html#onMeasure(int,%20int)
Measure the view and its content to determine the measured width and the measured height.
You should use onMeasure() method which is called before onDraw() method, so you'll be able to get your view dimensions like this:
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = measureWidth(widthMeasureSpec);
int height = measureHeight(heightMeasureSpec);
}
You should avoid getting View dimensions directly in onDraw() method because it will be called many times a second
Related
I use these two methods in my project, but two values sometimes equal, sometimes not.
public class HintView extends LinearLayout {
#Override
protected void dispatchDraw(Canvas canvas) {
this.getWidth();
canvas.getWidth();
}
}
If you inspect the source code for both LinearLayout and Canvas, you'd notice:
LinearLayout's getWidth() method is actually from View.java, a class LinearLayout extends:
* Return the width of your view.
*
* #return The width of your view, in pixels.
*/
#ViewDebug.ExportedProperty(category = "layout")
public final int getWidth() {
return mRight - mLeft;
}
If you on the other hand check out the Canvas' method:
/**
* Returns the width of the current drawing layer
*
* #return the width of the current drawing layer
*/
public int getWidth() {
return nGetWidth(mNativeCanvasWrapper);
}
The n prefix is Google's way to saying: this goes to native (c++) code.
The view and its canvas can and will be different if the view has paddings, margins, etc. (i don't recall of off the top of my head all the related properties) but remember that a Canvas is just a "surface" with some given dimensions, contained in a View.
All things on Screen on Android are one form of View or another. A view needs to measure, and ask its children (if a ViewGroup, which is also a View), and then, after all this is done, it can confidently start drawing (onDraw(Canvas)).
I'm interested in using a custom View to draw, measure, and display a set of buttons that is dependent on a back-end for my application. This requires me to implement this in Android dynamically. Would you help me get started?
Here we go: first in my MainActivity I instantiate my custom class which inherits from TableLayout which is also a view:
var keyboardView = new KeyboardView(this, layout, droidLayout, this.Colors);
Then I set the content view to the fresh instance of my custom class: SetContentView(keyboardView); Here's my class's constructor which just helps me get scope on all of the info I need:
public KeyboardView(Context context, KeyboardLayout layout, int droidLayout, Dictionary<string, int> colors)
: base(context) {
this._Colors = colors;
this._Context = context;
this.KeyboardLayout = layout;
this.SetWillNotDraw(false);
//this.ButtonLayout = ll;
this.DrawingCacheEnabled = true;
this.DroidLayout = droidLayout;
I've also overridden both OnMeasure and OnDraw:
protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int h = 100;
int w = 100;
//Overall keyboard/View dimensions //Difference between Canvas size and KeyboardDimensions?
this.SetMeasuredDimension(w, h);
//this.Layout.CanvasSize.
}
protected override void OnDraw(Canvas canvas) {
var bt = new Button(this._Context);
bt.Text = "laaaa";
this.AddView(bt);
}
Now what's happening is that OnDraw and OnMeasure both get it, in fact, OnDraw seems to be getting hit lots of time -- more so then I wish to count. However, the one button that I added via AddView is NOT drawn on the screen. If you guys could help me get this one button on the screen I can get to writing the core logic!
On a side-note: I can draw stuff on my screen if I set an XML layout file as the view as such: SetContentView(Resource.Layout.LayoutName) But, since the nature of my program requires dynamic views being added all the time, I'd rather avoid writing lengthy Layout files. Thanks guys! Bump my question up if you think it's a worthwhile one!
In general, this is a good place to start. In particular it details how to add custom attributes, how to perform custom drawing, and how to design custom events that make sense in the context of your own application.
Also, it looks like you're trying to use C# style syntax in Java, which won't work for things like inheritance. Reading some java tutorials might help you out.
This is a good resource for that and should help you get up and going. Good luck!
Don't forget to call the super() to allow the parent class to do what it needs to do for the overridden method.
protected override void OnDraw(Canvas canvas) {
super(canvas)
}
Related link
http://developer.android.com/guide/topics/ui/custom-components.html#compound
I have a custom view which I quickly learned to not do object allocations in and moved all my Paint allocations into a different method, fine.
I need to use a StaticLayout however for some text that had some spannable 'stuff' applied.
layoutBpm = new StaticLayout(bpmValue,
textPaintBPM, arcRadius, Layout.Alignment.ALIGN_NORMAL, 0, 1,
false);
layoutBpm.draw(canvas);
This seemed to make sense to me to have in onDraw but I am of course getting warned to avoid this. It is important to me that I do everything I can to avoid any performance issues so I am trying to do this properly.
I can't seem to find any documentation I can understand that explains what some of the parameters to StaticLayout actually do (float spacingmult? float spacingadd?), but the third one there I think is maximum width for the StaticLayout text which I can only get from onMeasure as it relates to my canvas size. This leaves me wanting to put the assignment in onMeasure or onDraw, neither of which it seems I am meant to do. Is it okay to put StaticLayout assignment in onDraw or is there a better way to do this?
Hope this makes sense, I am very new to this. Thank you for any help.
(edit: I assume putting this assignment in a method and calling from onDraw/onMeasure is just being silly and will stop Eclipse warning me but wont actually help?)
The reason for the warning is because, very often, you do not need to recreate an object multiple times in a draw operation. onDraw() could be called hundreds of times compared to other methods in a View. Most of the time, the objects being recreated are being recreated with the exact parameters. Other times, it's simply less overhead to change an object's state than it is to create a new object.
In the case of a StaticLayout, you only need to create a new one when the text changes or when you adjust the padding, spacing, or maxwidth. If the text changes often, then you may want to consider DynamicLayout which will remeasure itself every time. Remeasuring costs more overhead than creating a new object, but there's no way it's happening more often than onDraw() calls.
If the text doesn't change often and you absolutely MUST use a StaticLayout, then you can get away with something like this structure.
StaticLayout myLayout;
String textSource = defaultSource;
Paint textPaint = defaultPaint;
int textWidth = defaultWidth;
public CustomView(Context ctx) {
super(ctx);
createLayout();
}
public void setText(String text) {
textSource = text;
createLayout();
}
public void setWidth(int width) {
textWidth = width;
createLayout();
}
#Override
public void onDraw(Canvas canvas) {
myLayout.draw(canvas);
}
private void createLayout() {
myLayout = new StaticLayout(textSource, textPaint, textWidth, Layout.Alignment.ALIGN_NORMAL, 0, 1, false);
}
Basically, you only create a new layout when something changes. Else you just reuse the last object you created.
EDIT:
One way to skip a measure pass is to call View#measure on the newly resized view itself. So your createLayout() method would be something like this.
private void createLayout() {
myLayout = new StaticLayout(textSource, textPaint, textWidth, Layout.Alignment.ALIGN_NORMAL, 0, 1, false);
int textWidthSpec = MeasureSpec.makeMeasureSpec(maxLayoutWidth, MeasureSpec.AT_MOST);
int textHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.AT_MOST);
myLayout.measure(textWidthSpec, textHeightSpec);
forceLayout();
}
Basically what this does is tells the layout the maximum the View can be. It will measure itself and it's children. The forceLayout() will be invoked on the parent (your custom view) which will re-layout the other contents based on the new measurements.
I should note that I have never done this with a StaticLayout so I have no idea what will happen. It seems like this type of measurement might already be handled when the View is created, but maybe not.
I hope this helps.
I have drawn a bitmap image over a canvas.
Bitmap image = BitmapFactory.decodeResource(getResources(), R.drawable.sq);
canvas.drawColor(color.black);
Rect dstRectForRender = new Rect(0,0,320,450);
canvas.drawBitmap(image, null,dstRectForRender,null);
The image gets displayed based on my screnn on a cnavs.
On my touch input, I need to pass the x and y co-ordinate position of the image and fill that pixel with a color to show that the image is painted on a drag event.
How can I pass the x and y coo-ordinate parameters? Which functions should I use to plot the pixels on the image?
I appreciate your help and sweet time.
I'm not sure if this is the best way to do this, but it would be much easier to do this if you defined your own subclass of ImageView and name it something like DrawableImageView. You'd have to make sure you implement all the basic constructors from ImageView, then override the onTouchEvent method. From that event you can get the touch coordinates and store them in an ArrayList<Point> and use that ArrayList by overriding the onDraw method and "painting" the image.
public class DrawableImageView extends ImageView {
ArrayList<Point> list = new ArrayList<Point>();
//constructors..
#Override
public boolean onTouchEvent (MotionEvent event) {
float x = event.getX();
float y = event.getY();
list.add(new Point(x,y));
invalidate();
}
This is just a very brief overview of how to start your class, and may not be the most accurate way of doing things (depending on your specific code). Now, instead of using <ImageView> tags in your xml (or, loading an ImageView programatically), you refer to your subclass like so:
<your.package.name.DrawableImageView
/>
Edit
In response to your comment, there is no predetermined way to draw over an image. You must implement this yourself, which is why I recommended storing Points in an ArrayList. I'm not really sure what you're trying to achieve here, but to draw (for example) black dots over an image you have to override onDraw:
public void onDraw(Canvas c) {
super.onDraw(c);
for(Point p : list) {
//Draw black point at x and y.. I'm posting from my cell so I can't go into much detail
}
}
Also, to force a View to redraw itself you need to use the invalidate() method in your onTouchEvent() (which I've added above).
I want to make a custom View so I extended the View class and override the onDraw(Canvas canvas) method.
The problems is, I found out that the method is never stopped being called.
well it seems that calling View.invalidate on a different View causes this view to redraw to.
I can't post the code in here so I try to describe only the relevant parts.
in the activity I create a FrameLayout m_mainLayout which is the one I finally pass to setContentView() method.
I add different Views and Layouts to m_mainLayout, one of them is GameView m_gameView which extends View and a GameFrameView which extends RelativeLayout and to this layout I add the View in question.
now, I constantly call (every ~100 ms) m_gameView.invalidate().
how does it cause other views to be redrawn ?
what do I need to do to stop this?
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas); <-- add this
int width = canvas.getWidth();
int height = canvas.getHeight();
Log.i("AttackDialogView", "onDraw ");
}