Canvas Clip function bug on Galaxy Nexus - android

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);
}
}
}
}

Related

Drawing on Canvas, Adding Stickers, Custom Bitmaps - Android

I am looking to implement a feature that allows me to take predefined borders/images and place them over top a picture that a user has taken.
This app would be very similar to the functionality of Aviary, or Photo Editing Apps.
I cannot seem to figure out how to draw many images (say mustaches, noses, etc..) onto a canvas or view.
Is there a specific method to accomplishing this stacked bitmap/filter editing? I tried creating a custom View, but I can only set 1 setContentView();
My code is relatively simple right now, but I just wanted to know if there was a method of stacking views, or bitmaps individually.
public class CustomView extends View {
public CustomView(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
public CustomView(Context context, AttributeSet attrs) {
super(context);
}
#Override
public void onDraw (Canvas canvas) {
//Draw stuff in here
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.basketball);
canvas.drawBitmap(bitmap, 0, 0, null);
}
}
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(R.layout.activity_main);
CustomView m_View = new CustomView(this);
setContentView(m_View);
}
}
This logic can be implemented many ways. It is based on your requirements and details of the application. Basically you can do something similar to this:
#SuppressLint("WrongCall")
#Override
protected void onDraw(Canvas canvas) {
// use for-int to avoid object (iterator) allocations
int count = mItems.size();
for (int i = 0; i < count; i++) {
mItems.get(i).onDraw(canvas);
}
}
ArrayList<Item> mItems;
static class Item {
Context mContext;
Drawable mDrawable;
Rect mRect;
int mResourceId;
// resource ID to point to drawable resources
public Item(Context context, int resourceId) {
mContext = context;
mResourceId = resourceId;
mRect = new Rect();
}
public void onDraw(Canvas canvas) {
// allocate image only when it actually required
if (mDrawable == null) {
mDrawable = mContext.getResources().getDrawable(mResourceId);
}
// manage mRect to place sticker and etc.
if (mDrawable != null) {
mDrawable.setBounds(mRect);
mDrawable.draw(canvas);
}
}
}

Why onDraw is not called after invalidate()?

I have found many posts on stackoverflow but I still cannot solve my problem. Here is my code piece:
public class MyView extends RelativeLayout {
Button b1;
Button b2;
Context sContext;
public static int i = 0;
private int w = 400;
private int h = 400;
private int w2 = 100;
private int h2 = 100;
public MyView(Context context) {
super(context);
sContext = context;
init();
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
sContext = context;
init();
}
private void init() {
b1 = new Button(sContext);
addView(b1);
b1.setBackgroundColor(Color.YELLOW);
b1.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
if (w >= 600) {
MyView.this.setBackgroundColor(Color.GREEN);
//b1.setBackgroundColor(Color.RED);
} else {
MyView.this.setX(100);
}
MyView.this.invalidate();
w += 100;
w2 += 20;
}
});
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//b1.setBackgroundColor(Color.RED);
Toast.makeText(sContext, ""+i, Toast.LENGTH_SHORT).show();
++i;
}
}
Would you please explain why onDraw is not called the first three times I press b1? Because I called invalidate everytime I press b1. Thank you very much!
By default all ViewGroup sub-classes do not call their onDraw method, you should enable it by calling setWillNotDraw(false) link
Borrowing from the link #
Android Custom Layout - onDraw() never gets called
Try the below it works
1.If you're extending a ViewGroup (in your case a RelativeLayout) you should override dispatchDraw() instead of onDraw().
Discussion on the topic #
https://groups.google.com/forum/?fromgroups=#!topic/android-developers/oLccWfszuUo
protected void dispatchDraw (Canvas canvas)
Called by draw to draw the child views. This may be overridden by derived classes to gain control just before its children are drawn (but after its own view has been drawn).
Parameters
canvas the canvas on which to draw the view
Example
public class Hello extends Activity {
private MyView myView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
super.onCreate(savedInstanceState);
Log.e("hello", "hello");
this.myView = new MyView(this);
setContentView(this.myView);
}
public class MyView extends RelativeLayout
{
private Paint myPaint = new Paint();
private int[] numbers;
public MyView(Context paramContext)
{
super(paramContext);
Log.e("MyView", "MyView");
setFocusable(true);
setBackgroundResource(R.drawable.ic_launcher);
}
#Override
protected void dispatchDraw(Canvas canvas){
super.dispatchDraw(canvas);
Log.i("...............","drawing");
}
}
}
2.If you Override onDraw in the constructor call setWillNotDraw(false) then it should work.
http://developer.android.com/reference/android/view/View.html#setWillNotDraw(boolean)
public MyView(Context context) {
super(context);
sContext = context;
init();
setWillNotDraw(false);
}
You need to call setWillNotDraw(false); in order for your onDraw method to be called:
private void init() {
setWillNotDraw(false);
...
}
I was having OnDraw not called in C# using 4.1 Jelly Bean API 16. I switched from compiling with API 16 SDK to compiling with 7.1 Nougat SDK. It solved the problem completely. I still have in manifest min sdk of API 16 and it runs fine in my Jelly Beam avd.
In my case onDraw was not called because I returned 0 as width or height of view.

How to implement the parent activity code when custom child view is touched?

I have a custom ImageView('CustomImageView') and an Edit-text in a linear layout of an activity 'ImageViewActivity'. The Edit-text is initially set invisible. When the customimageview is touched, and onDraw() is called, I want the visibility of the Edit-text to be set visible. Where should I put the code for this?
Code for ImageViewActivity:
public class ImageViewActivity extends Activity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.custom_imageview);
}
}
And the code for CustomImageView is:
public class CustomImageView extends ImageView {
Paint paint = new Paint();
float xp = -1, yp = -1;
private Options opt;
public CustomImageView(Context context) {
super(context);
init();
}
public CustomImageView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public CustomImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
public void init() {
opt = new BitmapFactory.Options();
opt.inJustDecodeBounds = true;
paint.setAntiAlias(true);
paint.setColor(Color.RED);
paint.setStyle(Paint.Style.FILL);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {
xp = event.getX();
yp = event.getY();
invalidate();
}
return true;
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//some code
setMeasuredDimension(width, height);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (xp >= 0 && yp > 0) {
canvas.drawCircle(xp, yp, 20, paint);
}
}
}
Implement touch listener on your customImageview class and then call the edittext to hide it or show is as you need, for results of touch on custom view you have to define touchlistener on your customview not of activity and you can manage views of parent activity in touchevent of customview OR other method is implement touch listener on you activity and find the view which is touched if touched view is your custom view then make visible your Edittext. Then you will not need to do anything in onDraw() as regarding the edittext.
I'd recommend setting the view to visible once you have detected a touch motion on your particular view. Specifically it doesn't matter how the user touches it, since once they touch it you want to show your view (at least that is what you said you wanted).

Eclipse development for Android: Layout Editor's Custom Views dialog

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.

Android Multiple SurfaceViews

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);

Categories

Resources