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();
}
Related
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.
I'm drawing a tooltip after clicking inside a custom bar chart (created with MPAndroidChart). The view hierarchy is as follows
<LinearLayout>
<TextView text=Move & Max Pain/>
<RelativeLayout with 2 textviews>
<chart
clipToChildren=false
clipToPadding=false
/>
</LinearLayout>
While the View is inside the Chart or its inmediate sibling, everthing looks good. But the moment it collides with its sibling, the tooltip is truncated
Using HierarchyViewer I can see that the content is present, but it's not drawn.
In order to get the clipping, I'm using this code inside draw
#Override
public void draw(Canvas canvas, float posx, float posy) {
// take offsets into consideration
posx += getXOffset();
posy += getYOffset();
canvas.save();
// translate to the correct position and draw
canvas.translate(posx, posy);
Rect clipBounds = canvas.getClipBounds();
clipBounds.inset(0, -getHeight());
canvas.clipRect(clipBounds, Region.Op.INTERSECT);
draw(canvas);
canvas.translate(-posx, -posy);
canvas.restore();
}
If I change Op to Region.Op.Replace, the tooltip is draw correctly but it replaces the Toolbar content, instead of scrolling under it.
You'll need the bounds of the area in which you want to be able to draw the tooltip, and I'm assuming that would be a scrollview. Then you can intersect the tooltip bounds with the scroll to work out what the clipping should be; and if it should be drawn at all.
To explain it in code it would be something like this (untested):
Rect scrollViewRect; // the bounds of your scrollview
Rect tooltipRect; // the bounds of your tooltip
bool intersects = tooltipRect.intersect(scrollViewRect)
if(intersects)
{
canvas.clipRect(tooltipRect, Region.Op.REPLACE);
draw(canvas);
}
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'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);