Search code examples
androidtransparentandroid-custom-viewonclicklistener

Real custom shape of button


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?


Solution

  • 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