47

A good example of this is either on the Twitter launch screen (the screen with the large icons that is seen when the application is first launch) or even just look at the application tray when you focus an application icon.

Basically I need to highlight an ImageView where the highlight contours to the image within the ImageView and looks like it's a border to that image. I would also like to customize the highlight to have it be a certain color and for it to fade out.

Thanks,

groomsy

groomsy
  • 4,945
  • 6
  • 29
  • 33

10 Answers10

87

You need to assign the src attribute of the ImageView a state list drawable. In other words, that state list would have a different image for selected, pressed, not selected, etc. - that's how the Twitter App does it.

So if you had an ImageView:

<ImageView style="@style/TitleBarLogo"
            android:contentDescription="@string/description_logo"
            android:src="@drawable/title_logo" />

The src drawable (title_logo.xml) would look like this:

<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_focused="true" android:state_pressed="true" android:drawable="@drawable/title_logo_pressed"/>
    <item android:state_focused="false" android:state_pressed="true" android:drawable="@drawable/title_logo_pressed"/>
    <item android:state_focused="true" android:drawable="@drawable/title_logo_selected"/>
    <item android:state_focused="false" android:state_pressed="false" android:drawable="@drawable/title_logo_default"/>
</selector>

The Google IO Schedule app has a good example of this.

Josh Clemm
  • 2,966
  • 20
  • 21
  • I was aware that you could do this, but I was wondering if there was a built in way of handling this. As I said, I know in the application drawer, the application icons do this. Thank you for your help. – groomsy Nov 15 '10 at 16:40
  • 2
    Well it's possible to set the src to your image and then set the background attribute to a selector list. For the normal state, you could show a transparency, and then for selected, you could show the green gradient. I think that should work, but have never tried it. – Josh Clemm Nov 15 '10 at 16:44
  • 1
    Actually, now that I think about it, the application drawer is using a grid layout. So when you have an image in a grid, selecting it will cause the grid cell to highlight. The API demos sample project that comes with the SDK has example code for a grid view. – Josh Clemm Nov 15 '10 at 16:48
  • 4
    Don't forget to add android:clickable="true" in the ImageView. – Song Oct 27 '15 at 00:43
31

If you don't have another drawable for the pressed state you can use setColorFilterto achieve a simple tint effect.

It behaves just like pressed state selector so when the image is pressed it changes the background to light grey color.

final ImageView image = (ImageView) findViewById(R.id.my_image);
image.setOnTouchListener(new View.OnTouchListener() {
        private Rect rect;

        @Override
        public boolean onTouch(View v, MotionEvent event) {
            if(event.getAction() == MotionEvent.ACTION_DOWN){
                image.setColorFilter(Color.argb(50, 0, 0, 0));
                rect = new Rect(v.getLeft(), v.getTop(), v.getRight(), v.getBottom());
            }
            if(event.getAction() == MotionEvent.ACTION_UP){
                image.setColorFilter(Color.argb(0, 0, 0, 0));
            }
            if(event.getAction() == MotionEvent.ACTION_MOVE){
                if(!rect.contains(v.getLeft() + (int) event.getX(), v.getTop() + (int) event.getY())){
                    image.setColorFilter(Color.argb(0, 0, 0, 0));
                } 
            }
            return false;
        }
    });

It handles moving finger outside the view boundaries, thus if it occurs, it restores a default background.

It's important to return false from onTouch method when you want to support onClickListner too.

klimat
  • 24,711
  • 7
  • 63
  • 70
  • man, this is what i need, but in my case it is highlighting only the last image on a HorizontaScrollView. Do you know what could cause this ? thnks – FpontoDesenv Jul 19 '15 at 22:08
  • Yes, it's important to return false to let the click events be triggered, but if you return false, your action up & move will never be called. What to do in this situation? – Sébastien BATEZAT Feb 09 '16 at 22:15
  • @Sebastien remove onClickListener and move logic from it to onTouchListener. You can always implement click as a touch event. – klimat Feb 10 '16 at 07:09
  • 1
    Yes, sure it's possible but I'm working with a gridview and I would like to keep my onItemClickListener. I've tried to call view.performClick on my touch listener but it has no effect. Too bad there is no better options. Thanks anyway – Sébastien BATEZAT Feb 10 '16 at 09:42
5

Use selectableItemBackground as a background:

android:background="?android:attr/selectableItemBackground"
slowcar
  • 314
  • 2
  • 13
4

This is an extension of mklimek. I couldn't make it work properly from his snippet. I edited a bit

 ImageView testImage = (ImageView)findViewById(R.id.imageView);
 testImage.setOnTouchListener(listener);

 View.OnTouchListener listener = new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {


            ImageView image = (ImageView) v;
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    image.getDrawable().setColorFilter(0x77000000,PorterDuff.Mode.SRC_ATOP);
                    image.invalidate();                        
                    break;

                case MotionEvent.ACTION_UP:                        
                case MotionEvent.ACTION_CANCEL: {
                    //clear the overlay
                    image.getDrawable().clearColorFilter();
                    image.invalidate();
                    break;
                }
            }

            return true;
        }
    };
SilleBille
  • 605
  • 5
  • 21
