I have custom ViewGroup with 2 child views, matched to parent, and one on another, one is front and one is behind. I wanted to cut out circle in front view so behind view would be visible in that circle.
I did it with dispatchDrawfunction, code is following:
protected void dispatchDraw(Canvas canvas) {
System.out.println("dispatchDraw");
views.get(1).setVisibility(GONE); //views variable is list of child views
super.dispatchDraw(canvas);
Path pathHole = new Path();
pathHole.addCircle(touchPos[0], touchPos[1], 100, Path.Direction.CW);
canvas.clipPath(pathHole, Region.Op.DIFFERENCE);
views.get(1).setVisibility(VISIBLE);
super.dispatchDraw(canvas);
}
Basically what I did is hide front view and draw the ViewGroup (it drew only behind view),then I cut a circle path, set Visibility of front view and redraw again.
Here is the result:
Problem is dispatchDraw function goes on the loop because .setVisibility() calls this dispatchDraw again.
How to fix this problem or what would be the better approach of this result?
I managed same result with view.draw(canvas); function. Instead of adding views on ViewGroup I saved them in ListArray. then in dispatchDraw did following:
protected void dispatchDraw(Canvas canvas) {
System.out.println("dispatchDraw");
views.get(0).measure(canvas.getClipBounds().width(), canvas.getClipBounds().height());
views.get(0).layout(0, 0, canvas.getClipBounds().width(), canvas.getClipBounds().height());
views.get(0).draw(canvas);
Path pathHole = new Path();
pathHole.addCircle(touchPos[0], touchPos[1], 100, Path.Direction.CW);
canvas.clipPath(pathHole, Region.Op.DIFFERENCE);
views.get(1).measure(canvas.getClipBounds().width(), canvas.getClipBounds().height());
views.get(1).layout(0, 0, canvas.getClipBounds().width(), canvas.getClipBounds().height());
views.get(1).draw(canvas);
}
I tried this before, but I did not know that .draw(canvas) does not work without .measure() and .layout() functions, if it has not parent and is not visible already.
Related
I have a ViewGroup that consists on a header and a circle of menu items. Basically I have a closing/opening animation where my item views go behind the header view. Since all views have transparencies, when the item views go behind the header view, they are still visible and end up appearing behind the header view through the transparencies.
What I wanted to do is to intersect the item views with the hweader view, erasing the intersection. What I came up with was to override dispatchDraw and do something like PorterDuff.Mode.CLEAR
But I can only do this to all views at once, in the sense. Using the code below, it'll erase everything that's been drawn in the view in that specific area, thus the header as well.
#Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
//do stuff here
}
Is there any way i can draw the view again, or even select which views I want to erase?
Just for future reference, this is what I did. Override dispatch draw, erase the given area and draw the child again with child.draw(canvas)
#Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
Paint p = new Paint();
p.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
canvas.drawCircle((float) center.x, (float) center.y, headerSize / 2, p);
canvas.save();
canvas.translate(padding, padding);
getChildAt(0).draw(canvas);
canvas.restore();
}
I am using this ArcMenu https://github.com/daCapricorn/ArcMenu
Please check the code and the image of the ArcMenu on the github page
This custom view is arranged this way ->
ArcMenu[View] has Children -> ArcLayout[ViewGroup] has children -> ImageViews (to hold the icons for menu)
I am trying to draw colored arc behind the Arclayout. I am doing this in the onDraw
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
if(isExpanded()){
Log.d("ArcLayout","onDraw(Canvas)");
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(colorOne);
paint.setAlpha(125);
canvas.drawArc(rect, startAngle, sweepAngle, true, paint);
//Dimensions of rect are set in OnMeasure
}
super.onDraw(canvas);
}
Now the problem is that the OnDraw gets called for all the children and the logic for drawing is same for the parentView and children view. I want the Colored Arc to be drawn behind the parent view and not the children(Imageviews).
Rect dimensions are top=0, left=0, right=getMeasuredWidth(), bottom=getMeasuredHeight().
When the call to canvas.onDraw is made, top Left of the rect becomes relative to child i.e 0,0 of the child and the not the parent. The result is a colored arc behind the child and not the parent.
How should I ensure that the colored arc gets drawn behind the Parent view only?
I has my own View subclass that does Layout (I call it ViewGallery), my problem is that views that I draw manually wont appear at screen, here's it onDraw method.
#Override
protected void onDraw(Canvas canvas) {
for(Child child : visibleChilds){
canvas.save();
canvas.clipRect(child.bounds);
child.view.draw(canvas);
canvas.restore();
}
}
private List<Child> visibleChilds = new ArrayList<ViewGallery.Child>();
private static class Child {
private View view;
private Rect bounds;
public Child(View view, Rect rect) {
this.view = view;
bounds = rect;
}
}
As far as I know that should draw the inner view in the specified clipped Canvas.
Why the view is still empty?
Also I tried extends ViewGroup so I pass itself as parameter in a Adapter, but default ViewGroup.LayoutParams doesn't has left (or x) properties that I need to handle properly translation of views. But when subclassing it the onDraw never get called and childs still wont appear.
I'm not sure if I understand the question properly.
But if you are trying to draw a view on the canvas, you have to enable the drawing cache, get the bitmap from it and draw that.
example:
// you have to enable setDrawingCacheEnabled, or the getDrawingCache will return null
view.setDrawingCacheEnabled(true);
// we need to setup how big the view should be..which is exactly as big as the canvas
view.measure(MeasureSpec.makeMeasureSpec(canvas.getWidth(), MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(canvas.getHeight(), MeasureSpec.AT_MOST));
// assign the layout values to the textview
view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
mBitmap = view.getDrawingCache();
canvas.drawBitmap(mBitmap, x, y, mPaint);
// disable drawing cache
view.setDrawingCacheEnabled(false);
Ofcourse in this instance it'll just be a bitmap of the view drawn on the given location.
I'm extending LinearLayout:
public class MyLinearLayout extends LinearLayout {
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint = new Paint();
paint.setColor(0xFFFF0000);
canvas.drawLine(0, 0, getWidth(), getHeight(), paint);
}
}
If the layout has child elements, shouldn't the red line above be drawn on top of them? Considering a layout like:
<MyLinearLayout>
<ImageView />
</MyLinearLayout>
I'd expect to get the red line drawn above the child ImageView. But it gets drawn below it. I was assuming all child drawing would have been completed after the super.onDraw() line is finished.
Is there a way to get to the canvas and draw something on it after all child drawing has been completed?
Thanks
Layouts don't draw unless you call setWillNotDraw(false);. They do that for efficiency reasons.
EDIT:
onDraw() really is meant to just allow you to modify the Canvas before the drawing operation happens. It doesn't actually draw. What you want to do is override draw() like so:
#Override
protected void draw(Canvas canvas) {
super.draw(canvas);
Paint paint = new Paint();
paint.setColor(0xFFFF0000);
canvas.drawLine(0, 0, getWidth(), getHeight(), paint);
}
Calling the super will draw all the children in the view. Then it will draw all the lines. I do still believe you need setWillNotDraw(false) to be called though.
I'm rewriting my answer as I hadn't seen your line of code where you were drawing to the canvas.
The canvas like some other graphics environments is inverted. So calling getHeight() is actually drawing the line at the bottom. Instead call
canvas.drawLine( 0, 0, getWidth(), 0, paint);
This means draw the line at the top, across the width of the view.
Also, calling super first paints the children prior to any custom painting so your fine there.
If you have a LinearLayout and you need:
draw all of its children first, like buttons, image views
then, on top of those components, draw something directly on the canvas.
You can try this:
public class YourClass extends LinearLayout
{
#Override
protected void dispatchDraw(Canvas canvas)
{
super.dispatchDraw(canvas);
// do your custom drawings afterwards
canvas.drawCircle...
canvas.drawLine...
I am using the onDispatchDraw(Canvas canvas) method to draw lines in my view. When I call canvas.drawLine() it always draws the line on top of all my views. Is there any way to draw the line under a button but on top of another view in my layout using canvas.drawLine()?
I have tried the following but it still draws the line over the button.
Button b;
RelativeLayout r;
#Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
Paint p = new Paint();
p.setColor(Color.Black);
canvas.drawLine(0,0,100,100,p);
r.removeView(b);
r.addView(b);
}
You're trying to reinvent the wheel. Z-ordering is already implemented in the window management subsystem and you can use it.
Create a custom view you want to draw on.
Make it non-clickable using android:clickable="false" or setClickable(false).
Make its background transparent and implement dispatchDraw().
Put all the views you don't want to draw on above this view in the view hierarchy.
Call super.dispatchDraw() after drawing the line. The dispatchDraw is called by viewgroup for drawing its children, so in your case, calling super.dispatchDraw() will draw the button first then you are drawing the line over it. Do dispatchDraw this way :
Updated code
class myListViewWithLine extends ListView {
....
#Override
protected void dispatchDraw(Canvas canvas) {
Paint p = new Paint();
p.setColor(Color.Black);
canvas.drawLine(0,0,100,100,p);
super.dispatchDraw(canvas);
}
....
}
Another method will be to just add a view with 1 dip as height and background color whatever you like under the button. It should look like like a line.
View v = new View(this);
ViewGroup.LayoutParams lp = new LayoutParams(LayoutParams.FILL_PARENT,1);
v.setLayoutParams(lp);
v.setBackgroundColor(Color.WHITE);
addView(v,INDEX NUMBER AFTER THE BUTTON);