7

Given any shape (either filled circle, star, triangle, bitmap with transparent areas, etc.) I would like to know if it's possible (using the latest Android API) to know if the user has clicked on the view, or outside of it.

For example, if I have a circular button, I would like to know if the user has clicked inside the circle, but not outside of it.

Is it possible?

If not, maybe I could poll the pixel of the touch event, and if it's transparent, ignore it, and if it isn't, handle it as a click event?

John Watts
  • 8,717
  • 1
  • 31
  • 35
android developer
  • 114,585
  • 152
  • 739
  • 1,270

4 Answers4

8
ImageView image=(ImageView) findViewById(R.id.imageView1);
image.setOnTouchListener(this);
Bitmap bitmap = ((BitmapDrawable)image.getDrawable()).getBitmap();    

@Override
public boolean onTouch(View v, MotionEvent event) {
    // TODO Auto-generated method stub
    int pixel = bitmap.getPixel((int)event.getX(), (int)event.getY());
    int alphaValue=Color.alpha(pixel);
    return true;
}

This way you can get the alpha value of the pixel touched. Now you can easily check whether the pixel touched is transparent or not.

Adnan Zahid
  • 573
  • 1
  • 10
  • 38
  • this actually seems promising! i will need to check it out before i tick your answer, though . there might be a problem: what would happen on different device screens (different densities/resolutions) ? will it still work? also , does this method use more memory this way for the bitmap? – android developer Jul 31 '12 at 13:02
  • also , will it work even if i don't use a bitmap to be shown in the view? – android developer Jul 31 '12 at 13:07
  • If you're designing for different screens, you must be using fragments nevertheless. And as for the bitmap, i think using a plain button image wont use much memory since it can easily be scaled without losing much detail. Lastly, yes you can use the drawbitmap method but that would require a canvas so I'd recommend using an imageview instead. – Adnan Zahid Jul 31 '12 at 19:48
  • what does fragments have to do with anything here? i asked about the screen density because (maybe,that's just a guess) the bitmap that you receive might be different than the original one , so you get 2 bitmaps stored in memory - one for viewing and one for polling . about the polling , would this solution also work for 9-patch images too? – android developer Jul 31 '12 at 21:14
  • Great answer @Adnan Zahid. It works like a charm. I've tested it on emulators with different densities/resolutions. Thanks a lot bro. – JiTHiN Aug 01 '12 at 03:41
  • it doesn't seem to work, as it doesn't sample the bitmap correctly. it depends on the density, i think. maybe it will work only if you use wrap_content. – android developer Aug 28 '13 at 21:01
  • i've posted a code that really work, and it doesn't even matter if the view is an imageView. – android developer Aug 29 '13 at 05:22
  • @AdnanZahid : It works..thanks.But what if I want to alpha value of three different shapes and handle it onTouch event.Is it possible to differentiate three different buttons? You can see screenshot of image which i ma talking about here : http://stackoverflow.com/questions/18433097/android-layout-solution-for-place-button-on-top-of-the-irregular-shaped-image – Ponting Aug 29 '13 at 05:35
  • @Ponting how about a mask of which buttons are there ? for each button set a different color. the mask doesn't even have to be on a view, but just loaded as a 2d array. – android developer Sep 29 '13 at 14:02
2

I also wanted to do something like that and i ended up tweaking with the FrameLayout. FrameLayout allows you to add multiple views on top of each other.

Add a FrameLayout. Inside that you can add a 'View' and set its height and width to match_parent. On top of the View, add the buttons you want to.

Then in your code, get the reference of the View and set onClickListener on it so that whenever user touches that View, you can handle that event. Set click listeners on other buttons as well.

Now just handle your touch events. You will now know if the user has clicked on the button or outside it (user has clicked on the View).

If you want to create transparent or translucent buttons, check https://stackoverflow.com/a/11689915/1117338. I hope this helps.

Community
  • 1
  • 1
Vikram Gupta
  • 6,496
  • 5
  • 34
  • 47
1
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    FrameLayout root = (FrameLayout)findViewById(R.id.root);
    root.setOnTouchListener(new OnTouchListener() {         
        public boolean onTouch(View v, MotionEvent event) {
            if (v.getId() == ID){
                // Your code
            }
            return true;
        }
    });
}
Nikola Ninkovic
  • 1,252
  • 1
  • 12
  • 27
