On my app I have an ImageView which I turned into a Bitmap for editing. I need to detect which pixels on the ImageView were touched by the user. In addition, if the user draws a line with his finger, I need to know all the pixels that were touched in order to change them. How do I detect which pixels were touched?
Ok Jonah, here are some directions for you.
I guess you want that blending effect to react quickly to user input so first thing you'd better go for a custom SurfaceView instead of a ImageView because it is more suitable for drawing high frame rate animations required in 2D action games and animations. I strongly recommend you to read this guide; giving special attention to the part about the use of SurfaceView, before going any further. You will basically need to create a class that extends SurfaceView and implements SurfaceHolder.Callback. This view will then be responsible to listen for user touch events and to render the frames to animate the blending effect.
Take a look at following code as a reference:
public class MainView extends SurfaceView implements SurfaceHolder.Callback {
public MainView(Context context, AttributeSet attrs) {
super(context, attrs);
SurfaceHolder holder = getHolder();
holder.addCallback(this); // Register this view as a surface change listener
setFocusable(true); // make sure we get key events
}
#Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
// Check if the touch pointer is the one you want
if (event.getPointerId(event.getActionIndex()) == 0) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// User touched screen...
case MotionEvent.ACTION_CANCEL:
// User dragged his finger out of the view bounds...
case MotionEvent.ACTION_UP:
// User raised his finger...
case MotionEvent.ACTION_MOVE:
// User dragged his finger...
// Update the blending effect bitmap here and trigger a frame redraw,
// if you don't already have an animation thread to do it for you.
return true;
}
return false;
}
/*
* Callback invoked when the Surface has been created and is ready to be
* used.
*/
public void surfaceCreated(SurfaceHolder holder) {
// You need to wait for this call back before attempting to draw
}
/*
* Callback invoked when the Surface has been destroyed and must no longer
* be touched. WARNING: after this method returns, the Surface/Canvas must
* never be touched again!
*/
public void surfaceDestroyed(SurfaceHolder holder) {
// You shouldn't draw to this surface after this method has been called
}
}
Then use it on the layout of your "drawing" activity like this:
<com.xxx.yyy.MainView
android:id="#+id/main_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
To draw to this surface you need the following code:
Canvas c = null;
try {
c = mSurfaceHolder.lockCanvas(null);
synchronized (mSurfaceHolder) {
if (c != null)
c.drawBitmap(blendingImage, 0, 0, null); // Render blending effect
}
} catch (Exception e) {
Log.e("SurfaceView", "Error drawing frame", e);
} 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) {
mSurfaceHolder.unlockCanvasAndPost(c);
}
}
A fully functional example would be impractical to put in an answer so I recommend you to download the Lunar Lander sample game from Google for a full working example. Note however, that you won't need a game animation thread (although it won't hurt having one), like the one coded in the Lunar Lander sample, if all you need is the blending effect. The purpose of that thread is to create a game loop in which game frames are constantly generated to animate objects that may or may not depend on user input. In your case, all you need is to trigger a frame redraw after processing each touch event.
EDIT: The following code are fixes to get the code you've provided in the comments, working.
Here are the changes to MainActivity:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// put pics from drawables to Bitmaps
Resources res = getResources();
BitmapDrawable bd1 = (BitmapDrawable) res.getDrawable(R.drawable.pic1);
// FIX: This block makes `operation` a mutable bitmap version of the loaded resource
// This is required because immutable bitmaps can't be changed
Bitmap tmp = bd1.getBitmap();
operation = Bitmap.createBitmap(tmp.getWidth(), tmp.getHeight(), Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(operation);
Paint paint = new Paint();
c.drawBitmap(tmp, 0f, 0f, paint);
BitmapDrawable bd2 = (BitmapDrawable) res.getDrawable(R.drawable.pic2);
bmp = bd2.getBitmap();
myView = new MainView(this, operation, bmp);
FrameLayout preview = (FrameLayout) findViewById(R.id.preview);
preview.addView(myView);
}
...
Here are the changes to the MainView class:
public class MainView extends SurfaceView implements Callback {
private SurfaceHolder holder;
private Bitmap operation;
private Bitmap bmp2;
private boolean surfaceReady;
// took out AttributeSet attrs
public MainView(Context context, Bitmap operation, Bitmap bmp2) {
super(context);
this.operation = operation;
this.bmp2 = bmp2;
holder = getHolder(); // Fix: proper reference the instance variable
holder.addCallback(this); // Register this view as a surface change
// listener
setFocusable(true); // make sure we get key events
}
// Added so the blending operation is made in one place so it can be more easily upgraded
private void blend(int x, int y) {
if (x >= 0 && y >= 0 && x < bmp2.getWidth() && x < operation.getWidth() && y < bmp2.getHeight() && y < operation.getHeight())
operation.setPixel(x, y, bmp2.getPixel(x, y));
}
// Added so the drawing is now made in one place
private void drawOverlays() {
Canvas c = null;
try {
c = holder.lockCanvas(null);
synchronized (holder) {
if (c != null)
c.drawBitmap(operation, 0, 0, null); // Render blending
// effect
}
} catch (Exception e) {
Log.e("SurfaceView", "Error drawing frame", e);
} 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) {
holder.unlockCanvasAndPost(c);
}
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
if (!surfaceReady) // No attempt to blend or draw while surface isn't ready
return false;
// Check if the touch pointer is the one you want
if (event.getPointerId(event.getActionIndex()) == 0) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// User touched screen. Falls through ACTION_MOVE once there is no break
case MotionEvent.ACTION_MOVE:
// User dragged his finger...
blend((int) event.getX(), (int) event.getY());
}
// Update the blending effect bitmap here and trigger a frame
// redraw,
// if you don't already have an animation thread to do it for you.
drawOverlays();
return true;
}
return false;
}
/*
* Callback invoked when the Surface has been created and is ready to be
* used.
*/
public void surfaceCreated(SurfaceHolder holder) {
surfaceReady = true;
drawOverlays();
}
/*
* Callback invoked when the Surface has been destroyed and must no longer
* be touched. WARNING: after this method returns, the Surface/Canvas must
* never be touched again!
*/
public void surfaceDestroyed(SurfaceHolder holder) {
// You shouldn't draw to this surface after this method has been called
surfaceReady = false;
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
// TODO Auto-generated method stub
}
}
This code works for me. I just hope I didn't forget anything =:)
Let me know if you still have trouble, ok?
So the answer to this is that you're going to have to be a little clever, but it really shouldn't be so bad. Instead of posting all the code to do what you want to do, I'll give you a link here and an explanation.
So by managing the touch events of an application, you can figure out the average coordinates of a touch event. Using that information you can determine the center of all the pixels touched and continue to track that as a line is drawn with the finger. To track a straight line use the
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_UP:
clauses to determine the start and end of the line. If you want to track a line that is not straight and drawn, you're going to need a little more tracking using
case MotionEvent.ACTION_MOVE:
and that should get you fairly started. You may need a little bit of logic to deal with the fact that you will be drawing a very thin line and I suspect that's not quite what you're going for. Maybe it is though. Either way this should be a good place to get started.
EDIT
In regards to your first comment, here is a link you can use for an example. I have to make a small disclaimer though. To get all of the pieces to work together correctly, it may not seem that simple at first. I assure you that this is one of the simplest examples and breaks the tutorial into sections.
For what you would like to do, I think you'll want to pay particular attention to section 2 (no to be confused with step 2):
2. Facilitate Drawing
I suggest this because it shows different ways to use information form the TouchEvent. The things included in section 1 will explain a little bit about the environment to setup displaying a TouchEvent's captured data whereas section 3 is mostly about aesthetics. This may not directly answer your question, but I suspect it will get you where you need to be.
Happy coding! Leave a comment if you have any questions.
Related
I am writing an application in Java for android that draws filled circle shapes on canvas.
I have written a draw circles class to handle events and the actual drawing of the circles:
public class drawCircles extends android.support.v7.widget.AppCompatImageView {
float x, y;
Paint paint = new Paint();
PointF pointf = new PointF();
ArrayList<PointF> locations = new ArrayList<PointF>();
public drawCircles(Context context) {
super(context);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
x = event.getX();
y = event.getY();
pointf = new PointF(x, y);
locations.add(pointf);
invalidate();
return true;
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Random rand = new Random();
int n = rand.nextInt(4) + 1;
switch(n)
{
case 1:
paint.setColor(GREEN);
break;
case 2:
paint.setColor(RED);
break;
case 3:
paint.setColor(YELLOW);
break;
case 4:
paint.setColor(BLUE);
break;
}
int i=0;
while(i < locations.size())
{
canvas.drawCircle(locations.get(i).x,locations.get(i).y,50, paint);
i = i + 1;
}
}
}
public class MainActivity extends AppCompatActivity{
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
drawCircles draw = new drawCircles(this);
setContentView(draw);
}
}
My application works just fine as long as the setContentView method in the onCreate() method for the main activity is set to an object of the drawCircles class
My question, is when you point setContentView at an instance of a class like this, is it possible to customise the layout? I want to add widgets for paint brush size etc.
My first thought was to set the content view to a customised xml file then call methods from the drawCircle class where appropriate however if I am understanding the reading I have done correctly, it is not possible to call the onDraw() method from a class that doesn't extend a view, so I can't call them from my main class which extends an activity class for backwards compatibility.
It has occured to me that it may be possible to assign the drawCircles class a particular layout.xml file but I haven't been able to find any cases where this has been done.
Any advise is greatly appreciated
You can do whatever you want to a view either way. The two functions run the same code- the one that takes a layout identifier just inflates the layout into a View, then calls setContentView on the result.
Its also quite possible to put a custom view in xml, in which case its onDraw will be called. I think you don't quite understand how xml inflation works, which makes it difficult to figure out how to help you.
Also, you would never call onDraw directly. Its called by the framework when a view has an invalid area.
Yes you can, let see some custom libraries like custom dialog, calendar,... they define many case for properties.
In xml, just choose property value then the custom view class will do every things.
The first thing you need is learn more about custom view, collect attribute, set conditions...
Let see some samples:
http://www.vogella.com/tutorials/AndroidCustomViews/article.html
http://trickyandroid.com/protip-inflating-layout-for-your-custom-view/
I'm developing simple drawing application and I was wondering if it is possible to add visible pointer when touching canvas? What I mean is something similar to mouse indicator on PC. The reason for that is - when using eraser it is not visible where exactly your touch is registered and with the pointer, it would help (especially when using finger touch, because the pen is quite precise). I've tried with other similar topics but none of them answered the question.
taken from this pos :
How to programmatically enable "show touches" option in Android?
• enable show touches:
Settings.System.putInt(context.getContentResolver(),
"show_touches", 1);
• To disable show touches:
Settings.System.putInt(context.getContentResolver(),
"show_touches", 0);
Remember to add android.permission.WRITE_SETTINGS to your Manifest.
EDIT:
here is a DrawView.java that implements View.OnTouchListener and defines multiple draw modes. however it lacks the indicators.
in main activity inside setListener for the DrawView you override onStartDrawing method: here you can decide whether or not to show an indicator and how to style it depending on DrawView's current mode ( pen ,eraser etc ) and on the other interface methodes move the indicator if its visible and hide it when drawing ends. check the code:
mDrawView.setOnDrawViewListener(new DrawView.OnDrawViewListener() {
#Override public void onStartDrawing() {
canUndoRedo();
// decide whether or not to show an
// indicator and style it depending on
// mDrawView's attributes and current drawing mode
setIndicator(mDrawView.getDrawingMode(),mDrawView.getDrawWidth());
}
#Override
public void onMove(MotionEvent motionEvent) {
if(isIndicatorVisible){
indicatorView.animate()
.x(motionEvent.getX())
.y(motionEvent.getY())
.setDuration(0)
.start();
}
}
#Override public void onEndDrawing() {
canUndoRedo();
//hide indicator here
if (isIndicatorVisible){
indicatorView.setVisibility(View.GONE);
isIndicatorVisible = false;
}
}
here is the edits that i add to DrawView.java :
defined new interface method void onMove(MotionEvent motionEvent)
public interface OnDrawViewListener {
void onStartDrawing();
void onEndDrawing();
void onClearDrawing();
void onRequestText();
void onMove(MotionEvent motionEvent); // added this method
}
called the new method inside onToch:
case MotionEvent.ACTION_MOVE:
onDrawViewListener.onMove(motionEvent); // added this line
mDrawMoveHistory.get(mDrawMoveHistory.size() - 1).setEndX(motionEvent.getX()).setEndY(motionEvent.getY());
if (mDrawingTool == DrawingTool.PEN || mDrawingMode == DrawingMode.ERASER) {
mDrawMoveHistory.get(mDrawMoveHistory.size() - 1).getDrawingPathList()
.get(mDrawMoveHistory.get(mDrawMoveHistory.size() - 1).getDrawingPathList().size() - 1)
.lineTo(motionEvent.getX(), motionEvent.getY());
}
invalidate();
break;
here's setIndicator method :
private void setIndicator(DrawingMode drawingMode, int drawWidth) {
if (drawingMode == DrawingMode.ERASER){
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(drawWidth, drawWidth);
indicatorView.setLayoutParams(params);
GradientDrawable border = new GradientDrawable();
border.setStroke(1, 0xFF000000); //black border with full opacity
if(Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
indicatorView.setBackgroundDrawable(border);
} else {
indicatorView.setBackground(border);
}
indicatorView.setVisibility(View.VISIBLE);
isIndicatorVisible = true;
}
}
I have an idea for implementing of a login with password as the image touch positions when the member touch the same position I have to login the user.Can anyone help me how to implement it I can get the position of screen only. and the x and y coordinated are changing even when I touch the same position.
I would say create a custom View that extends ImageView:
public class PasswordView extends ImageView {
public PasswordView(Context context) {
super(context);
// Any additional PasswordView setup
}
#Override
public boolean onTouchEvent(MotionEvent event) {
float xTouchPos = event.getX();
float yTouchPos = event.getY();
// If the PasswordView was clicked
if (event.getAction() == MotionEvent.ACTION_DOWN) {
// Check if they touched in the secret spot
}
return super.onTouchEvent(event);
}
}
By subclassing ImageView, you will still have access to all the standard features of an ImageView (setting an image source, etc.) and you will also be able to add the additional functionality for checking a user's touch position.
Here's an example project that uses onTouchEvent.
I am creating an image by using Canvas- and Bitmap class. I want to set it as a background for the user. Then I want to add some more images on top of it.
this is the code for image that is supposed to be as background.
ImageView imgMap1 = (ImageView) findViewById(R.id.imgMap1);
imgMap1.setImageDrawable(new BitmapDrawable(Bitmap.createBitmap(bmp, 0, 0, 500, 500)));
and this is the code to make it as background:
LinearLayout ll = new LinearLayout(this);
ll.setBackgroundResource(R.drawable.nn);
this.setContentView(ll);
The Problem here is: When I set it as background, I can't see the other photo anymore.
How can I do this?
Thanks in advance.
The other Images are added in the Layout. they are movable by finger touch. user can reposition them by finger.
ImageView i1 = (ImageView) findViewById(R.id.Image1);
ImageView i2 = (ImageView) findViewById(R.id.Image2);
The layout is built from the top down in your XML file, or in the order you add elements in code. It sounds like your other images is being added to the layout first, either as a higher-up element in the XML file, or earlier in your code. You'll need to add the other code as context for a complete answer.
Just noticed that you can directly set Bitmap as your ImageView's content using setImageBitmap(Bitmap bm)See the Android Reference
Then let talk about your question.
First, create your own class extends the View;
Second, load background image and overlay image using Bitmap
Third, invoke onTouch event, so that the onDraw method will automatically redraw the overlay image using the coordinates returned by onTouch
Something like:
public class dragndrop extends Activity {
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
// using this to load your background image
BitmapFactory.Options opt = new BitmapFactory.Options();
opt.inPreferredConfig = Bitmap.Config.RGB_565; // this is not a must
bmBackground = BitmapFactory.decodeFile(filePath, opt);
super.onCreate(savedInstanceState);
DrawView dv = new DrawView(dragndrop.this);
setContentView(dv);
}
public class DrawView extends View {
Point coordinate;
public DrawView(Context context) {
super(context);
setFocusable(true); //necessary for getting the touch events
}
#Override
protected void onDraw(Canvas canvas) {
// assume you have already load your background image as bitmap
canvas.drawBitmap(bmBackground, 0, 0, null);
// assume bm is the overlay image you need to put on top,
// the method here will draw the object with the coordinate you give
canvas.drawBitmap(bm, coordinate.x, coordinate.y, null);
}
// events when touching the screen
public boolean onTouchEvent(MotionEvent event) {
int eventaction = event.getAction();
int x = (int)event.getX();
int y = (int)event.getY();
switch (eventaction ) {
case MotionEvent.ACTION_DOWN:
// add code here to determine the point user touched is within the object or not
break;
}
}
break;
case MotionEvent.ACTION_MOVE: // touch 'n' drag
// pass the touch point to your object
coordinate.x = x;
coordinate.y = y;
break;
case MotionEvent.ACTION_UP:
// touch drop - just do things here after dropping
break;
}
// redraw the canvas
invalidate();
return true;
}
}
}
This is my own snippet, please edit before use, and please let me know when your question is resolved.
Presumably it must be quite common to desire a screen in which
there are graphical elements (for which you may like to use a canvas) and widgets/buttons on the same screen. But everything I've looked at so far gives examples of either screens full of widgets OR whole screen canvases. Can someone point me to some example code for using both at the same time.
...or is this not the done thing?
EDIT: Following on from Steve's suggestion my code now looks like this:
public class CanLay extends Activity
{
Bitmap bm;
#Override
protected void onCreate(Bundle savedInstanceState)
{
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.canlay);
InputStream is = getResources().openRawResource(R.drawable.ella);
bm = BitmapFactory.decodeStream(is);
SurfaceView sv;
SurfaceHolder sh;
Canvas can = null;
sv = (SurfaceView)findViewById(R.id.surview);
sh = sv.getHolder();
try
{
can = sh.lockCanvas(null);
synchronized(sh)
{
can.drawBitmap(bm, 0, 0, null);
}
}
finally
{
if (can != null)
{
sh.unlockCanvasAndPost(can);
}
}
}
}
The only problem now is that sh.lockCanvas(null); always returns null.
I don't know of any sample code, but you usually get a Canvas from a SurfaceView, which is a View like other widgets (eg, TextView and Button). I would try just laying out the SurfaceView along with the other elements of the layout. The your basic XML structure might look something like
<LinearLayout >
<TextView />
<Button />
<SurfaceView />
</LinearLayout>
EDIT:
To get the Canvas from SurfaceView, first get the SurfaceHolder, then lock the canvas, draw your stuff, and unlock the canvas to have it displayed. In code that normally looks like:
SurfaceHolder holder = surfaceView.getHolder();
Canvas c = null;
try {
c = holder.lockCanvas(null);
synchronized(holder) {
// draw here
// c.drawBitmap() or whatever
}
} finally {
if(c != null)
holder.unlockCanvasAndPost(c);
}
DOUBLE EDIT:
According to the docs, lockCanvas returns null when the surface isn't ready. When you're still in onCreate(), the surface is definitely not ready. The way you know a surface is ready is by handling a callback to SurfaceHolder.Callback.surfaceCreated(). (Games often use surfaceCreated() to know when to start running their non-event thread.)
I know this may sound like more and more stuff you have to do, but it's really not that bad. You can even do it inline with something like this:
void onCreate(Bundle savedInstanceState) {
// inflate the XML with setContentView(), create your Bitmap, etc
sv = (SurfaceView)findViewById(R.id.surview);
sv.addCallback(new SurfaceHolder.Callback() {
#Override
void surfaceCreated(SurfaceHolder holder) {
Canvas can;
try {
can = holder.lockCanvas(null);
synchronized(holder) {
can.drawBitmap(bm, 0, 0, null);
}
} finally {
if(can != null) {
holder.unlockCanvasAndPost(can);
}
}});
// the rest of onCreate()
}
I may have messed up some of the braces, but you get the idea. Overall, it might be easier to put your SurfaceHolder.Callback implementation in its own non-anoymous class since there are restrictions on being anonymous, but that's the way you know your SurfaceView is ready for business. And of course, it's good to implement SurfaceHolder.Callback.surfaceDestroyed() so that you know when SurfaceView is going out of business. (Games often use surfaceDestroyed() to know when to stop running their non-event thread!)