android custom buttons touch event - android

I have used 4 different colored custom shape buttons. I am trying to implement an ontouch listener by getting the pixel color as shown below
#Override
public boolean onTouch(View v, MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
return isPixelTransparent(v, x, y) || v.onTouchEvent(event);
}
private boolean isPixelTransparent(View v, int x, int y) {
Bitmap bmp = Bitmap.createBitmap(v.getDrawingCache());
int color = Color.TRANSPARENT;
try {
color = bmp.getPixel(x, y);
} catch (IllegalArgumentException e) {
// x or y exceed the bitmap's bounds.
// Reverts the View's internal state from a previously set "pressed" state.
v.setPressed(false);
}
return color == Color.TRANSPARENT;
}
However the rectangular area of the buttons overlap so the buttons underneath dont get pressed if pixel is transparent. Also please note all the buttons have large white text. I have done lots of searching but cannot figure out how to go about it, any help would be much appreciated please.
XML
<za.co.####.####.DiamondShapeButton
android:id="#+id/button_resources"
style="#style/NavigationButton"
android:layout_width="150dp"
android:layout_height="150dp"
android:text="#string/resources"
custom:diamondColor="#color/red"/>
<za.co.####.####.DiamondShapeButton
android:id="#+id/button_practise"
style="#style/NavigationButton"
android:layout_width="150dp"
android:layout_height="150dp"
android:text="#string/practise"
custom:diamondColor="#color/blue"/>
<za.co.####.####.DiamondShapeButton
android:id="#+id/button_tests_exams"
style="#style/NavigationButton"
android:layout_width="150dp"
android:layout_height="150dp"
android:text="#string/my_tests_exams"
custom:diamondColor="#color/orange_light"/>
<za.co.####.####.DiamondShapeButton
android:id="#+id/button_track_studies"
style="#style/NavigationButton"
android:layout_width="150dp"
android:layout_height="150dp"
android:text="#string/track_studies"
custom:diamondColor="#color/black"/>
</za.co.####.####.DiamondShapeLayout>

A View only knows about its own rectangular space, not that of the views that might be behind it. So if a View has a transparent pixel, it will only know that its own pixel is transparent, not anything about the views that may be behind it.
If you want to have a view bypass some event handling, it will need to know about the views behind it and if they wish to handle the event instead. You can't do this with pixel color of a single view - you will have to code this logic yourself based on the overall layout of the views.

Related

make a transparent part of an image unclickable

I use Android studio and I have this image with a transparent background. Whenever i click on it it'll bring me to another Activity. But even when I click on the transparent part of the image it'll bring me to the other Activity.
Is it possible to make the nontransparent part clickable (or touchable) and the transparent part unclickable?
Yes this is possible but it becomes much more difficult than just adding an OnClickListener.
The trick is to use a Touch listener instead of click and on either a DOWN or UP event take the position and then either use some simple maths to work out whether it was a transparent area (if the design is a simple one) or, do some more complicated stuff to work out your pixel values at the centre.
new View.OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
If (event.getAction() == MotionEvent.ACTION_DOWN) {
final int x = (int) event.getX();
final int y = (int) event.getY();
//now map the coords we got to the
//bitmap (because of scaling)
ImageView imageView = ((ImageView)v);
Bitmap bitmap =((BitmapDrawable)imageView.getDrawable()).getBitmap();
int pixel = bitmap.getPixel(x,y);
//now check alpha for transparency
int alpha = Color.alpha(pixel);
If (alpha != 0) {
//do whatever you would have done for your click event here
}
}
return true; //we've handled the event
}
}

Android No Touch Events after Translating Canvas

