I've an ImageView, but how can I see where the user has clicked/touched on the image.
I know I have to use MotionEvent.getX() and MotionEvent.getY(), but how can I see where the user has clicked on the image?
Thanks !
You have to subclass your image view and explicitly draw an indicator at the position where the user is touching.
public class TouchableImageView extends ImageView {
// Constructors should come here
// Override onTouch to remember the touch position in our variable touchLocation
// Set touchLocation to null when getting the ACTION_UP event
// Override onDraw to draw something at touchLocation. You should create a proper Paint object
// in your constructors and use it here
public void onDraw(Canvas c) {
if (touchLocation!=null) {
canvas.drawCircle(touchLocation.x, touchLocation.y, 10, indicatorPaint);
}
}
private Point touchLocation;
private Paint indicatorPaint;
}
Related
I'm working in an app that uses the ml kit text recognition library; The app reads text from images and puts a Rect around every word.
Now I want these Rects to change color when the user tap on one of them or swipe above some words.
So, I was able to handle touch events correctly, but what I can't do is changing the color of the touched Rect!
Should I draw new colored Rects above the touched Rect? Or can I color the existing rects (which I don't think I can)?
Classes:
TextGraphic, GraphicOverlay.
//This is where the Rects get drawn
I also tried this solution, so I typed this methods in the TextGraphic class:
public void changeColor(boolean isHighlighted) {
if(isHighlighted) {
rectPaint.setColor(COLOR_HIGHLIGHTED);
rectPaint.setAlpha(400);//set opacity of rect
}else{
rectPaint.setColor(COLOR_DEFAULT);
rectPaint.setAlpha(400);//set opacity of rect
}
postInvalidate();
}
and called it when the user touches the text, but the problem is that all Rects colors get changed, and they do not change at runtime!
A snippet from my ActivityClass, where I used some callback methods to pass things out.
ArrayList<Rect> rects = new ArrayList<>();
#Override
public void onAdd(FirebaseVisionText.Element element, Rect elementRect, String wordText) {
GraphicOverlay.Graphic textGraphic = new TextGraphic(mGraphicOverlay, element);
mTextGraphic = new TextGraphic(mGraphicOverlay, element);
mGraphicOverlay.add(textGraphic);
rects.add(elementRect);
}
A snippet from my ActivityClass where I handle touch events:
#Override
public boolean onDown(MotionEvent event) {
helper.dismissKeyboard();
touchX = Math.round(event.getX());
touchY = Math.round(event.getY());
for(int x=0; x< rects.size();x++) {
if (rects.get(x).contains(touchX, touchY)) {
// line has been clicked
mTextGraphic.changeColor(true);
return true;
}
}
return true;
}
You are changing the color using the mTextGraphic variable. If you look closely in your onAdd() method you will see that you are assigning a new object to mTextGraphic that has nothing to do with the objects drawn to screen because only the objects that you add to GraphicOverlay list using the mGraphicOverlay.add() will get drawn to screen.
So what you need is to call changeColor() not on mTextGraphic but on the respective object that is already in the list inside GraphicOverlay
Since the list inside GraphicOverlay is private you can't manipulate it in the onDown() method. You will need to write a public method that will do the job for you.
Write the following method in GraphicOverlay class
public TextGraphic getGraphicElementAtIndex(int index) {
return (TextGraphic)graphics.get(index)
}
Now use this method inside the if condition of onDown() method like this
if (rects.get(x).contains(touchX, touchY)) {
// line has been clicked
Log.d("PreviewActivity", "changeColor() is called");
mGraphicOverlay.getGraphicElementAtIndex(x).changeColor();
touchedText = texts.get(x);
return true;
}
Hope this helps.
SIDE NOTE: Now even after this if for some reason the ordering of objects inside rects list and graphics list (which is inside GraphicOverlay) change then you will see that when you click a rectangle some other rectangle changes color.
Maybe you should not do it by coding but by ColorStateList
Android Developer: colorstatelist
I have created a custom view class to use in a project I'm working on. To put is simply, I'm displaying an image, then adding images on top of the original image (currently by clicking on the image, but that's not final).
I have these 2 methods in my custom view:
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
for (Drawable d : drawableList)
{
d.draw(canvas);
}
}
public void AddPoint(float x, float y)
{
Drawable tempDrawable = pin;
tempDrawable.setBounds((int)x, (int)y, (int)x + 50, (int)y + 50);
drawableList.add(tempDrawable);
invalidate();
}
The AddPoint() method is called in an OnTouchListener and is passes the coordinates of the touch event.
The way it currently works is it displays the main image, but will only display the most recent images where I clicked, previous ones just disappear.
Does anybody know what I am doing wrong here?
I've figured out what I was doing wrong.
The line of code:
Drawable tempDrawable = pin;
I changed to:
Drawable tempDrawable = mainRes.getDrawable(R.drawable.pin);
I got mainRes from the init(Context context) method I wrote in the custom View class.
I want to divide the image into sub images and when I click on a part of the image will give me the name of the region, for example, this is my question how to recognize a region from the image, or how to divide the image into sub-images and use it in imageViews
And thank you in advance
In my opinion #fractalwrench's idea is quite good for your case. Basic steps are listed below.
Subclass Android ImageView. For example, MultiRegionImageView.
Override its onTouchEvent method. (This method gets called whenever user touches the view)
User touches the image and thereby onTouchEvent is called and provides the exact touch point (x, y).
Declare another method or interface which determines at which region a given point is. For example, getRegionByPoint(int x, int y)
If you would like to highlight that region boundaries, you could use paths. First off, you should define paths and save them into a raw file (XML, for example), then using region ID, fetch its path and finally draw that path over the main image.
For drawing a path over the main image, you should also override onDraw method of ImageView class and use canvas.drawPath();
public class MultiRegionImageView extends ImageView {
RegionProvider mRegionProvider;
int mId = -1;
private Paint mPaint;
public MultiRegionImageView(Context context) {
super(context);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
mId = mRegionProvider.getRegionIdByPoint(event.getX(), event.getY());
return super.onTouchEvent(event);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if(mId != -1) {
canvas.drawPath(mRegionProvider.getRegionBoundaryPath(mId), mPaint);
}
}
public interface RegionProvider{
int getRegionIdByPoint(float x, float y);
Path getRegionBoundaryPath(int id);
}
}
You should only need one ImageView to display the map. If you override onTouchEvent(MotionEvent e), you can get the position which is being touched in the View. If you store the position and shape of each region in some sort of List, you can check whether a touch event is within a region (and display whatever text you need to).
I am kinda new to android and I am creating a small app where I can draw rectangle and circle on custom view.
What I am trying to do is when Circle button is pressed I want to draw circle and when Rectangle button is pressed I want to draw a rectangle.
But the problem, I am having is when I press circle button it draws the circle correctly, but when I click on the rectangle button and tries to draw it, it hides the previous drawn circle and draws a rectangle. If again I select the circle, and draw it, it will hide the rectangle and draws a circle.
MainActivity.java: when button is pressed, buttonPressed method is called:
private DrawingView drawView;
public void buttonPressed(View v)
{
String shape = v.getTag().toString();
if(shape.equals("circle"))
{
Log.e("button pressed", "circle");
drawView.setValue("circle");
}
else if(shape.equals("rect"))
{
Log.e("button pressed", "rect");
drawView.setValue("rect");
}
}
and in my DrawingView class's onDraw method I am doing:
#Override
protected void onDraw(Canvas canvas)
{
if(testValue.equals("rect"))
{
// draw rectangle
}
else if(testValue.equals("circle"))
{
// draw circle
}
}
// setter method to set value
public void setValue(String val)
{
testValue = val;
}
Any suggestions on how to handle this situation?
Android Views will automatically clear their canvas before onDraw() is called, so you'll have to do one of two things:
Keep a list of items to draw, add to the list when a button is pressed, and iterate over it in onDraw(). This is simple to implement, but might slow down the draw process if your list gets long. This happens on the UI thread, so be careful.
Make your own Canvas from a private Bitmap matching the size of your View, and draw on it when a button is pressed. In onDraw(), use drawBitmap() to copy your buffer onto the View.
For my activity i use 3 custom views stacked.
the lower one is a SurfaceView fullscreen, the middle one is derived from GridView and overrides onDraw to add custom graphic.
The top one is derived directly from View, sits in a corner of the screen and act as a knob to control the others two views (this is the problematic view).
to add a custom animation to this view, i used this pattern:
public class StubKnobView extends View{
private Drawable knob;
private Point knobPos;
private float rotation;
private boolean needsToMove;
public void moveKnob(){
/* ...various calculations... */
this.needsToMove=true;
invalidate(); //to notify the intention to start animation
}
private void updateAnimation(){
/* ........... */
this.needsToMove= !animationComplete;
invalidate();
}
#Override
protected void onDraw(Canvas canvas) {
int saveCount=canvas.getSaveCount();
canvas.save();
canvas.rotate(this.rotation, this.knobPos.x, this.knobPos.y );
this.knob.draw(canvas);
canvas.restoreToCount(saveCount);
if(this.needsToMove){
updateAnimation();
}
}
}
ideally, if there is an animation pending, after each drawing cycle the view should auto invalidate.
right now this doesn't work, to force the animation i have to touch the screen to cause a onDraw cycle.
Using "show screen updates" of Dev tools I see that no screen invalidate/update cycle happen , apart when i click the screen.
specifying the dirty rect also ha no effect.
So, any idea where to look to know why this invalidate/draw cycle does not work the way is intended?
I encontered this situation, and also doubted that invalidate() doesn't make onDraw() called again.
After simplified the business codes, it's turn out that there is a dead loop - exactly too much iterations, which blocks the UI thread.
So, my advice is that make sure the UI thread going smoothly first.
Try something like this with a private class in your View. The idea is not to call invalidate() from within onDraw(). Instead, post it on the running queue. Instantiate the private class in your View constructor and then just call animateKnob.start() when you want the animation. The onDraw() can then just focus on drawing.
public void moveKnob(){
// update the state to draw... KnobPos, rotation
invalidate()
}
private class AnimateKnob{
public void start(){
// Do some calculations if necessary
// Start the animation
MyView.this.post( new Runnable() {
stepAnimation()
});
}
public void stepAnimation(){
// update the animation, maybe use an interpolator.
// Here you could also determine if the animation is complete or not
MyView.this.moveKnob();
if( !animationComplete ){
// Call it again
MyView.this.post( new Runnable() {
stepAnimation()
});
}
}
}