1

ok, i've found a working solution for any type of view.

some notes:

  • sadly it uses a bitmap of the size of the view, but only for a tiny amount of time.

    after that, it holds where it's considered on the visible area and where it's considered outside of the visible area.

  • i could make it more memory friendly by making an array of integers, which have flags. currently it's a simple boolean array.

  • i could check the bitmap's alpha values in JNI instead, and avoid having the (short) time where i have both the bitmap and the array together.

  • if anyone could help making it better, it could be really great.

here's the code:

public class MainActivity extends Activity
  {
  boolean[] _inVisibleAreaMap;
  private int _width,_height;

  @Override
  protected void onCreate(final Bundle savedInstanceState)
    {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    final View view=findViewById(R.id.imageView1);
    view.setDrawingCacheEnabled(true);
    runJustBeforeBeingDrawn(view,new Runnable()
      {
        @Override
        public void run()
          {
          final Bitmap bitmap=view.getDrawingCache();
          _width=bitmap.getWidth();
          _height=bitmap.getHeight();
          _inVisibleAreaMap=new boolean[_width*_height];
          for(int y=0;y<_width;++y)
            for(int x=0;x<_height;++x)
              _inVisibleAreaMap[y*_width+x]=Color.alpha(bitmap.getPixel(x,y))!=0;
          view.setDrawingCacheEnabled(false);
          bitmap.recycle();
          }
      });
    view.setOnTouchListener(new OnTouchListener()
      {
        @Override
        public boolean onTouch(final View v,final MotionEvent event)
          {
          final int x=(int)event.getX(),y=(int)event.getY();
          boolean isIn=x>=0&&y>=0&&x<_width&&y<_height;
          // if inside bounding box , check if in the visibile area
          if(isIn)
            isIn=_inVisibleAreaMap[y*_width+x];
          if(isIn)
            Log.d("DEBUG","in");
          else Log.d("DEBUG","out");
          return true;
          }
      });
    }

  private static void runJustBeforeBeingDrawn(final View view,final Runnable runnable)
    {
    final ViewTreeObserver vto=view.getViewTreeObserver();
    final OnPreDrawListener preDrawListener=new OnPreDrawListener()
      {
        @Override
        public boolean onPreDraw()
          {
          runnable.run();
          final ViewTreeObserver vto=view.getViewTreeObserver();
          vto.removeOnPreDrawListener(this);
          return true;
          }
      };
    vto.addOnPreDrawListener(preDrawListener);
    }
  }

in case the imageView has set its width&height to wrap_content, and it really got the size it needs, only then you can use Adnan Zahid solution, which could be written this way:

public class MainActivity extends Activity
  {
  private int _width,_height;

  @Override
  protected void onCreate(final Bundle savedInstanceState)
    {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    final ImageView image=(ImageView)findViewById(R.id.imageView1);
    final Bitmap bitmap=((BitmapDrawable)image.getDrawable()).getBitmap();
    _width=bitmap.getWidth();
    _height=bitmap.getHeight();
    image.setOnTouchListener(new OnTouchListener()
      {
        @Override
        public boolean onTouch(final View v,final MotionEvent event)
          {
          final int x=(int)event.getX(),y=(int)event.getY();
          boolean isIn=x>=0&&y>=0&&x<_width&&y<_height;
          if(isIn)
            {
            final int pixel=bitmap.getPixel((int)event.getX(),(int)event.getY());
            final int alphaValue=Color.alpha(pixel);
            isIn=alphaValue!=0;
            }
          if(isIn)
            Log.d("DEBUG","in");
          else Log.d("DEBUG","out");
          return true;
          }
      });
    }
  }

EDIT: Alternative to runJustBeforeBeingDrawn: https://stackoverflow.com/a/28136027/878126

android developer
  • 114,585
  • 152
  • 739
  • 1,270