I have scoured the web in search of an answer to a question that has been bugging me for days. I am making a widget that allows a user to pan the entire widget by dragging (around the Y axis only) within the viewing area. I then have a smaller view (like a button, listens to onTouchEvent) inside here that the user can also drag. The idea is that the user will drag this view up or down and if they want to go higher or lower they can pan the entire widget and then continue dragging the view higher or lower. Now, I'm having two issues with this. First off, after dragging the main view (whole widget) the view that I would like to then move still resides in the area it once was before translating the entire canvas.
So for example, if I move the entire canvas by 50 in the Y position, I then want to move the smaller view but have to touch in the old position rather than the offsetted one (by 50). So it looks like I don't click the smaller view where it is now, but where it used to be (I hope this makes sense).
Next issue, after I move the smaller view. I am not able to trigger anymore touch events. I'm assuming that's because it was moved outside of its parent (which is still viewable since I set clipChildren to false) and its "z-order" is lower than the other views in the layout. Is there a way to always position this on top? I am using Android API 17. Even if I set this smaller view (that I am moving when touched and ACTION_MOVE event is triggered) as the last child in the main layout, I need to be able to drag it out of here (which I can) and continue to drag it on a new onTouchEvent. ANY help is greatly appreciated. I added some layout xml and code snippets below to help.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToPadding="false">
<RelativeLayout
android:id="#+id/main_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToPadding="false">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_centerHorizontal="true"
android:clipChildren="false"
android:clipToPadding="false">
<ImageView
android:id="#+id/ref_image"
android:layout_width="wrap_content"
android:layout_height="216dp"
android:src="#drawable/my_ref_image"
android:layout_centerInParent="true"
android:clipChildren="false"/>
<RelativeLayout
android:id="#+id/selection_view"
style="#style/MyStyle"
android:layout_alignTop="#id/ref_image"
android:layout_marginTop="116dp"
android:clipChildren="false"
android:clipToPadding="false">
<RelativeLayout
android:id="#+id/selection_area"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:clipChildren="false"
android:clipToPadding="false">
<ImageView
android:id="#+id/underlay_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/translucent_image"
android:layout_centerInParent="true"/>
<ImageView
android:id="#+id/point_area"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/point_image"
android:layout_centerInParent="true"/>
</RelativeLayout>
</RelativeLayout>
</RelativeLayout>
</RelativeLayout>
So the snippets of code that is of interest:
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (DEBUG) {
Log.d(TAG, TAG + "::onDraw: ");
}
canvas.translate(0, backgroundPositionY);
}
#Override
public boolean onTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
Log.d(TAG, TAG + "::onTouchEvent: parent touch event");
switch (action) {
case MotionEvent.ACTION_DOWN: {
final float y = ev.getY();
// Remember where we started
lastTouchY = y;
if (DEBUG) {
Log.d(TAG, TAG + "::onTouchEvent: parent ACTION_DOWN");
}
return true;
}
case MotionEvent.ACTION_MOVE: {
final float y = ev.getY();
// Calculate the distance moved
final float dy = y - lastTouchY;
backgroundPositionY += dy
// Remember this touch position for the next move event
lastTouchY = y;
// Invalidate to request a redraw
invalidate();
break;
}
case MotionEvent.ACTION_UP: {
lastTouchY = ev.getY();
}
}
return false;
}
// called upon initialization (selectionArea refs view id = selection_area)
private void initPointListener() {
this.setClipChildren(false);
this.setClipToPadding(false);
if (selectionArea != null) {
selectionArea .setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
RelativeLayout.LayoutParams pointParams = (RelativeLayout.LayoutParams) selectionArea.getLayoutParams();
final int action = motionEvent.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN: {
pointLastTouchY = motionEvent.getY();
}
break;
}
case MotionEvent.ACTION_MOVE: {
float moveY = motionEvent.getY();
final float dy = moveY - pointLastTouchY;
pointPosY += dy;
selectionArea.setY(pointPosY);
break;
}
}
return true;
}
});
}
}
Note that I have tried setting the padding and I have referenced the following on SO with no good results:
How to translate the canvas and still get the touch events on the correct places
Android : click / touch event not working after canvas translate
Alright, well... I found a work around. Basically, my parent view (the widget itself) could always see touch events. But the children could not given the circumstances I mentioned above. So, what I did was keep track of the x and y positions of the child view I was dragging relative to the viewing area. Then whenever I received a touch event I would check to see if I touched the child area or not. If I happened to trigger the onTouchEvent for the child when it was in its old location, I would ignore it if it didn't pass this check. Its sloppy, but its the only way I could get it to work. Now I have a pannable view and a draggable object
I'm not sure did I understand everything on your problem right but maybe this could help you
View with horizontal and vertical pan/drag and pinch-zoom You should try the code from Alex's answer. The code is doing some extra things but you could just take scalelistener (for zoom) off and ofcourse the panning for X-axis. That code is also moving childviews "inside" the canvas so then you shouldn't have to translate click positions after onTouch called.
The better solution is the leave your view in place. Then apply a matrix transformation to your view and the inverted matrix to MotionEvents. This way you can always get your view properly and klugelessly delegated your events, and all the panning takes place in the drawing of the scene. Moving the view around will also hit a basically unpassable problem that you cannot get the MotionEvents that start outside of the bounds of your real view. So if you zoom out, and press something outside the the area where the screen would be it ignores that and doesn't dispatch the touch events there.
Also, it goes insane on some implementations, I think galaxy tab 10, it was just horribly fubar and impossible wrong. It does the invalidation of the view a bit differently than other tablets and refreshed the wrong parts of the view.

