In my implementation, I have two images, one layed over the other. As, I move a circle object over the top image, I want to make that area inside circle transperent, so that I can see the beneath image. For example I have two images - a car image and its framework image. I overlay car image over framweork image and as i drag a circle over car image, it should show the beneath framework.
I tried to search a lot but not getting any pointers. Somewhere I read that I need to use alpha masking or image masking using porterduff and xfermode. but I did not understand.
Specifically,
How can I make the above image transperent and how can I only make the area inside circle transperent?
Thank You
I've used helpful inputs from the question PorterduffXfermode: Clear a section of a bitmap. In the example below, touch area becomes 'transparent' and it's possible to observe down_image part beneath up_image (both images are just jpg drawables in resources).
Basically there's two possible implementations:
Disabling hardware acceleration of drawing which would make PorterDuff.Mode.CLEAR to make view transparent (refer to here for more details about hardware acceleration effects):
Activity:
public class MyActivity extends Activity {
/** Overlay image */
private DrawingView mDrawingView = null;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
final RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
// Create the view for the xfermode test
mDrawingView = new DrawingView(this);
params.addRule(RelativeLayout.CENTER_IN_PARENT);
mDrawingView.setLayoutParams(params);
final RelativeLayout relativeLayout = new RelativeLayout(this);
relativeLayout.setBackgroundResource(R.drawable.down_image);
relativeLayout.addView(mDrawingView);
// Main part of the implementation - make layer drawing software
if (android.os.Build.VERSION.SDK_INT >= 11) {
mDrawingView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
// Show the layout with the test view
setContentView(relativeLayout);
}
}
DrawingView:
/**
* View which makes touch area transparent
*/
public class DrawingView extends View {
/** Paint to clear touch area */
private Paint mClearPaint = null;
/** Main bitmap */
private Bitmap mBitmap = null;
/** X coordinate of touch circle */
private int mXTouch = -1;
/** Y coordinate of touch circle */
private int mYTouch = -1;
/** Radius of touch circle */
private int mRadius = 0;
/**
* Default constructor
*
* #param ct {#link Context}
*/
public DrawingView(final Context ct) {
super(ct);
// Generate bitmap used for background
mBitmap = BitmapFactory.decodeResource(ct.getResources(), R.drawable.up_image);
// Generate array of paints
mClearPaint = new Paint();
mClearPaint.setARGB(255, 255, 255, 0);
mClearPaint.setStrokeWidth(20);
mClearPaint.setStyle(Paint.Style.FILL);
// Set all transfer modes
mClearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
mRadius = getResources().getDimensionPixelSize(R.dimen.radius);
}
#Override
public void onDraw(final Canvas canv) {
// Background colour for canvas
canv.drawColor(Color.argb(255, 0, 0, 0));
// Draw the bitmap leaving small outside border to see background
canv.drawBitmap(mBitmap, null, new RectF(0, 0, getMeasuredWidth(), getMeasuredHeight()), null);
// Loop, draws 4 circles per row, in 4 rows on top of bitmap
// Drawn in the order of mClearPaint (alphabetical)
if (mXTouch > 0 && mYTouch > 0) {
canv.drawCircle(mXTouch, mYTouch, mRadius, mClearPaint);
}
}
#Override
public boolean onTouchEvent(final MotionEvent event) {
boolean handled = false;
// get touch event coordinates and make transparent circle from it
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
mXTouch = (int) event.getX();
mYTouch = (int) event.getY();
// TODO: Note, in case of large scene it's better not to use invalidate without rectangle specified
invalidate();
handled = true;
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
case MotionEvent.ACTION_CANCEL:
mXTouch = -1;
mYTouch = -1;
// TODO: Note, in case of large scene it's better not to use invalidate without rectangle specified
invalidate();
handled = true;
break;
default:
// do nothing
break;
}
return super.onTouchEvent(event) || handled;
}
}
Adopt this solution, but looks like it depends on Android version, because suggested solution hasn't worked on mine 4.2 device at all.
Related
I'm trying to develop a game using a transparent canvas and a surfaceView, I use the canvas to paint and it works fine.
Now I want to add objects that will move on the screen, for example bouncing balls.
I want to use a new thread for the balls, (because otherwise if it's the same thread of the surfaceView it doesn't move smoothly ).
So I'm having trouble of doing this.
Can you please advise me how and when should I send the same canvas to a new ball object and then return it back to the surfaceview.
I'm not sure what the best way to handle this is,
I tried using a ball as a view in the xml layout, and it worked perfect on another thread but it doesn't make sense to create 1000 views when I can just draw them on a canvas.
Any insight will be helpful!
This is my code:
public class SurfaceMyPaint extends SurfaceView implements Runnable
{
Thread t;
SurfaceHolder holder;
Bitmap brush;
boolean isItOk = false;
public SurfaceMyPaint(Context context)
{
super(context);
holder = getHolder();
initial();
}
public void initial()
{
brush= BitmapFactory.decodeResource(getResources(), R.drawable.brush2);
}
public boolean onTouchEvent(MotionEvent event)
{
if(event.getAction()== MotionEvent.ACTION_DOWN)
{
x= event.getX();
y= event.getY();
}
if(event.getAction()== MotionEvent.ACTION_MOVE)
{
x = event.getX();
y= event.getY();
}
return true;
}
public void run()
{
while (isItOk == true)
{
if (!holder.getSurface().isValid())
continue;
myCanvas_w = getWidth();
myCanvas_h = getHeight();
if (result == null)
result = Bitmap.createBitmap(myCanvas_w, myCanvas_h, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(result);
canvas = holder.lockCanvas(null);
c.drawBitmap(brush, x - (brush.getWidth() / 2), y - (brush.getWidth() / 2), null);
canvas.drawBitmap(result, 0, 0, null);
// How can I add this here: Ball ball = new Ball (canvas)????
holder.unlockCanvasAndPost(canvas);
}
}
public void pause(){
isItOk = false;
while (true){
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
break;
}
t=null;
}
public void resume()
{
isItOk = true;
t = new Thread(this);
t.start();
}
}
Summarizing your rendering code:
Create a screen-sized off-screen bitmap, which you hold in a variable called "result" that isn't declared in the code you show. I assume this is a field, and the allocation only happens once?
Each time, you create a new Canvas for that Bitmap.
You draw on the Bitmap, with drawBitmap().
You copy the Bitmap to the Surface, with drawBitmap().
You want to add a step #5, "draw Ball".
The obvious place to do the ball drawing is in step #3, but I'm going to assume you're not doing it there because that has some relatively static content that you update infrequently but want to blend in every frame.
That being the case, you should just draw the balls onto the Canvas you got back from SurfaceHolder. What you must not do is store a reference to the Surface's Canvas in the Ball object itself, which it appears you want to do (based on new Ball(canvas)). Once you call unlockCanvasAndPost() you must not use that Canvas anymore.
I don't really see an advantage to using multiple threads with your current code. If the "background" Bitmap were also changing you could render that with a different thread, and merge in the current version when you draw, but I suspect that will be more trouble than it's worth -- you will have to deal with the synchronization issues, and I don't think it'll solve your non-smoothness.
currently I am trying to make an animation where some fish move around. I have successfully add one fish and made it animate using canvas and Bitmap. But currently I am trying to add a background that I made in Photoshop and whenever I add it in as a bitmap and draw it to the canvas no background shows up and the fish starts to lag across the screen. I was wondering if I needed to make a new View class and draw on a different canvas or if I could use the same one? Thank you for the help!
Here is the code in case you guys are interested:
public class Fish extends View {
Bitmap bitmap;
float x, y;
public Fish(Context context) {
super(context);
bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.fish1);
x = 0;
y = 0;
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawBitmap(bitmap, x, y, null);
if (x < canvas.getWidth())
{
x += 7;
}else{
x = 0;
}
invalidate();
}
}
You can draw as many bitmaps as you like. Each will overlay the prior. Thus, draw your background first, then draw your other images. Be sure that in your main images, you use transparent pixels where you want the background to show through.
In your code, don't call Invalidate() - that's what causes Android to call onDraw() and should only be called from somewhere else when some data has changed and needs to be redrawn.
You can do something like this, where theView is the view containing your animation:
In your activity, put this code in onCreate()
myAnimation();
Then
private void myAnimation()
{
int millis = 50; // milliseconds between displaying frames
theView.postDelayed (new Runnable ()
{
#Override public void run()
{
theView.invalidate();
myAnimation(); // you can add a conditional here to stop the animation
}
}, millis);
}
I'm trying to create an app that has a fairly complex UI.
I'm trying to understand what is the best practice to render irregular button shapes to the screen.
I'm adding this image as an example. Each one of these wooden boards is a button.
I obviously can't use Android's Button or ImageButton because the shape is not a rectangle.
I assume I need to draw this directly onto a canvas or use onDraw or Draw to make this happen.
Is this the correct way I should use to render these as buttons?
Any good reading material on these is HIGHLY appreciated..
Thank you
You can create a customized View working in following way:
in onDraw() paint 3 bitmaps each representing single button (where 2 other buttons are transparent),
in onTouch() check the touched pixel against those bitmaps to see which bitmap was clicked
Code snippet:
public class DrawingBoard extends View {
Bitmap mBitmap1 = BitmapFactory.decodeResource(getResources(), R.drawable.button1);
Bitmap mBitmap2 = BitmapFactory.decodeResource(getResources(), R.drawable.button2);
Bitmap mBitmap3 = BitmapFactory.decodeResource(getResources(), R.drawable.button3);
public DrawingBoard (Context context) {
// TODO Auto-generated constructor stub
super (context);
}
#Override
protected void onDraw (Canvas canvas) {
canvas.drawBitmap(mBitmap1, 0, 0, null);
canvas.drawBitmap(mBitmap2, 0, 0, null);
canvas.drawBitmap(mBitmap3, 0, 0, null);
}
#Override
public boolean onTouchEvent (MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN :
int xx = (int)event.getX();
int yy = (int)event.getY();
if(Color.alpha(mBitmap1.getPixel(xx,yy)) != 0) {
// button1 pressed
}
else if(Color.alpha(mBitmap2.getPixel(xx,yy)) != 0) {
// button2 pressed
}
else if(Color.alpha(mBitmap3.getPixel(xx,yy)) != 0) {
// button3 pressed
}
break;
}
return true;
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
// bitmaps are assumed to be of the same size
setMeasuredDimension(mBitmap1.getWidth(),mBitmap1.getHeight());
}
}
I didn't test the code, it it may have errors.
A variant - you can create a virtual bitmap storing 'hit codes' for pixels on whole image. You can create it from original picture but replace pixels with ids to detect which area was touched, all other pixels make 'empty' (0x0). So getPixel() will return id of a button.
I'm implementing drag'n'drop feature into my application.
I'm moving TextView object, and I'm using DragShadowBuilder to build shadow for this textview.
The question is, is there any way to change textView text while dragging item? I mean in
DragEvent.ACTION_DRAG_ENTERED or DragEvent.ACTION_DRAG_LOCATION case?
private static class MyDragShadowBuilder extends View.DragShadowBuilder {
// The drag shadow image, defined as a drawable thing
private static Drawable shadow;
// Defines the constructor for myDragShadowBuilder
public MyDragShadowBuilder(View v) {
// Stores the View parameter passed to myDragShadowBuilder.
super(v);
// Creates a draggable image that will fill the Canvas provided by the system.
shadow = new ColorDrawable(Color.LTGRAY);
}
// Defines a callback that sends the drag shadow dimensions and touch point back to the
// system.
#Override
public void onProvideShadowMetrics (Point size, Point touch)
// Defines local variables
private int width, height;
// Sets the width of the shadow to half the width of the original View
width = getView().getWidth() / 2;
// Sets the height of the shadow to half the height of the original View
height = getView().getHeight() / 2;
// The drag shadow is a ColorDrawable. This sets its dimensions to be the same as the
// Canvas that the system will provide. As a result, the drag shadow will fill the
// Canvas.
shadow.setBounds(0, 0, width, height);
// Sets the size parameter's width and height values. These get back to the system
// through the size parameter.
size.set(width, height);
// Sets the touch point's position to be in the middle of the drag shadow
touch.set(width / 2, height / 2);
}
// Defines a callback that draws the drag shadow in a Canvas that the system constructs
// from the dimensions passed in onProvideShadowMetrics().
#Override
public void onDrawShadow(Canvas canvas) {
// Draws the ColorDrawable in the Canvas passed in from the system.
shadow.draw(canvas);
}
}
Drag n drop android
For an app I need to develop, I need to be able to select one view from ViewGroup when user long press on the view have to popup an alert dialog, from that i will select options depending on the requirement, those are delete view and change background color for that particular view. I am creating different custom view's ( Custom view extends from View) and It has Touchlistener's. Dynamically attaching those view's to layout (RelativeLayout). Let us assume my layout having 3 views.
My questions:
I want to select one view from the list of available view's on the layout.
How to restrict the Custom view size instead of occupying full parent view.
I am facing Problem's like this: Look at this image if i set background color for custom view it look like this, Actually i was set for layout background color White here custom view occupies full screen. so, unable to see parent background color. Here problem with customview size, unable to restrict the customview size rather than occupying full parent view. even if i touch outside of my custom view, the touch listeners of custom view was triggering. i want to restrict those touch listeners upto my customview only..
.
Main Requirement:
Finally i want to be achieve like this below image, Now i am able to add more custom view's to layout,but unable to delete selected one. When i long press on any one of the view from the layout, it should popup one alert dialog with two buttons, if i select one selected view should be delete for another selection background color change.
RelaventCode: i am adding view to layout like this.
findViewById(R.id.line).setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
CustomView drawView = new CustomView(context);
frame.addView(drawView);
}
});
and this is the customview :
public class CustomView extends View {
public CustomView (Context context) {
super(context);
init();
}
private void init() {
Paint paint = new Paint();
Paint pai = new Paint();
Path path = new Path();
paint.setColor(Color.RED);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(5);
path.lineTo(50, 100);
}
#Override
public void onDraw(Canvas canvas) {
path.setFillType(Path.FillType.EVEN_ODD);
path.moveTo(Top_Point.x, Top_Point.y);
path.lineTo(Right_Point.x, Right_Point.y);
path.lineTo(Left_Point.x, Left_Point.y);
path.lineTo(Top_Point.x, Top_Point.y);
path.close();
canvas.drawPath(path, Inner_Paint);
canvas.drawLine(Top_Point.x, Top_Point.y, Right_Point.x, Right_Point.y,
Lines_Paint);
canvas.drawLine(Right_Point.x, Right_Point.y, Left_Point.x,
Left_Point.y, Lines_Paint);
canvas.drawLine(Left_Point.x, Left_Point.y, Top_Point.x, Top_Point.y,
Lines_Paint);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
PointF NewP = new PointF(0, 0);
NewP.x = event.getX();
NewP.y = event.getY();
if (TopTouchArea.contains(event.getX(), event.getY())) {
currentTouch = TOUCH_TOP;
} else if (RightTouchArea.contains(event.getX(), event.getY())) {
currentTouch = TOUCH_RIGHT;
} else if (LeftTouchArea.contains(event.getX(), event.getY())) {
currentTouch = TOUCH_LEFT;
} else if (a) {
Translate_Point.x = event.getX();
Translate_Point.y = event.getY();
currentTouch = Inner_Touch;
return true; // Return false if user touches none of the
// corners
} else {
return false;
}
return true;
case MotionEvent.ACTION_MOVE:
switch (currentTouch) {
case TOUCH_TOP:
/* ------ */
case TOUCH_RIGHT:
/* ----- */
invalidate();
return true;
case TOUCH_LEFT:
/* ------ */
invalidate();
return true;
case Inner_Touch:
/* ------ */
invalidate();
return true;
}
return false;
case MotionEvent.ACTION_UP:
switch (currentTouch) {
case TOUCH_TOP:
/* ----- */
return true;
case TOUCH_RIGHT:
/* ----- */
return true;
case TOUCH_LEFT:
/* ----- */
return true;
case Inner_Touch:
/* ----- */
invalidate();
return true;
}
return false;
}
return super.onTouchEvent(event);
}
You should override onSizeChanged or onMeasure methods to let your ViewGroup know your view's size. And make sure that your view's width and height are both WRAP_CONTENT.
About touch event - problem here is that your view size is matching parent size, so it intercepts all touch events (since it returns true in onTouchEvent).
And here is a great tutorial
Add an OnLongClickListener to the customview before adding it to the view group.
To help identify the view at a higher level, you may want to set an identifier on the view using setTag("id", id);
If I understand you correctly, the view you add is too big and fills it's parent.
Isn't it sufficient to do something like
TwoPointsDraw drawView = new TwoPointsDraw(context);
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT);
drawView.setLayoutParams(lp)
frame.addView(drawView);
Checkout this project, it might be helpful => Android multitouch controller, a simple multitouch pinch zoom library for Android.
Here is another example.