Prevent custom SurfaceView from drawing on top of other views - android

I have created a custom SurfaceView which I use to draw shapes, and the user can zoom and pan the view to interact with the shapes. This all works great, but the problem now is that the shapes will always be drawn on top of everything on my screen, also on top of other views such as buttons and textviews. I have tried ordering the custom view and my buttons in different ways in the XML layout, tried parenting the views to FrameLayouts and RelativeLayouts (as suggested here).
I would like to have buttons and other layout elements floating on top of my custom view. As seen in the image below, when I pan my view the rectangle will obscure the buttons.
How can I prevent my custom SurfaceView from drawing on top of everything?
My drawing code runs in a separate thread in the SurfaceView class:
public class CustomShapeView extends SurfaceView implements Runnable, SurfaceHolder.Callback {
/* Other implementation stuff */
#Override
public void run() {
while (isDrawing) {
if (!holder.getSurface().isValid())
continue; //wait till it becomes valid
Canvas canvas = null;
try {
canvas = holder.lockCanvas(null);
synchronized (holder) {
try {
lock.lock();
canvas.concat(sampleMatrix);
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
drawShapes(canvas);
} finally {
lock.unlock();
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (canvas != null) {
holder.unlockCanvasAndPost(canvas);
}
}
}
}
}
My test XML layout, which I would expect to create a button (Left) which is behind the custom view and a button (Right) which is floating above the custom view, is:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Left"
android:id="#+id/button"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"/>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.example.CustomShapeView
android:id="#+id/floor_plan_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</FrameLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Right"
android:id="#+id/button2"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"/>
</RelativeLayout>
</RelativeLayout>

In my code I was calling the function setZOrderOnTop(true); which made sure that the surface view would always be drawn on top of everything. This piece of code was left in by the previous developer in order to have a custom background for the canvas instead of only a solid color. With SurfaceView it is a tradeoff between having a custom background for the canvas or being able to draw views on top of the canvas.
The options are
With setZOrderOnTop(true); there can be custom backgrounds but no overlaying views.
With setZOrderOnTop(false); there can be overlaying views but only solid color backgrounds for the canvas (by calling e.g. canvas.drawColor(Color.WHITE); to clear between draw calls).
I chose the second option as I value overlaying views over custom backgrounds.

I'm facing a similar issue. I want to put transparent SurfaceView between another views which are being animated in RelativeLayout. When I do so, during animation views sometimes dissapear, sometimes only part of them is visible, it is weird. Then I discovered if I put SurfaceView to the layout first (so at the bottom) and then I call SurfaceView.setZOrderMediaOverlay(true), the content of SurfaceView is drawn at the bottom of the window, view are drawn on the top. If I put SurfaceView anywhere in the layout and then call SurfaceView.setZOrderOnTop(true), the content is always drawn on the top of the window (and also on the top of the views). If I put SurfaceView between views, its behavior is crazy, maybe it's because content of SurfaceView is drawn from separated thread and views are drawn from GUI (main) thread so they are "fighting" and that's why this behavior. Only way I found is drawing at the bottom or at the top. Try to create a new Fragment with transparent background where the SurfaceView is at the bottom as I described in first case and put that buttons you want to have above on top of it (so add them later in some RelativeLayout or FrameLayout) then show this fragment on the top of activity which holds content you want to have below it. Hopefully I helped a bit.

Related

Android how to draw several lines on an ImageView as another layer?

I would like to have the app draw several lines for showing user directions by using a background image as a base view and the app "draws" onto it in another layer on top of the background ImageView. Several lines would be made for the objectives to be met, and also in a specific location, so it has to have several layers too right??
I found a trick for drawing a single line using XML
<View
android:id="#+id/line"
android:layout_width="2dip"
android:layout_height="300dip"
android:background="#000000"
/>
But that code doesn't show positions and not in a layered form.
Update :
I would like the lines to appear through drawing, not through another picture
You can make a custom SurfaceView class, set the image as background and paint your lines on its Canvas. If I correctly understood what you want to achieve, that's probably the easiest way.
SurfaceView is a widget from the Android framework. Extend it to make your own: MySurfaceView extends SurfaceView. Then override public void onDraw(Canvas canvas), as android_user77 noted. Use this canvas object to paint on. Finally, add your class in the layout xml:
<com.your.app.MySurfaceView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#drawable/my_image" />
For details as why to use SurfaceView instead of View:
Difference between SurfaceView and View?
You can extend view and do the drawing by overriding the onDraw method
Use FrameLayout to create many "layers". I do not think you want to draw anything on the view. So do not use onDraw(), SurfaceView...
The code is like:
<FrameLayout ...>
<ImageView id="#+id/layer1"...>
<ImageView id="#+id/layer2"...>
</FrameLayout>

Set background on SurfaceView

I’m developing an Android drawing app, using a SurfaceView (to be more specific it’s a class that extends SurfaceView). I already saw a lot of topics on this forum, but I didn’t get my answer.
My activity is basically a FrameLayout, which contains all of my views. I would like to set my SurfaceView on the lowest level of my FrameLayout, in order to see the upper elements (buttons, fragments, etc.). I also would like to set the background of my SurfaceView with a drawable but I’m facing problems.
I tried first to set the background of my SurfaceView itself and it works. Unfortunately, my painting content (canvas and bitmap) was overload by this background so I tried a second solution. I apply the drawable background to my FrameLayout, and I set my SurfaceView background as transparent, using setZOrderOnTop. My SurfaceView was indeed transparent, but my drawing stuff was above my buttons and fragments.
So my first question is: why do we need to setZOrderOnTop to get a transparent background? Then, how can I set a simple drawable background, keeping my structure hierarchy?
Thank’s for your answers!
XML View:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/mainFrame"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#drawable/repeat_background"
tools:context=".MyActivity">
<!-- Option menu -->
<fragment ... />
<Button ... />
<Button ... />
<Button ... />
...
</FrameLayout>
#drawable/repeat_background
<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:src="#drawable/drawing_font_texture"
android:tileMode="repeat" />
MyActivity
#Override
protected void onCreate(Bundle p_savedInstanceState) {
super.onCreate(p_savedInstanceState);
setContentView(R.layout.main_controller_activity);
// Getting my FrameLayout
FrameLayout mainFrame = (FrameLayout) findViewById(R.id.mainFrame);
// Instantiating my SurfaceView
this.drawableView = new MCustomDrawableView(getApplicationContext());
// Don't works
//setZOrderMediaOverlay(true);
this.drawableView.setZOrderOnTop(true);
this.drawableView.getHolder().setFormat(PixelFormat.TRANSLUCENT);
FrameLayout.LayoutParams style = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT);
// Add the SurfaceView to the FrameLayout, on lowest position
mainFrame.addView(this.drawableView, 0, style);
}
I think your solution is on the right way. Set the background to the framelayout or add another layout under the surfaceview for your drawable. Then increase the z indices of the surfaceview AND the z indices of the buttons/elements that should be over the surfaceview,otherwise they are under it.
If you use material design, you can also set the elevation in dp for nice shadows.

Draw a line on top of existing layout programmatically

I have an existing layout that I am setting my current activity to. However, I want to draw a line (horizontal) and move it down in slow motion. Most articles talk about creating custom view and doing
setContentView(myView) .
How ever I dont want to set my activity view to only this view. I already did setContentView(R.layout.main). And I just want to draw the line on top of moving contents.
Something like drawLine(fromX, fromY, toX, toY) and then add a loop while increasing Y to show it in motion.
I hope I am clear. Please point me to the right direction.
Thank you
create a view and then animate it.
<View
android:id="+#id/ivHorizontalLine"
android:layout_width="match_parent"
android:layout_height="1px"
android:background="#000000" />
change the height of the view to match how thick you want the line to be. and the background color for the color of the line.
TranslateAnimation horizontalLineAnimation = new TranslateAnimation(0, 0, YstartPoint, YendPoint);
horizontalLineAnimation.setDuration(duration);
ivHorizontalLine.startAnimation(horizontalLineAnimation);
change YstartPoint and YendPoint to match where you want the line to move from and to. and the duration to match how fast you want the line to move.
The best way to do this is create a View that takes up the entire container that you would like to paint on top of. No background is necessary, as it is only to be used to create a canvas on it. An example would be like this:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<com.packagename.PaintView
android:id="#+id/paintView"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</FrameLayout>
And PaintView would be a public class PaintView extends View

Android custom view and alpha animation

I have a custom view on top of other views which I am fading out using a ViewPropertyAnimator like this:
mCustomView.animate().setDuration(sFadeDuration).alpha(0f).setListener(new Animator.AnimatorListener() { ... });
This works but the problem is that I have some views under the mCustomView which will only show after the animation has finished.
What I want is for them to be revealed while the view on top fades away. Strangely, there is one view which behaves as expected, and that is a VideoView (see below for layout structure).
So I see the VideoView fading in under the disappearing view but all the other views just pop up after the animation has finished.
The animated view is a custom view which overrides onDraw like this:
#Override
protected void onDraw(Canvas canvas) {
TextPaint paint = new TextPaint();
canvas.drawColor(Color.TRANSPARENT);
Canvas c = new Canvas(mBitmap);
c.drawRect(0, 0, mMetrics.widthPixels, mMetrics.heightPixels, paint);
paint.setColor(mColor);
canvas.drawBitmap(mBitmap, 0,0, paint);
}
When I do not override onDraw, everything works as expected.
Does anyone know why the other views are not rendered until the animation finishes? Is there a solution to avoid this? Do I have to somehow apply the animation inside of the onDraw method?
Here's the layout structure:
<RelativeLayout>
<VideoView />
<RelativeLayout>
<LinearLayout>
<TextView />
<TextView />
</LinearLayout>
<LinearLayout>
<View />
</LinearLayout>
<LinearLayout>
<Button />
<Button />
</LinearLayout>
<Button />
</RelativeLayout>
<CustomView />
</RelativeLayout>
VideoView is a subclass of SurfaceView. SurfaceView has special interactions, so that may explain why it is behaving differently. From the SurfaceView docs:
"The view hierarchy will take care of correctly compositing with the Surface any siblings of the SurfaceView that would normally appear on top of it. This can be used to place overlays such as buttons on top of the Surface, though note however that it can have an impact on performance since a full alpha-blended composite will be performed each time the Surface changes."
Try overriding view.isOpaque to be false in your custom view.

Draw a shape over a view

I have an activity with several ListViews in it. I need a rectangle with transparent background that overlaps some of the ListViews and I got lost a little.
I have some drawing coordinates that are known after all views are loaded and I want to put the graphics above the views programmatically.
Should I create a separate view that overlaps the activity window and draw on it or use the existing views? How do I start?
You can just add this in your layout xml:
<View android:id="#+id/view"
android:layout_width="300dp"
android:layout_height="300dp"
android:background="#android:color/transparent"/>
if you want views to overlaps another views use relative layout and build it correctly.

Categories

Resources