Android: Detect touch event in circle shapes

Reference Image
In above image. I want to detect that in which circle user has taped his finger.
If the user has touched black circle then a toast should be shown saying that "You are in Black circle".
If it is blue then "You are in Blue Circle".
And similarly for Red and Yellow Circles.
I know how to detect touch events DOWN, MOVE, PRESS etc and also x, y coordinates.
But don't now how to detect circles area in that image.
EDIT
Actually the above image was just to make you understand my problem. In the actual scenario I don't want to detect the colour pixels. I want to get the exact area of each circle. Because in my case my background colour and my circle colours may be same. So detecting colour pixel will not solve my real problem.
If you know where the center of your circle is (and I assume you know the width of each ring), simply calculate the distance from the center in your on touch event as follows.
#Override public boolean onTouchEvent(MotionEvent ev) {
double distanceFromCenter = Math.sqrt((CENTER_X - ev.getX())^2 + (CENTER_Y - ev.getY())^2);
//Figure out which ring it's in.
}
You could also draw a bunch of overlapping circle shapes, but that would be more complex. See this to get started.
With this you will able to get the proprotion of Red, Green, Blue:
final Bitmap bitmap = ((BitmapDrawable)imageView.getDrawable()).getBitmap();
imageView.setOnTouchListener(new OnTouchListener(){
#Override
public boolean onTouch(View v, MotionEvent event){
int x = (int)event.getX();
int y = (int)event.getY();
int pixel = bitmap.getPixel(x,y);
int red = Color.red(pixel);
int blue = Color.blue(pixel);
int green = Color.green(pixel);
Log.i("color", red+","+green+","+blue);
return false;
}
});
You get the pixel color of pressed position, then detect which color it hits closest and show Toast according to that
How to get pixel color in Android?

How to prevent onClick method on transparent portion of a PNG-loaded ImageView

