Reputation: 4209
How can i make a custom shaped clickable view or button in Android?
When I click , I want to avoid touching on an empty area .
please help. Thank you.
Upvotes: 45
Views: 68335
Reputation: 382
I tried the answer by @Basim Sherif (link) and it works great however only if the button size is the same as the original image. If the button was stretched up then the clickable region will be smaller and if the button was set to a smaller size the clickable region will be bigger than the actual button.
The solution is simple which is to scale the iX and iY values to match the original bitmap.
And here is my modified version of the code:
final Bitmap bitmap; //Declare bitmap
bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.TheImage);
public boolean onTouch(View v, MotionEvent event) {
int eventPadTouch = event.getAction();
float iX=event.getX();
float iY=event.getY();
// Get the dimensions used in the view
int realW = this.getWidth();
int realH = this.getHeight();
// Get the dimensions of the actual image
int bitmapW = bitmap.getWidth();
int bitmapH = bitmap.getHeight();
// Scale the coordinates from the view to match the actual image
float scaledX = iX * bitmapW / realW;
float scaledY = iY * bitmapH / realH;
switch (eventPadTouch) {
case MotionEvent.ACTION_DOWN:
if (scaledX >= 0 & scaledY >= 0 & scaledX < bitmap.getWidth() & scaledY < bitmap.getHeight()) { //Makes sure that X and Y are not less than 0, and no more than the height and width of the image.
if (bitmap.getPixel((int) scaledX, (int) scaledY)!=0) {
// actual image area is clicked(alpha not equal to 0), do something
}
}
return true;
}
return false;
}
Upvotes: 0
Reputation: 1
Rather than doing all those changes, you should use frame layout at the portion that surrounds the button, and mask the top right portion with some thingy(circular, like a rounded button) and assign no click listener on that part. This in effect hides the lower frame(i.e. your original button) and masks it with the non active part.
Upvotes: 0
Reputation: 51
I had a similar problem but I did not want to depend on code behind to examine the pixel value. I wanted a simple way (not class overloading) to constrain a touch event to only a sub portion of a drawable. Below I use a LinearLayout for the drawable and then inside that I put a transparent button (with text). I can adjust the margin of the button to position the clickable area.
<LinearLayout
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
android:orientation="horizontal"
android:background="@drawable/circle">
<Button
android:layout_height="match_parent"
android:layout_width="match_parent"
android:id="@+id/btnTimer1"
android:text="0:00"
android:textColor="#ffffff"
android:textSize="22dp"
android:layout_margin="20dp"
android:background="@android:color/transparent"/>
</LinearLayout>
Upvotes: 1
Reputation: 6571
Best and easiest solution (as4me) I found here - it is subclassed Button and thus it supports selector. So all you need to do is to draw/add corresponding pngs for every button state to use selector and declare onClick in xml or add OnClickListener in code and you are ready to go.
Upvotes: 0
Reputation: 5440
Use OnTouch instead of OnClick and check the alpha value of image you have used in the button.If its not equal to zero, do whatever you want. Check the followig code,
final Bitmap bitmap; //Declare bitmap
bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.TheImage);
public boolean onTouch(View v, MotionEvent event) {
int eventPadTouch = event.getAction();
float iX=event.getX();
float iY=event.getY();
switch (eventPadTouch) {
case MotionEvent.ACTION_DOWN:
if (iX>=0 & iY>=0 & iX<bitmap.getWidth() & iY<bitmap.getHeight()) { //Makes sure that X and Y are not less than 0, and no more than the height and width of the image.
if (bitmap.getPixel((int) iX, (int) iY)!=0) {
// actual image area is clicked(alpha not equal to 0), do something
}
}
return true;
}
return false;
}
Upvotes: 15
Reputation: 4683
Interesting question. I tried some solutions and this is what I found that has the same result of what you are trying to achieve. The solution below resolves 2 problems:
So this is the solution in 3 steps:
Create two shapes.
First simple rectangle shape for the button: shape_button_beer.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
<gradient
android:angle="90"
android:endColor="#C5D9F4"
android:startColor="#DCE5FD" />
<corners
android:bottomLeftRadius="5dp"
android:bottomRightRadius="5dp"
android:topLeftRadius="5dp" >
</corners>
</shape>
Second shape is used as mask for the top right side of the button: shape_button_beer_mask.xml. It is simple circle with black solid color.
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval" >
<solid android:color="#000000" />
</shape>
In your main layout add the button by next approach:
android:soundEffectsEnabled="false"
- such that user will not feel that he pressed on something.The XML:
<!-- Custom Button -->
<RelativeLayout
android:layout_width="120dp"
android:layout_height="80dp" >
<LinearLayout
android:id="@+id/custom_buttom"
android:layout_width="100dp"
android:layout_height="100dp"
android:background="@drawable/shape_button_beer" >
<!-- Beer icon and all other stuff -->
<ImageView
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginLeft="5dp"
android:layout_marginTop="15dp"
android:src="@drawable/beer_icon" />
</LinearLayout>
<ImageView
android:id="@+id/do_nothing"
android:layout_width="120dp"
android:layout_height="100dp"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:layout_marginRight="-50dp"
android:layout_marginTop="-50dp"
android:background="@drawable/shape_button_beer_mask"
android:soundEffectsEnabled="false" >
</ImageView>
</RelativeLayout>
<!-- End Custom Button -->
In your main activity you define on click events for both: button and the mask as follow:
LinearLayout customButton = (LinearLayout) findViewById(R.id.custom_buttom);
customButton.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View arg0)
{
Toast.makeText(getApplicationContext(), "Clicked", Toast.LENGTH_SHORT).show();
}
});
// Mask on click will do nothing
ImageView doNothing = (ImageView) findViewById(R.id.do_nothing);
doNothing.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View arg0)
{
// DO NOTHING
}
});
That's it. I know that is not a perfect solution but in your described use case it could help. I have tested it on my mobile and this is how it looks when you click on the blue area and nothing will happen on other areas:
Hope it helped somehow :)
Upvotes: 40
Reputation: 2318
use layer-list, You can design any shape any gradient button tops here is example
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners
android:topLeftRadius="0dp"
android:topRightRadius="0dp"
android:bottomLeftRadius="2dp"
android:bottomRightRadius="15dp"
/>
<!-- The border color -->
<solid android:color="#ffffff" />
</shape>
</item>
<item android:right="2dp"
android:left="2dp"
android:bottom="2dp">
<shape>
<gradient
android:startColor="#002a36"
android:centerColor="#457c8e"
android:endColor="#e6ffff"
android:angle="90"
android:centerY="1"
android:centerX="0.5"
/>
<corners
android:topLeftRadius="0dp"
android:topRightRadius="0dp"
android:bottomLeftRadius="2dp"
android:bottomRightRadius="15dp"
/>
<padding
android:left="10dp"
android:top="10dp"
android:right="10dp"
android:bottom="10dp"
/>
</shape>
</item>
</layer-list>
use -ve radius values to make button shape as u mentioned
Upvotes: 3
Reputation: 3422
u can try this one:
<Button
android:id="@+id/logout"
android:layout_width="240dp"
android:layout_height="28dp"
android:layout_weight="1"
android:gravity="center"
android:text="ContactsDetails"
android:textColor="#ffffff" android:layout_marginLeft="50dp" android:background="@drawable/round"/>
and create round.xml file in drawable folder:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" android:padding="0dp" android:useLevel = "false">
<!-- you can use any color you want I used here gray color-->
<solid android:color="#ABABAB"/>
<corners
android:bottomRightRadius="0dp"
android:bottomLeftRadius="0dp"
android:topLeftRadius="0dp"
android:topRightRadius="70dp"/>
</shape>
Upvotes: 3