I'm trying to implement a Waveform visualizer in my app which draws the waveform on a canvas from a real-time audio input. (Screenshot below)
What I need to do is to make it in a way that it can be scrolled horizontally from left to right.
I figured I should use a HorizontalScrollView and make the canvas gradually grow in width so that it spans over the screen width. Now I'm wondering how this can be achieved?
Keep the Canvas's width the same and use the canvas.translate() method when actually drawing.
So, let's say you have a Rect rect that contains the rectangle to draw (whose boundaries may exceed that of the View), as well as int horizontalOffset = 0 as the variable holding the offset of your drawing rectangle which will be adjusted as the user touches the View.
Now, when onDraw(Canvas) is called:
#Override
public void onDraw(Canvas canvas){
super.onDraw(canvas);
canvas.save();
canvas.translate(horizontalOffset, 0);
canvas.drawRect(rect, paint);
canvas.restore();
}
Then you can then override onTouch(View, MotionEvent) to calculate the horizontal offset based on the touch event and then invalidate() the View.
Note: this is clearly only drawing a Rect, but you should be able to modify what I did in there to draw whatever you're drawing. The important parts are (in order) save(), translate(), draw, restore().
Related
I've written a custom view that displays text in the center of the screen as seen below.
I've also made it so that if you touch the green box you can rotate and scale the text as seen below.
Heres the problem.
Whenever I rotate the text and let go, then try to rotate again it cant detect that the rect is being touched using myRect.contains(X,Y).
After some time I found that after its rotated, and touch where the original green box was, it allows me to rotate again.
OnTouchEvent is obviously calculating the Rects position correctly since it drawing in the correct location. I just can figure out why the touch coordinates seem to be referencing old positions.
Here's my onDraw() method.
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
canvas.rotate((float)mAngle, mBorderRect.centerX(), mBorderRect.centerY());
canvas.drawRect(mBorderRect, mTextBorderPaint);
canvas.drawText(mText, mBorderRect.left, mBorderRect.bottom, mTextPaint);
canvas.drawRect(mResizeRect, mBGPaint);
canvas.restore();
}
Remember that you're rotating the canvas, not the Rect. To get this to work you'll have to apply the inverse rotation on the touch position first, then compute myRect.contains(X,Y).
I'm trying to override onDraw in an EditText subclass, to show a custom subtitle.
I've got it working, but there are a few bugs.
Basicall, all I need to do is draw StaticLayout at a certain offset from top left corner of the view.
Unfortunantly, all I get in my onDraw method is canvas. The size of the canvas is equal to the size of the whole screen (320x480 on a device with 320x480 display) and its clip bounds can be pretty much anything - it can be the whole view; it can be only top or bottom part of the view if the view is inside scrollview and partially visible; it can even be same arbitrary rect inside the view, probably because superclass invalidates only some of its region.
So if I have this view with size 320x48, I can get canvas with size 320x480 and clipping rect (200, 200, 300, 230) (left, top, right, bottom). I don't understand how this clipping rect maps to my view coordinates.
I need to know where is top left corner of the clipping rect relative to the top left corner of the view. Unfortunantly, I cannot figure out how to get this.
Added:
This code will work on all os versions that I've tested:
private int[] coordinates = new int[2];
private Matrix identityMatrix = new Matrix();
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
getLocationInWindow(coordinates);
canvas.setMatrix(identityMatrix);
canvas.translate(coordinates[0], coordinates[1]);
//do the drawing in EditText coordinate space
canvas.restore();
}
However, I still have one question: why does it work?
I've trying overriding View class and it's onDraw method will always recieve a canvas which size matches that of the View itself. Canvas for direct View subclass will have no clipping rect. Same for TextView (direct ancestor of the EditText class). But it's not the same for EditText. Canvas that gets passed to onDraw method of EditText will always (or not?) have the size of the screen, and a custom clipping rect. This whole "translate by view coordinates in window thing" seems very hacky. I don't understand why I should translate the coordinate space.
I've tried hacking android source code for the answers, but found none. EditText has no onDraw of its own. Theoretically, there should be no difference between overriding TextView onDraw and EditText onDraw. But there is a difference. The canvas object passed to onDraw method will be different depending on whether its TextView or EditText. Why? How do I know when I should apply transformation to matrix, and when I shouldn't?
you can have it with the View.getLocationOnScreen method
I try to do circle menu like in this app.
In "expanded" mode i draw this component like follows:
<RelativeLayout android:id="#+id/bigCircle">
<!--color full borders-->
<my.custom.component android:id="#+id/middleCircle">
<!--circle for buttons-->
<RelativeLayout android:id="#+id/smallCircle">
<!--minus button-->
</RelativeLayout>
</my.custom.component>
</RelativeLayout>
In onDraw method of my.custom.component i divide circle on 8 parts by using android.graphics.Path with android.graphics.Paint and some math.
Visually i have exactly as shown in the screenshot. But when i press on part of circle, i need redraw this part in another color to show user what something going on.
How i can redraw part of component's canvas cutting off from another part of canvas by android.graphics.Path for example. In another word i know what redraw canvas i should do in onDraw method, i know that i can show some bitmap from drawables painted in photoshop and have some "multiscreen trouble", i know how i can determine part which user pressed. But i don't know how i can select part of canvas and redraw it.
Developer of Catch here. If I'm understanding your issue, you're having trouble understanding how to specifically draw the highlight/selection indicator on a section of your circular menu.
While there are plenty of different ways one could implement it, what you're leaning towards (using android.graphics.Path) is how we did it. In the view hierarchy of our capture button, there's an element that serves as the canvas on which the selection highlight color (if there is an active selection) is drawn.
If you had a similar custom View in your layout, you could duplicate this behavior like so. First, you'll need the Path that defines the selection for a particular circle segment. Using Path.addArc(RectF, float, float) we can get the pizza-slice-shaped path we need:
private Path getPathForSegment(float startAngle, float sweep) {
Point center = new Point(getWidth() / 2, getHeight() / 2);
RectF rect = new RectF(0f, 0f, getWidth(), getHeight());
Path selection = new Path();
selection.addArc(rect, startAngle, sweep);
selection.lineTo(center.x, center.y);
selection.close();
return selection;
}
The getWidth() and getHeight() above are for the enclosing custom view object, so they define the bounding box that contains the circle on which the selection is drawn.
Then, in your custom view's onDraw(Canvas), if your code has determined a selection should be drawn for a segment:
#Override
protected void onDraw(Canvas canvas) {
// Assume one has the rest of these simple helper functions defined
if (shouldDrawSelection()) {
float startAngle = getStartAngleOfSelectedSegment();
float sweep = getSweepAngle();
Paint paint = getPaintStyleForSelectedSegment();
Path path = getPathForSegment(startAngle, sweep);
canvas.drawPath(path, paint);
}
// ...
super.onDraw(canvas);
}
In the other areas of your code that are tracking touches, just call invalidate() on the custom view so that it will redraw (or not) the selection path based on changes in input or state.
Remember that it's good practice to avoid newing objects in onDraw(), so most of these building blocks (Paths, Paints, etc.) can be constructed ahead of time (or once, on first occurrence) and reused.
Hope this is close to what you were asking!
I am creating a custom view that extends the TextView. What I need is a couple of labels on the left handside and another checker on the right hand side of the TextView.
So I can draw those labels and then save the canvas and translate it right so that the text can be drawn beside the bitmaps. This is how I am doing that.
canvas.drawBitmap(bitmap1, 0, top, paint);
canvas.drawBitmap(bitmap2, bitmap1_width, top, paint);
canvas.save();
canvas.translate(bitmap1_width + bitmap2_width, 0);
Here I want to call super.onDraw but ask it to use the width so that the widths of the two bitmaps and the checker are subtracted.
Then I am can call my
canvas.drawBitmap(checker, checkerLeft, top, null);
So after translating the canvas, how can I can tell the canvas in the super.onDraw to use only the measured width? Please feel free to throw in any alternatives. I do want to use canvas though since my requirement is a little more complicated than this example.
Ok I did it by changing the scale factor using canvas.scale(sx, sy) before calling super.onDraw().
This is displaying the text in the way I want. If there is a better answer, please post it.
canvas.save();
canvas.translate(margin, 0);
canvas.scale(scaleX, 1f);
super.onDraw(canvas);
canvas.restore();
I have a rotated TextView class whose Canvas I rotate in the onDraw method:
#Override
protected void onDraw(final Canvas canvas) {
canvas.save();
canvas.rotate(-90);
canvas.translate(-getHeight(), 0);
super.onDraw(canvas);
canvas.restore();
}
The TextView is being positioned and rotated correctly, but the text seems to be getting clipped off:
How can I expand the Canvas correctly such that the TextView does not cut the text off?
Note the highlight area is the result of drawing the clip bounds of canvas right before super.onDraw(canvas) so I know the clip region is correct.
Of course the text is clipped off!
If you rotate the canvas, you just rotate the drawing inside the View, whereas the bounds (which don't depend by Canvas, but they're defined by the View) aren't rotated.
Therefore you have to rotate the whole View, just like the code below:
this.setRotation(-90); // 'this' is your view.