I am currently developing an Android app that displays multiple images (as ImageView's) stacked on top of each other. Here is how the layers are currently configured:
Background layer: scales the entire screen, must be clickable
Foreground layer: scales the entire screen, must be clickable,
contains transparency which allows the user to see some of the
background layer
The problem I face is with the foreground layer. I am assigning the onClick() method to the imageview, but the method is being called whether they hit the portion of the image which is visible as well as the part which contains transparency. I only want the foreground ImageView onClick() method to be called when the user clicks a portion of that imageview that is not transparent.
This is what the scenario looks like:
The diagonal lines represent the transparent portion of the Foreground image. If a user touches this space, I want it to access the Background image instead of the Foreground image. Thank you for any assistance you can provide.
Here is the solution I implemented (Thanks to answer below):
//ontouchlistener - gets X and Y from event
private void setClick(View view)
{
view.setOnTouchListener(new View.OnTouchListener()
{
public boolean onTouch(View v, MotionEvent event)
{
int imageId = getImageId((int)event.getX(), (int)event.getY());
if (imageId >= 0)
performActions(imageId);
return false;
}
});
}
//get the ID of the first imageview (starting from foreground,
//working backwards) which contains a non-transparent pixel
private int getImageId(int x, int y)
{
ViewGroup parent = (ViewGroup) findViewById(R.id.relative_layout);
for (int a = parent.getChildCount()-1; a >= 0; a--)
{
if (parent.getChildAt(a) instanceof ImageView)
if (!checkPixelTransparent((ImageView)parent.getChildAt(a), x, y))
return parent.getChildAt(a).getId();
}
return -1;
}
//get bitmap from imageview, get pixel from x, y coord
//check if pixel is transparent
private boolean checkPixelTransparent(ImageView iv, int x, int y)
{
Bitmap bitmap = ((BitmapDrawable) iv.getDrawable()).getBitmap();
if (Color.alpha(bitmap.getPixel(x, y)) == 0)
return true;
else
return false;
}
This one sample makes ImageView's transparent area not clickable.
ImageView:
ImageView imgView= (ImageView) findViewById(R.id.color_blue);
imgView.setDrawingCacheEnabled(true);
imgView.setOnTouchListener(changeColorListener);
OnTouchListener:
private final OnTouchListener changeColorListener = new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
Bitmap bmp = Bitmap.createBitmap(v.getDrawingCache());
int color = bmp.getPixel((int) event.getX(), (int) event.getY());
if (color == Color.TRANSPARENT)
return false;
else {
//code to execute
return true;
}
}
};
If your foreground image is not just a rect but a complex image and you really need that the touch is pixel-precise, you may use
http://developer.android.com/reference/android/view/View.OnTouchListener.html
foregroundImage.setOnTouchListener(new View.OnTouchListener(){...});
The MotionEvent in the callback will contain what kind of action happened (e.g. Touch up) and the exact location.
If you know the exact size of the foreground image as it is displayed, you can figure out which pixel of it was clicked, then check if that pixel's alpha is 0. Or you may need to apply some scaling if the image was scaled. This may get quite tricky since depending on the screen size and proportions the image may have been scaled/positioned differently. This also depends on the layouts your were using.
For the check of the pixel value you'd probably need to keep in memory the Bitmap object containing your foreground's image data as well.
Frankly, I doubt you'd really need all that precision unless your foreground image is really of a very irregular shape.
Bitmap.createBitmap(v.getDrawingCache() and imageView.setDrawingCacheEnabled(true) are depreciated so you can do this by the following snippet code:
imageView.setOnTouchListener { v, event ->
val bmp = convertViewToDrawable(v)
val color: Int = bmp.getPixel(event.x.toInt(), event.y.toInt())
if (color == Color.TRANSPARENT)
return#setOnTouchListener false
else {
Toast.makeText(baseContext, "image clicked", Toast.LENGTH_SHORT).show()
return#setOnTouchListener true
}
}
private fun convertViewToDrawable(view: View): Bitmap {
val b = Bitmap.createBitmap(view.measuredWidth, view.measuredHeight,
Bitmap.Config.ARGB_8888)
val c = Canvas(b)
c.translate((-view.scrollX).toFloat(), (-view.scrollY).toFloat())
view.draw(c)
return b
}

Implement onClick only for a TextView compound drawable

I need to have some text with a drawable on the left and I want to execute some code when the user clicks/touches the image (only the image, not the text), so I used a LinearLayout with a TextView and an ImageView which is clickable and launches an onClick event. The XML parser suggests me to replace this with a TextView with a compound drawable, which would draw the same thing with far less lines of XML.. My question is "can I specify I want to handle an onClick event only on the drawable of the TextView and not on the TextView itself? I've seen some solutions which involves writing your own extension of TextView, but I'm only interested in being able to do it within the layout resource, if possible, otherwise I'll keep the following XML code:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="bottom"
android:paddingTop="10dp"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:text="#string/home_feedback_title"
android:textColor="#android:color/primary_text_dark"
android:textStyle="bold"
android:paddingBottom="4dp"/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/action_feedback"
android:clickable="true"
android:onClick="onClickFeedback"
android:contentDescription="#string/action_feedback_description"/>
</LinearLayout>
Its very simple. Lets say you have a drawable on left side of your TextView 'txtview'. Following will do the trick.
TextView txtview = (TextView) findViewById(R.id.txtview);
txtview.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if(event.getAction() == MotionEvent.ACTION_UP) {
if(event.getRawX() <= txtview.getTotalPaddingLeft()) {
// your action for drawable click event
return true;
}
}
return true;
}
});
If you want for right drawable change the if statement to:
if(event.getRawX() >= txtview.getRight() - txtview.getTotalPaddingRight())
Similarly, you can do it for all compound drawables.
txtview.getTotalPaddingTop();
txtview.getTotalPaddingBottom();
This method call returns all the padding on that side including any drawables. You can use this even for TextView, Button etc.
Click here for reference from android developer site.
You can go either way. Using the compound drawable is faster though because it was intended to be an optimization. It uses less ram because you reduce 3 views into 1 and it's faster layout because you lose 1 depth.
If I were you I'd consider stepping back to see if both the text and the image intercepting the touch to do whatever action is possibly a good thing. In general having a larger touch region makes it easier to press. Some users may actually be inclined to touch the text instead of the image.
Lastly if you go that route of merging the 2 you might want to consider using a Button instead of a TextView. You can style the button to not have the rectangle around it. They call it a borderless button. It's nice because you get visual feedback that you clicked on a actionable item where as an ImageView or TextView normally aren't actionable.
How to Create Borderless Buttons in Android
#Vishnuvathsan's answer is almost perfect, but getRaw() returns an absolute x position of the touch point. If the textview is located not on the left edge of the view, you should compare with the absolute position of the textview by using getLocationOnScreen. Code below is an example to check both left drawable tap and right drawable tap.
textView.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {
int[] textLocation = new int[2];
textView.getLocationOnScreen(textLocation);
if (event.getRawX() <= textLocation[0] + textView.getTotalPaddingLeft()) {
// Left drawable was tapped
return true;
}
if (event.getRawX() >= textLocation[0] + textView.getWidth() - textView.getTotalPaddingRight()){
// Right drawable was tapped
return true;
}
}
return true;
}
});
final int DRAWABLE_LEFT = 0;
final int DRAWABLE_TOP = 1;
final int DRAWABLE_RIGHT = 2;
final int DRAWABLE_DOWN = 3;
This click listener is getting in on touch listener
if (event.getAction() == MotionEvent.ACTION_DOWN)
if (event.getAction() == MotionEvent.ACTION_DOWN) {
if (event.getX() >= (tvFollow.getTop() - tvFollow.getCompoundDrawables()[DRAWABLE_TOP].getBounds().width())) {
// your action here
return true; } }
Since getRaw() and getRight() both returns in regards with the entire screen coordinates, this will not work if your views are not on the left edge of the screen. The below solution can be used anywhere regardless of layout position. This is for right side drawable touch.
textView.setOnTouchListener(OnTouchListener { _, event ->
if (event.action == MotionEvent.ACTION_UP)
{
if (event.x >= textView.width - textView.totalPaddingEnd)
{
<!---DO YOUR ACTIONS HERE--->
return#OnTouchListener true
}
}
true
})
PS: Kotlin code

Categories

Resources