I try to implement a hover effect (effect when button is pressed) through putting a semi transparent PNG file on top of the button background and the button icon. Unfortunatly the button background file is a 9-PATCH-PNG which causes some trouble here: It "swallows" everything on top of its layer and doesnt allow to cover the stretchable areas (the fine light line around) of the nine-patch-png. In other words, the black lines the top and left edge of the 9 PATCH PNG cause not only stretching, but also padding behaviour.
Removing the 9-Patch-Information is not a good solution.
Here u can see my Button. The blue background is a 9 PATCH PNG. The thin light line around the button is unwanted.
This layer-list is assigned to the button attribute "background":
<?xml version="1.0" encoding="utf-8"?>
<layer-list
xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:drawable="#drawable/home_btn_bg_blue_without_padding" />
<item>
<bitmap
android:src="#drawable/home_icon_test"
android:gravity="center" />
</item>
<item
android:drawable="#drawable/layer_black_50" />
</layer-list>
Setting the offsets of the layer to "-1" on each border is not valid. Have u guys suggestions?
Update
I tried following, which shall avoid scaling, suggested from here. But didn't work either:
<!-- To avoid scaling, the following example uses a <bitmap> element with centered gravity: -->
<item>
<bitmap android:src="#drawable/image"
android:gravity="center" />
</item>
My version (There are still the stretchable areas of the 9-patch-png uncovered):
<?xml version="1.0" encoding="utf-8"?>
<layer-list
xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:drawable="#drawable/home_btn_bg_blue_hover_without_padding" />
<item>
<bitmap
android:src="#drawable/home_icon_test"
android:gravity="center" />
</item>
<item>
<bitmap android:src="#drawable/layer_black_100"
android:height="100dp"
android:width="100dp"/></item>
</layer-list>
Update 2
Could that work for me? Making Overlaid image transparent on touch in Android?
[NeverMind]
The internal comments for LayerDrawable.getPadding claim that it takes the padding from the first drawable in the list. If this comment is telling the truth, you could get the behavior you want by putting an arbitrary (perhaps empty) image before your 9 patch in the list.
A quick reading of the code, however, implies that it actually uses the sum of all the item's paddings, which means that there's no way to eliminate your problem using the default LayerDrawable. The statement implies the solution: implement a subclass of LayerDrawable which overrides "getPadding" to return {0, 0, 0, 0}. You may have to initialize your subclass in code rather than by loading an XML layout, but this isn't particularly difficult.
[/NeverMind]
Update:
The solution above doesn't work, because the problem isn't the padding itself, it's the fact that the default implementation sets the bounds of each image to be the sum of the paddings of the preceding images. In other words, it enforces nesting, which is what most people will want. The proper solution is still to override LayerDrawable, but you replace "onBoundsChange" instead. A complete, tested demo follows:
package com.beekeeper.ninepatchcover;
import android.app.Activity;
import android.graphics.*;
import android.graphics.drawable.*;
import android.os.Bundle;
import android.view.Gravity;
import android.widget.ImageButton;
public class NinePatchCover extends Activity {
private Drawable mCover0;
private Drawable mCover1;
/** Called when the activity is first created. */
#Override public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
final Drawable button =
getResources().getDrawable(android.R.drawable.btn_default);
final Bitmap iconBitmap =
BitmapFactory.decodeResource(getResources(),
android.R.drawable.ic_menu_mylocation);
final BitmapDrawable icon = new BitmapDrawable(iconBitmap);
icon.setGravity(Gravity.CENTER);
mCover0 =
getResources().getDrawable(android.R.drawable.title_bar);
mCover1 =
getResources().getDrawable(android.R.drawable.title_bar);
final LayerDrawable unsolved =
new LayerDrawable(new Drawable[]{button, icon, mCover0});
final LayerDrawable solved =
new MyLayerDrawable(new Drawable[]{button, icon, mCover1,}, mCover1);
((ImageButton)findViewById(R.id.uncovered)).setBackgroundDrawable(unsolved);
((ImageButton)findViewById(R.id.covered)).setBackgroundDrawable(solved);
}
class MyLayerDrawable extends LayerDrawable {
Drawable mCover;
public MyLayerDrawable(final Drawable[] layers, final Drawable cover) {
super(layers);
mCover = cover;
}
#Override protected void onBoundsChange(final Rect bounds) {
super.onBoundsChange(bounds);
mCover.setBounds(bounds);
}
}
}
using the following layout/main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<ImageButton android:id="#+id/uncovered"
android:layout_width="fill_parent" android:layout_height="wrap_content" />
<ImageButton android:id="#+id/covered"
android:layout_width="fill_parent" android:layout_height="wrap_content" />
</LinearLayout>
A sample screenshot follows:
Update 2:
As requested, here's how you can modify it to initialize a Selector within the code. Replace the initialization of "mCover1" with the following code:
final StateListDrawable sld = new StateListDrawable();
sld.addState(new int[]{android.R.attr.state_pressed},
new ColorDrawable(0xffff0000));
sld.addState(new int[]{android.R.attr.state_window_focused},
new ColorDrawable(0xff00ff00));
sld.addState(new int[]{},
getResources().getDrawable(android.R.drawable.title_bar));
mCover1 = sld;
This will show green in the normal case where the window is focused but the button isn't pressed, red when the button is pressed, and the default drawable (grey) when the window isn't focused. (Try dragging down the "windowshade" notification bar to see the window in it's unfocused state.)
I got it working just fine :
res/layout/main.xml
...
<ImageButton
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:src="#drawable/button"
/>
...
res/drawable/button.xml
<layer-list
xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="#drawable/frame" />
<item>
<bitmap
android:src="#drawable/tomato"
android:gravity="center"
/>
</item>
</layer-list>
frame.9.png is my nine-patch-png. Tomato is a basic png with transparency around it.
Here is the result :
Removing the transparent part around the tomato (filling up with pink) :
Edit 2:
This will make the tomato cover completely the patch-9-png :
<layer-list
xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<bitmap
android:src="#drawable/frame"
/>
</item>
<item>
<bitmap
android:src="#drawable/tomato"
/>
</item>
</layer-list>
Another way, with using an ImageButton is that you can use the patch-9-png as the background and the "content" as the src of the button. In that case, you need to set the padding to 0 for that src
I got a full overlay to work by manipulating the nine-patch. Instead of leaving the bottom and right sides (content) empty, try filling them in completely with black pixels.
I think there are two solutions to this issue.
Solution 1:
When you say "hover effect", do you mean while the button is being pressed down? If that's the case, then what you want to do use use a selector a.k.a. state list for the button background, where the selected state is different than your normal image:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true"
android:drawable="#android:color/black" /> <!-- pressed -->
<item android:drawable="#drawable/home_btn_bg_blue_hover_without_padding" /> <!-- default -->
</selector>
Then set the button background to that state list drawable XML.
Solution 2:
While you haven't posted your raw nine patch PNG, I suspect that you can remove the right and bottom markings denoting the "padding box", while keeping the left and top markings, denoting the "Stretchable area" as documented in the 2D Graphics Doc. This will retain the image's current stretching behavior, but remove any padding that is intrinsic to the image. You can then add or remove padding as desired to the inner views to get the desired display.
Related
I have a simple button
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/add"
android:backgroundTint="#color/add_bg"
android:textColor="#color/add_fg"
<!--android:borderColor?="#color/button_border"-->
android:text="#string/add"/>
I would like to have white background, blue text and blue border around. I am aware that I can achieve that through a drawable as shown here and in numerous other places. However I have observed that if you add a drawable to the button then it will lose all of its material properties (such as shadow and also upon clicking having the fancy ripple animation). So how would I add a border around the button without losing the material theme animations (shadow and tipple animation on click)?
Most of the items that android comes with are simply a pre-packaged set of attributes.
It would be almost impossible to expect the Android API developers to include a pre-packaged set of attributes for every possible color/border combination, but there is always a solution!
Unfortunately,as you mentioned, the solution does reside in creating your own custom XML file which can often be intimidating until you get the hang of it. Once you do, you too will marvel at the flexibility it allows.
Specifically for your situation, there are two options...
1) Create a custom XML border drawable.
2)under your buttons background property set your new custom border drawable
3)then also set the ripple effect under your buttons xml properties by adding:
android:foreground="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
----OR----
A more complex way is to make a drawable like the one below. This will add the "ripple" button effect as well as a custom shadow, button color, and border color!
"For anybody reading this later that may be less experienced)
1)In your project view go to res/drawable
2)right click the folder itself and select new/drawable resource file
3)Enter a file name my_ripple_button.xml(the root doesn't really matter because you wil replace it with the below code)
4)Click on the text tab if you aren't already there
5)select all text and basically replace with the following: (creating a custom color border is basically the same steps)
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="#color/colorPrimaryDark">
<item android:id="#android:id/ripple">
<shape android:shape="rectangle">
<solid android:color="#color/colorPrimaryDark" />
<corners android:radius="#dimen/button_radius_large" />
</shape>
</item>
<item android:id="#android:id/background">
<shape android:shape="rectangle">
<gradient
android:angle="90"
android:endColor="#color/colorPrimaryLight"
android:startColor="#color/colorPrimary"
android:type="linear" />
<corners android:radius="#dimen/button_radius_large" />
</shape>
</item>
</ripple>
I have two buttons, one of them has default style, another has custom background drawable:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="4dp" />
<solid android:color="#color/green" />
</shape>
In the end buttons look like this:
So default button is somehow smaller than button with custom background, though they have same properties:
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#drawable/bg_button_rounded_green"
android:text="Positive"
android:textColor="#color/white"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Negative"/>
It seems that custom background changes minHeight. How can I make this buttons look the same?
btn_default_normal.9.png is the default Image set to default Button widget in Android from any specific platform. Find this file in you SDK installation and copy to your res folder.
Path to find android-sdk\platforms\android-APILEVEL\data\res
Default button have default 9-patch background with own margins, shadows, etc (different for different Android versions). You have to use custom background for both buttons or take one of the default 9-patch background and create similar bg for positive button.
The default Holo Android button graphic is a PNG which purposely contains padding. If you press and hold, you'll notice a glow around the button appears...which takes up that spacing.
The default 9 patch image for the button uses drawable padding. In order to mimic it in your own drawable you can use insets. Something like:
<inset xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="#drawable/bg_button_rounded_green" android:insetBottom="10dip"
android:insetLeft="10dip" android:insetRight="10dip" android:insetTop="10dip" />
I'll let you figure out what the values should be.
I can't dig deep into how android implements its layer-list drawable. But I find it interesting and I can hardly know why this happens.
Here are some drawables:
the nine-patch xml
<?xml version="1.0" encoding="utf-8"?>
<nine-patch xmlns:android="http://schemas.android.com/apk/res/android"
android:src="#drawable/cam2tst_ripple_bg_img">
</nine-patch>
the shape xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<padding
android:left="#dimen/cam2tst_ripple_horizontal_padding"
android:top="#dimen/cam2tst_ripple_vertical_padding"
android:right="#dimen/cam2tst_ripple_horizontal_padding"
android:bottom="#dimen/cam2tst_ripple_vertical_padding" />
<solid android:color="#android:color/holo_green_dark" />
</shape>
the ripple xml
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="#android:color/holo_green_light">
<item android:drawable="#drawable/cam2tst_ripple_shape"></item>
</ripple>
the layer-list containing all above
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="#drawable/cam2tst_ripple_bg_img" />
<item android:drawable="#drawable/cam2tst_ripple_base" />
</layer-list>
Unfortainately I still can't get my screenshot thing work on my L preview, but I can describe it.
What I get is the shape (which i obviously didn't set its size explicitly) doesn't cover over the whole nine-patch! The un-streched part of the nine-patch is magically considered as some kind of "auto padding thing". What I was expecting (ok I was expecting exactly what android has done for me, I mean what I was... supposing...) is something not so positive: the not-particularly-sized shape drawable covering the entire nine-patch just as if the latter is a normal png.
But the shape does magically avoid the un-stretched part of the nine-patch and overlays only above the streched-part of the nine-patch.
This is awesome...but confusing, why? I may not able to dig that deep into the source but this do sounds anti-intuition (but nice). I want to know the reason though. So I post this here.
Since I tagged this as android-L because I am working on one. But I think this shall be working from something like gingerbread.(just to replace the ripple drawable with something else, maybe a inset drawable etc.)
This effect is caused by the combination of two things:
All nine-patch drawables have a padding area defined automatically from the edges of the content area. The content area can be defined either explicitly, using the right and bottom lines at the border, or implicitly from the stretchable area defined by the left and top lines.
Layer-list applies the padding on each layer cumulatively to the next layer by default*, effectively treating each layer as the content of the previous layer.
* Lollipop has introduced a new attribute for disabling this behavior.
I am using a split ActionBar to display some simple media controls. See Below:
I used MenuItem.setActionView to override the default view for the Fast Forward and Rewind buttons because I need to detect when the user initially touches the control (to start rewinding) and subsequently releases the control (to finish rewinding). See code below:
ImageView rewindButton = new ImageView(getBaseContext());
rewindButton.setId(rewindViewID);
rewindButton.setClickable(true);
rewindButton.setImageResource(R.drawable.ic_action_rewind);
rewindButton.setOnTouchListener(forwardBackwardListener);
MenuItem rewindMenu = menu.findItem(R.id.menu_rewind);
rewindMenu.setActionView(rewindButton);
return true;
This is all working well for me but has had an unintended side effect. When I touch the fast forward or rewind button I do not get the default blue background (as shown on the skip backward control above). It displays no background at all. I tried setting the background in the onTouch handler but the background does not fill the height of the ActionBar in the same way as the default one (see example image below), it seems like there is some padding or margin in place, but I don't know how to remove it.
I have tried the following with no luck:
Setting the height of the ImageView Manually to try to fill the ActionBar
Returning True or False from the onTouch Handler
Does anyone know how I might resolve this?
I came up with a workaround which was to set the background of all the items to a small radial gradient when the item is selected. The gradient is still cut off a little but it is hardly noticeable. It makes the custom and regular buttons look almost the same.
First I created the radial gradient in a file called res/drawable/radialbackground.xml . Notice the end color is transparent, this is important to fade the gradient into nothing so it doesn't fill the whole rectangle.
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<gradient
android:centerX="0.5"
android:centerY="0.5"
android:endColor="#00FFFFFF"
android:gradientRadius="35"
android:startColor="#color/Gray"
android:type="radial"
/>
</shape>
Then I created a StateListDrawable called res/drawable/actionitembackground.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"
android:exitFadeDuration="#android:integer/config_mediumAnimTime" >
<item android:state_pressed="true" android:drawable="#drawable/radialbackground" />
<item android:drawable="#android:color/transparent" />
</selector>
Then I assigned that statelistdrawable to the backgrounds of my custom ActionViews :
rewindButton.setBackgroundResource(R.drawable.actionitembackground);
Then I altered the style for the actionbar to include this new radial fill for the regular action bar items.
So, in values/styles.xml I added:
<style name="Theme.MyTheme" parent="Theme.Sherlock.Light">
<item name="android:selectableItemBackground">#drawable/actionitembackground</item>
<item name="selectableItemBackground">#drawable/actionitembackground</item>
</style>
In my case, the base style was Theme.Sherlock.Light but you would replace that with whatever style you wanted to amend.
Then I set that style as the style for my application in AndroidManifest.xml
<application
android:icon="#drawable/ic_launcher"
android:label="#string/app_name"
android:theme="#style/Theme.MyTheme"
android:uiOptions="splitActionBarWhenNarrow" >
This seemed easier than digging through trying to debug why the background is cut off. Perhaps I will spend more time on it another day.
I had exactly the same problem, but with a spinner on action bar,
you need to set de minHeight of yout view to actionBar height:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#drawable/drop_down"
android:minHeight="?android:attr/actionBarSize"
>
<Spinner
android:id="#+id/category_spinner"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#drawable/default_selector"
android:minHeight="?android:attr/actionBarSize"
android:gravity="center_vertical|right"
android:paddingRight="5dp"
/>
I have a ListView that sits on the left side of a tablet-size screen. My goal was to give it a solid background with a border on the right, then apply an overlapping background on the list element to break that border so that it appears to be a part of the view on the right.
The ListView Background
I achieved the right border using a <layer-list> drawable as suggested by Emile in another question:
rightborder.xml
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="#color/black" />
</shape>
</item>
<item android:right="2dp">
<shape android:shape="rectangle">
<solid android:color="#color/white" />
</shape>
</item>
</layer-list>
...and here's the ListView definition for good measure:
<ListView
android:id="#+id/msglist"
android:layout_width="300dp"
android:layout_height="match_parent"
android:divider="#color/black"
android:dividerHeight="1dp"
android:background="#drawable/rightborder"
android:paddingRight="0dip">
</ListView>
<!-- I added the android:paddingRight after reading something
about shape drawables and padding, don't think it actually
did anything. -->
Attempting to override it with a color
In order to achieve the desired effect, I placed the following in the getView function of my adapter:
//If it's selected, highlight the background
if(position == mSelectedIndex)
convertView.setBackgroundColor(R.color.light_gray);
else
convertView.setBackgroundResource(0);
However, using this method, the black border of the ListView's drawable remained visible, and only the white part of the background was replaced by gray. Here's a screen capture:
Fixing it with a drawable
On a hunch, I replaced the color I was assigning with a shape drawable:
selectedmessage.xml:
<?xml version="1.0" encoding="utf-8"?>
<shape android:shape="rectangle"
xmlns:android="http://schemas.android.com/apk/res/android" >
<solid android:color="#color/light_gray" />
</shape>
getView snippet:
//If it's selected, highlight the background
if(position == mSelectedIndex)
convertView.setBackgroundResource(R.drawable.selectedmessage);
else
convertView.setBackgroundResource(0);
This achieves the desired result, as shown below:
The question:
Why does assigning a rectangle as the background for my ListView element cover the entire view, while assigning the color allows the black border to show through? I'm happy it's working, but I'd like to know why Android is rendering the view this way so I can learn more about how Android renders Views.
Other notes:
I'm running the project in the stock Android 3.2 emulator, if that makes any
difference.
One clue may be that the light_gray color background seems to render darker than the light_gray shape resource.
I doubt it makes a difference, but light_gray is:
<color name="light_gray">#FFCCCCCC</color>
You can't do this:
convertView.setBackgroundColor(R.color.light_gray);
setBackgroundColor does not take a resource id : http://developer.android.com/reference/android/view/View.html#setBackgroundColor(int)
So your getting some incidental behaviour that isn't doing what your expecting.
You would have to do:
convertView.setBackgroundColor(getResources().getColor(R.color.light_gray);
http://developer.android.com/reference/android/content/res/Resources.html#getColor(int)