2

For displaying dynamic images you can use a LayerDrawable for the image source.

LayerDrawable d = new LayerDrawable(new Drawable[]{new BitmapDrawable(myBmp), getResources().getDrawable(R.drawable.my_selector_list)});
imageView.setImageDrawable(d);
Tom Bollwitt
  • 10,849
  • 1
  • 17
  • 11
  • 2
    I think [this](http://stackoverflow.com/a/10724364/1099884) could also give us hint. I have add `OnFocusChangeListener` on `ImageVIew`. With `hasFocus`, say `imageVIew.setColorFilter(theColor);` – Yeung Dec 03 '13 at 10:48
  • with my solution you will also get the rest of the states and not have to track it yourself so long as your state list drawable is setup for the states you want. All you do is set the image drawable and your done. – Tom Bollwitt Dec 12 '13 at 17:44
  • Yes you are right. And also we can clone the resultant drawable easily with `drawable.getConstantState().newDrawable()`. It can prevent the imageViews using same drawable update their state together when I only want one ImageView 's drawable state change. – Yeung Dec 18 '13 at 03:13
2

Only to complete Josh Clemm answer. You can also maintain the same image defined by src, but change or highlight only the background. This would more or less like this:

logo_box.xml

<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="true" android:drawable="@drawable/background_normal"/>
    <item android:state_pressed="false" android:drawable="@drawable/background_pressed"/>
</selector>

And then defining the background of your button as logo_box:

<ImageView
    android:contentDescription="@string/description_logo"
    android:src="@drawable/logo"
    android:background="@drawable/logo_box" />

Where background_normal and background_pressed can be as complex as you want, or as simple as a @color :)

jsidera
  • 1,791
  • 1
  • 17
  • 19
2

My solution, custom attribute for ImageView :
https://github.com/henrychuangtw/Android-ImageView-hover

Step 1 : declare-styleable

<declare-styleable name="MyImageViewAttr">
    <attr name="hover_res" format="reference" />
</declare-styleable>


Step 2 : custom ImageView

public class MyImageView extends ImageView {

int resID, resID_hover;

public MyImageView(Context context) {
    super(context);
    // TODO Auto-generated constructor stub
}
public MyImageView(Context context, AttributeSet attrs) {
    super(context, attrs);
    // TODO Auto-generated constructor stub

    TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.MyImageViewAttr);
    resID_hover = array.getResourceId(R.styleable.MyImageViewAttr_hover_res, -1);
    if(resID_hover != -1){
        int[] attrsArray = new int[] {
                android.R.attr.src 
            };

        TypedArray ta = context.obtainStyledAttributes(attrs, attrsArray);
        resID = ta.getResourceId(0 , View.NO_ID);           
        ta.recycle();

        setOnTouchListener(listener_onTouch);
    }

    array.recycle();

}
public MyImageView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    // TODO Auto-generated constructor stub
    TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.MyImageViewAttr);
    resID_hover = array.getResourceId(R.styleable.MyImageViewAttr_hover_res, -1);
    if(resID_hover != -1){
        int[] attrsArray = new int[] {
                android.R.attr.src 
            };

        TypedArray ta = context.obtainStyledAttributes(attrs, attrsArray);
        resID = ta.getResourceId(0 , View.NO_ID);           
        ta.recycle();

        setOnTouchListener(listener_onTouch);
    }

    array.recycle();
}



OnTouchListener listener_onTouch = new OnTouchListener() {

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        // TODO Auto-generated method stub

        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            setImageResource(resID_hover);
            break;

        case MotionEvent.ACTION_MOVE:

            break;

        case MotionEvent.ACTION_UP:
            setImageResource(resID);
            break;

        default:
            break;
        }


        return false;
    }
};

}


Step 3 : declare myattr : xmlns:myattr="http://schemas.android.com/apk/res-auto" in layout xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:myattr="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">


Step 4 : set myattr:hover_res for MyImageView

<dev.henrychuang.component.MyImageView 
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:clickable="true"
        myattr:hover_res="@drawable/icon_home_h"
        android:src="@drawable/icon_home"/>


HenryChuang
  • 1,449
  • 17
  • 28
1

I put together small library that should help with that: https://github.com/noveogroup/Highlightify

Basically it creates selector in runtime, and it should be really easy to use. Though, focused state not supported yet...

Roman Zhilich
  • 145
  • 1
  • 5
1

I noticed that a drawable xml is not enough:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/ic_filter_up" android:state_pressed="true"/>
    <item android:drawable="@drawable/ic_filter_up_shadow"/>
</selector>

An ImageView doesn't press. You should also assign an OnClickListener for an ImageView. Then it will press as a button.

CoolMind
  • 26,736
  • 15
  • 188
  • 224
1

I am using android:state_selected="true" for the state of imageView.

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/ic_enable" android:state_selected="true" />

    <item android:drawable="@drawable/ic_disable" android:state_selected="false" />

    <!--for default-->
    <item android:drawable="@drawable/ic_enable" />

</selector>

use img_view.setSelected(true) OR img_view.setSelected(false) for change state of image in java/kotlin code.

Geet Thakur
  • 1,966
  • 1
  • 16
  • 23