I want create layer on top of the activity layout and I need make hole like rectangle or circle, something like the attached image. I did this by using SurfaceView and its working fine on lower version on android but not on version 4+
[Sample screen shot] (https://docs.google.com/file/d/0B3yZokwUGKFgTkM1ek1nZHNyZ2s/edit?usp=sharing)
My code is
public class SurfacePanel extends SurfaceView implements SurfaceHolder.Callback{
public SurfacePanel(Context context) {
super(context);
init();
}
public SurfacePanel(Context context, AttributeSet attrs) {
super(context, attrs);
this.CommonInit();
}
private void init() {
setZOrderOnTop(true); //necessary
getHolder().setFormat(PixelFormat.TRANSPARENT);
getHolder().addCallback(this);
setBackgroundColor(Color.TRANSPARENT);
}
public void onDraw(Canvas canvas){
Path path = new Path();
path.moveTo(10, 10);
path.lineTo(115, 10);
path.lineTo(115, 260);
path.lineTo(10, 260);
path.close();
canvas.clipPath(path, Region.Op.DIFFERENCE);
canvas.clipRect(150, 30, 200, 100, Region.Op.DIFFERENCE);
canvas.drawARGB(180, 0, 0, 0);
super.onDraw(canvas);
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
}
Someone please help me out of this.
Thanks in advance.
This is probably because ICS+ defaults hardware acceleration to true, and some of your drawing calls on the canvas aren't supported in hardware mode, check the following:
http://developer.android.com/guide/topics/graphics/hardware-accel.html
You can turn it off in your application tag in your manifest, per activity in your manifest, or on the view itself with setLayerType
Related
I want to render a camera preview inside a circle, like this:
I have it working inside a fragment added to an activity. But I would like to show the view above other apps by adding it to the WindowManager.
So what I have working is showing views above other apps but the SurfaceView does not clip at all. And separately I have the SurfaceView clipping to circle, but in a fragment.
I have read many answers here and articles about this topic. There is very little info about cropping a SurfaceView, and no library that does what I want. Most of the info I found only explains how to crop it to a square or some other rectangle.
How it looks when added to WindowManager
I am trying to dra
How can I clip a SurfaceView that is shown above all views?
SurfaceView subclass
public class CircleSurface extends SurfaceView {
private Path clipPath;
public CircleSurface(Context context) {
super(context);
init();
}
public CircleSurface(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public CircleSurface(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
setZOrderMediaOverlay(true);
setWillNotDraw(false);
SurfaceHolder sfhTrackHolder = getHolder();
sfhTrackHolder.setFormat(PixelFormat.TRANSPARENT);
clipPath = new Path();
//TODO: define the circle you actually want
clipPath.addCircle(710, 330, 250, Path.Direction.CW);
}
#Override
protected void dispatchDraw(Canvas canvas) {
canvas.clipPath(clipPath);
super.dispatchDraw(canvas);
}
}
Service that adds views to the window manager
public class LiveStreamingService extends Service implements SurfaceHolder.Callback {
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForeground(startId, getNotification());
}
sharedInstance = this;
// android shouldn't auto restart this service
return START_NOT_STICKY;
}
#Override
public void onCreate() {
WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);
// camera view
CircleSurface cameraSurfaceView = new CircleSurface(this);
cameraSurfaceView.getHolder().addCallback(this);
wm.addView(cameraSurfaceView, getLayoutParams(360, 270));
}
private WindowManager.LayoutParams getLayoutParams(int width, int height) {
int overlayType = android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O
? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
: WindowManager.LayoutParams.TYPE_PHONE;
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
width,
height,
overlayType,
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
PixelFormat.TRANSLUCENT
);
return layoutParams;
}
// ...
}
UPDATE
Seems like the camera rendering is somehow separate from the dispatchDraw method. I can do whatever in dispatchDraw and the camera keeps showing as a rectangle.
Created a drawing thread for the surface view that listens to the camera preview callback with OnPreviewFrame.
Inside the thread's drawing loop:
c.drawColor(0, PorterDuff.Mode.CLEAR);
c.drawColor(0x1000000); // magical color that removes everything (like transparency)
if (currentFrame == null) return;
c.clipPath(circleSurfaceView.path);
c.drawBitmap(currentFrame, 0, 0, paint);
I have no idea what the magical color is. I came across it by luck. Everything else I tried would just draw black or fuzzy noise.
I need to draw a line diagram using surfaceview so from Main activity i will pass 2 parameters to surfaceview following this [link][1] solution
[1]: Passing arguments to SurfaceView via Constructor but still my passed parameters are 0 inside surfaceview draw method, please help
MySurfaceView.java:
private int len,theta;
public void setParameter(int length, int angle){
this.len = length;
this.theta = angle;
System.out.println("inside setParameter, len: "+len+" ,theta: "+theta);
}
protected void drawSomething(Canvas canvas) {
System.out.println("Inside drawSomething() , len: "+len+" ,theta: "+theta);
canvas.drawColor(Color.WHITE);
}
MainActivity.java
MySurfaceView myView = new MySurfaceView(MainActivity.this);
myView.setParameter(90,30);
myView.invalidate();
Values are printed only inside setParameter() not inside drawSomething(), i need to use those values inside drawSomething(),please help
Edited my code below,
public MySurfaceView(Context context) {
super(context);
init();
}
private void init(){
paint = new Paint();
paint.setColor(Color.BLUE);
paint.setStrokeWidth(3);
paint.setStyle(Paint.Style.STROKE);
surfaceHolder = getHolder();
surfaceHolder.addCallback(new SurfaceHolder.Callback(){
#Override
public void surfaceCreated(SurfaceHolder holder) {
canvas = holder.lockCanvas(null);
drawSomething(canvas);
holder.unlockCanvasAndPost(canvas);
}
#Override
public void surfaceChanged(SurfaceHolder holder,
int format, int width, int height) {
// TODO Auto-generated method stub
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
// TODO Auto-generated method stub
}});
}
Since drawSomething(canvas) is called in a callback, you cannot know if it will be called earlier or not than your next function setParameter(int, int).
If you want to have the values available for drawSomething(canvas), you have to pass them in the constructor of MySurfaceView, or extend the class SurfaceHolder.Callback and pass the value to those.
I'm making a application it's picture cropping.
But Galaxy nexus has some problem.
Region.Op.DIFFERENCE is not works.
Desire(2.3.3) and GalaxyNexus(4.1) Emulator works well.
But not works only on GalaxyNexus Real Phone
Please see the code...
It's a onDraw overrided method it's extended imageview
#override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//all rectangle
getDrawingRect(viewRect);
//small rectangle
getDrawingRect(smallRect);
smallRect.left += 100;
smallRect.right -= 100;
smallRect.bottom -= 200;
smallRect.top += 200;
// paint color setting to transparency black
mNoFocusPaint.setARGB(150, 50, 50, 50);
// All Rectangle clipping
canvas.clipRect(viewRect);
// Small Rectangle clipping
canvas.clipRect(smallRect, Region.Op.DIFFERENCE);
// Draw All Rectangle transparency black color it's except small rectangle
canvas.drawRect(viewRect, mNoFocusPaint);
}
solved!
add this code in manifest
android:hardwareAccelerated="false"
: )
JuHyun's answer is great! In my case however I didn't want to remove hardware acceleration for my entire app across all SDK versions. The issue with hardware-accelerated canvas clipping seems to be confined to 4.1.1, so I took the route of disabling hardware acceleration for the specific view in which I was performing the clipping operation.
Custom view class (in this case, a RecyclerView):
public class ClippableRecyclerView extends RecyclerView {
private final CanvasClipper clipper = new CanvasClipper();
public ClippableRecyclerView(Context context) {
super(context);
configureHardwareAcceleration();
}
public ClippableRecyclerView(Context context, AttributeSet attrs) {
super(context, attrs);
configureHardwareAcceleration();
}
public ClippableRecyclerView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
configureHardwareAcceleration();
}
public void configureHardwareAcceleration() {
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN) {
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
}
/**
* Remove the region from the current clip using a difference operation
* #param rect
*/
public void removeFromClipBounds(Rect rect) {
clipper.removeFromClipBounds(rect);
invalidate();
}
public void resetClipBounds() {
clipper.resetClipBounds();
invalidate();
}
#Override
public void onDraw(Canvas c) {
super.onDraw(c);
clipper.clipCanvas(c);
}
}
Canvas clipper class:
public class CanvasClipper {
private final ArrayList<Rect> clipRegions = new ArrayList<>();
/**
* Remove the region from the current clip using a difference operation
* #param rect
*/
public void removeFromClipBounds(Rect rect) {
clipRegions.add(rect);
}
public void resetClipBounds() {
clipRegions.clear();
}
public void clipCanvas(Canvas c) {
if (!clipRegions.isEmpty()) {
for (Rect clipRegion : clipRegions) {
c.clipRect(clipRegion, Region.Op.DIFFERENCE);
}
}
}
}
I'm writing a game for Android, and would like to use the built-in layout system to lay things out based on the size and shape of the screen (which also means it'll change orientation dynamically), so I've written an interface (called "Renderable") that is wrapped by a View-derived class (called "RenderableView").
Since I am using the latest version of Eclipse, the Custom & Library Views section of the Layout Editor's tool pallet does show my class, but shows several copies of each View-derived class.
Is this a bug in Eclipse or with my code?
Here is my Renderable interface:
package games.DigSite;
import android.graphics.*;
public interface Renderable
{
/**
* Sets the new position and size for this Renderable.
* #param bounds THe new bounds for this Renderable.
*/
public void setBounds(RectF bounds);
/**
* Returns the current position and size of this Renderable.
* #return The bounds this Renderable has.
*/
public RectF getBounds();
/**
* Tells the Renderable to draw itself.
* #param canvas The Canvas to draw to.
*/
public void render(Canvas canvas);
}
And here is the code for my RenderableView class:
package games.DigSite;
import games.DigSite.play.*;
import android.content.*;
import android.graphics.*;
import android.util.*;
import android.view.*;
import android.view.SurfaceHolder.Callback;
public class RenderableView extends SurfaceView implements Callback, PlayConstants
{
private RectF bounds;
private Renderable renderable;
public RenderableView(Context context)
{
super(context);
getHolder().addCallback(this);
}
public RenderableView(Context context, AttributeSet attrs)
{
super(context, attrs);
getHolder().addCallback(this);
}
public RenderableView(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
getHolder().addCallback(this);
}
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
{
bounds = new RectF(0, 0, width, height);
if (renderable != null)
renderable.setBounds(bounds);
}
public void setRenderable(Renderable render)
{
renderable = render;
}
public Renderable getRenderable()
{
return renderable;
}
public void surfaceCreated(SurfaceHolder holder)
{
// Dunno if we need this one just yet
}
public void surfaceDestroyed(SurfaceHolder holder)
{
// Dunno if we need this one just yet
}
public void onDraw(Canvas canvas)
{
if (renderable != null)
renderable.render(canvas);
else
{
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(BACKGROUND_COLOUR);
canvas.drawRect(bounds, paint);
}
}
}
I'm not sure if this is the place to ask this, so please censor this as necessary.
This is a bug in ADT; the Eclipse code search engine returns the same class twice. I've changed the data structure where we accumulate custom views from using a List to using a Set. This fix will go into ADT 15.
I'm trying to work with 3 SurfaceViews on one screen, one on top half (BoardView), one on bottom half (StatusView), and the last one as an extra layer above the top half (TileView) (see main.xml).
I created a class MySurfaceView, which is extended by BoardView, StatusView and TileView.
I've got multiple problems with this.
Let me first give the code.
main.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#color/main_background">
<com.niek.test.BoardView
android:id="#+id/boardview"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
<FrameLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_below="#+id/boardview">
<com.niek.test.StatusView
android:id="#+id/statusview"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#F0931E"
android:layout_below="#+id/boardview" />
<com.niek.test.TileView
android:id="#+id/tileview"
android:layout_width="180dip"
android:layout_height="60dip"
android:layout_gravity="bottom"/>
</FrameLayout>
</RelativeLayout>
MainActivity.java:
package com.niek.test;
public class MainActivity extends Activity {
private Board board;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
board = new Board();
BoardView boardView = (BoardView) findViewById(R.id.boardview);
boardView.setBoard(board);
StatusView statusView = (StatusView) findViewById(R.id.statusview);
statusView.setBoard(board);
}
}
MySurfaceView.java
package com.niek.test;
public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback {
protected DrawThread drawThread;
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
getHolder().addCallback(this);
setFocusable(true);
drawThread = new DrawThread(getHolder());
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
// TODO Auto-generated method stub
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
// TODO Auto-generated method stub
drawThread.setRunning(true);
drawThread.start();
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
// we have to tell thread to shut down & wait for it to finish, or else
// it might touch the Surface after we return and explode
boolean retry = true;
drawThread.setRunning(false);
while (retry) {
try {
drawThread.join();
retry = false;
} catch (InterruptedException e) {
// we will try it again and again...
}
}
}
protected class DrawThread extends Thread {
private SurfaceHolder surfaceHolder;
private boolean isRunning;
public DrawThread(SurfaceHolder surfaceHolder) {
this.surfaceHolder = surfaceHolder;
isRunning = false;
}
public void setRunning(boolean run) {
isRunning = run;
}
public void run() {
Canvas c;
while (isRunning) {
try {
Thread.sleep(100);
} catch (Exception e) {
// TODO: handle exception
}
c = null;
try {
c = surfaceHolder.lockCanvas(null);
synchronized (surfaceHolder) {
onDraw(c);
postInvalidate();
}
} finally {
// do this in a finally so that if an exception is thrown
// during the above, we don't leave the Surface in an
// inconsistent state
if (c != null) {
surfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
}
}
These three classes extend MySurfaceView:
BoardView.java
package com.niek.test;
public class BoardView extends MySurfaceView {
private int squareSize, marginX, marginY;
private Board board;
Paint boardBorder;
public BoardView(Context context, AttributeSet attrs) {
super(context, attrs);
board = null;
}
public void setBoard(Board board) {
this.board = board;
}
private void init(SurfaceHolder holder) {
Canvas canvas = null;
try {
canvas = holder.lockCanvas();
/* Initialize the board */
squareSize = canvas.getWidth() / Board.GRIDSIZE;
/* Size the view */
LayoutParams lp = getLayoutParams();
lp.height = (squareSize * Board.GRIDSIZE) + 4;
setLayoutParams(lp);
/* Place the board neatly in the center */
marginX = (canvas.getWidth() - (squareSize * Board.GRIDSIZE)) / 2;
marginY = 1;
} finally {
holder.unlockCanvasAndPost(canvas);
}
boardBorder = new Paint();
boardBorder.setColor(Color.RED);
boardBorder.setStyle(Style.STROKE);
}
#Override
public void onDraw(Canvas canvas) {
drawBoard(board, canvas);
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
init(holder);
super.surfaceCreated(holder);
}
private void drawBoard(Board board, Canvas canvas) {
synchronized (board) {
if (board != null) {
for (Square[] ys : board.getSquares()) {
for (Square xs : ys) {
xs.onDraw(canvas, squareSize, squareSize, marginX, marginY);
}
}
}
canvas.drawRect(marginX - 1, marginY - 1, marginX + squareSize * Board.GRIDSIZE + 1, marginY + squareSize * Board.GRIDSIZE + 1, boardBorder);
}
}
}
StatusView.java
package com.niek.test;
public class StatusView extends MySurfaceView {
private Board board;
private Paint textPaint;
public StatusView(Context context, AttributeSet attrs) {
super(context, attrs);
board = null;
textPaint = new Paint();
textPaint.setColor(Color.BLACK);
textPaint.setTextSize(20);
textPaint.setTypeface(Typeface.DEFAULT_BOLD);
}
public void setBoard(Board board) {
this.board = board;
}
int tmp=0;
#Override
public void onDraw(Canvas c) {
if (board != null) {
c.drawText(tmp+"", 10, 20, textPaint);
tmp++;
System.out.println(tmp);
}
}
}
TileView.java
package com.niek.test;
public class TileView extends MySurfaceView {
public TileView(Context context, AttributeSet attrs) {
super(context, attrs);
System.out.println(0);
}
int tmp =0;
#Override
public void onDraw(Canvas c) {
System.out.println(2);
Paint p= new Paint();
p.setColor(Color.RED);
c.drawColor(Color.RED);
c.drawText(tmp+"",10,10,p);
tmp++;
}
}
Now what are my problems?
First off, as you can see in MySurfaceView I've got this:
try {
c = surfaceHolder.lockCanvas(null);
synchronized (surfaceHolder) {
onDraw(c);
postInvalidate();
}
}
When I only use onDraw(c), only the BoardView gets drawn, the StatusView doesn't get drawn, but the tmp increments in the onDraw of StatusView are being executed.
When I only use postInvalidate(), same story, but only StatusView gets drawn, BoardView doesn't.
So that's why I use both methods, and both Views get drawn.
Then there's TileView, the System.out(2) is being shown in logcat, but the view doesn't get drawn. It is a black square instead of the red square I ask it to be in the onDraw method.
When I turn the screen off and then on again, the TileView does get drawn, and the tmp increments are shown.
Who can help me?
For clarity, I've created this based on this tutorial.
You can have multiple SurfaceViewsin one layout. The "Multi-surface test" activity in Grafika has three.
The first post cited in #nonsleepr's answer was followed up 9 months later with this post by the same author, which mentioned the existence of SurfaceView#setZOrderMediaOverlay().
The key thing to understand is that SurfaceView is not a regular view. When your app comes to the foreground it gets a surface to draw on. Everything in your app's UI is rendered onto the app's surface by the app, and then that surface is composited with other surfaces (like the status bar and navigation bar) by the system compositor. When you create a SurfaceView, it's actually creating an entirely new surface that is composited by the system, not by your app.
You can control the Z-ordering (i.e. "depth") of the SurfaceView surface very loosely. There are four positions, from top to bottom:
SurfaceView + ZOrderOnTop
(app UI goes here)
SurfaceView + ZOrderMediaOverlay
SurfaceView (default)
If you have two SurfaceViews at the same depth, and they overlap, the results are undefined -- one will "win", but you can't control which.
The system compositor on modern devices is very efficient when you have N surfaces. At N+1 surfaces you hit a performance cliff. So while you can have three SurfaceViews, you're generally better off keeping the number down. The value of N varies from device to device.
Update: if you really want to understand how SurfaceView works, see the Android System-Level Graphics doc.
It looks like you are not supposed to create multiple SurfaceViews on one Layout.
According to this two posts written by Android framework engineer:
The way surface view is implemented is that a separate surface is created and Z-ordered behind its containing window, and transparent pixels drawn into the rectangle where the SurfaceView is so you can see the surface behind. We never intended to allow for multiple surface view.
and
you should effectively think of SurfaceView as an overlay you embed inside your window,
giving you an area in which you can directly draw independently of the normal view update system.
So, what you can do, is use one SurfaceView to draw all the graphics you want.
It sounds like the SurfaceViews are being drawn, but transparency is not enabled for whichever one is on top. In your MySurfaceView class in the surfaceCreated() method, make sure you are calling holder.setFormat(PixelFormat.TRANSPARENT);