views:

365

answers:

3

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.

alt text

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"&gt;
  <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):

alt text

<?xml version="1.0" encoding="utf-8"?>
<layer-list
  xmlns:android="http://schemas.android.com/apk/res/android"&gt;
  <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? http://stackoverflow.com/questions/3021401/making-overlaid-image-transparent-on-touch-in-android

+2  A: 

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"&gt;
    <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 : alt text

Removing the transparent part around the tomato (filling up with pink) : alt text

Edit 2: This will make the tomato cover completely the patch-9-png :

<layer-list
    xmlns:android="http://schemas.android.com/apk/res/android"&gt;
    <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

Matthieu
Does your Tomato-image cover the nine-patch entirely? Could u use a non-transparent picture?
OneWorld
It is on top of the nine-patch... the nine-patch is the black rectangle around it. I did not scale / move the tomato around, it's just laid on top..
Matthieu
Ok. I want to know how to cover this nine-patch, so non of the black rectangle is visible anymore.
OneWorld
so ?? Did that do what you wanted ?
Matthieu
Hey Matthieu, well your Edit 2 works on the first look. It was also one of my attempts. However that only works, because that disables ALL 9-patch-features like intelligent stretching of the border. So, that solution equals "Removing the 9-Patch-Information is not a good solution" ;(
OneWorld
A: 

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"&gt;
    <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.

Alex Pretzlav
@Solution1: Thats not the case. I already use a selector in combination with a layer list, which is similiar to the state list.@Solution2: Ok, I didnt provide the raw 9 PATCH PNG, but I can describe the edges pretty easily: LEFT SIDE: first and last pixel empty, rest has the black line. UPPER SIDE: first and last pixel empty, rest has the black line. RIGHT SIDE and BOTTOM SIDE: all empty. The left and upper side ALSO cause a padding behaviour and thats the whole problem of everything! I cant turn off the padding behaviour and keep the stretch behaviour at the same time!
OneWorld
+1  A: 

[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:

NinePatchCover screen shot

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.)

beekeeper
Oh ok, Sounds interesting. Could u provide code of an example? (It's 100 points worth)
OneWorld
Done. Tried, failed, refined, and tested.
beekeeper
So, the code u provided now is intented to work? Im gonna try it out tomorrow.
OneWorld
Yep. This is the complete App that I ran to produce the screen-shot above (minus the manifest which I forgot to paste in -- I can include it if necessary, but it's just the obvious). It certainly seems to meet your requirements. Hopefully you'll agree.
beekeeper
Congratulations! There are your 100 Points ;) You definitely deserve them! ;) Hey, I have some trouble to build a selector in code. I would go this way, but it doesnt work somehow: ((ImageButton)findViewById(R.id.covered)).setBackgroundDrawable( new Selector( ... ) ) ; Could u help me one more time?
OneWorld
I've added another update above to cover the topic. The key is to use "StateListDrawable", which is the class corresponding to the Selector XML declaration.
beekeeper