3

In the application "Google Play", we can see that if you click on any item (LinearLayout, button, imageview, textview ...) this has on it a blue coat.

Being above is not a simple background with android: state_pressed = "true" android: state_focused = "true" ...

Any idea how to apply that effect?

For example, I have a LinearLayout with multiple images and text. This LinearLayout acts as a "button", my intention is that pressing change in appearance having a blue layer on top, as does Google.

I typically use custom background but that applies back and not over.

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

And focus_blue_default.xml:

<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="#8051C9EC" />
</shape>
</item>
</layer-list>

enter image description here

ephramd
  • 561
  • 2
  • 15
  • 41
  • 1
    For what it's worth: Google is most likely populating a `GridView` with `android:drawSelectorOnTop` enabled. As a result, the selector doesn't act as 'background', but more like an 'overlay'. Unfortunately, this attribute is only available on extensions of `AbsListView`, and there is no generic way to apply it to any other view type. You're going to have to make an extension of every `View` you're interested in. Do set up some sort of a helper/util class for the actual logic, so you can leverage that in every extended view. – MH. Sep 11 '13 at 19:26

4 Answers4

3

This is only for reference and I do not think it is practical and it is not tested and ...

Since the answer is accepted, I will state the problems of this code (the ones I can think of right now):

  1. As @user2558882 stated in his comment on the answer, if a subclass uses setContentView(view, params) the params are lost.
  2. This will not work with inflated views -i.e. adapters; Yet, you can solve by calling fix(View) on any inflated view.
  3. It only works with colors. This can be fixed as well.
  4. I think this might work with sherlock-actionbar.
  5. The code has not been tested so please test it.
  6. It shifts your whole view tree one more level by adding a RelativeLayout. This can be fixed using addContentView. I did use this RelativeLayout because I answered this fast and I also think the current code will work better on all phones and apis. This is true because the whole solution is compacted in a RelativeLayout whose behavior is pretty consistent. After all I am only using RelativeLayout.LayoutParams which is the very basic functionality of this ViewGroup.

Solution

Create a BaseActivity.java:

package mobi.sherif.overlay;

import android.app.Activity;
import android.graphics.Color;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.widget.RelativeLayout;

public class BaseActivity extends Activity {
    RelativeLayout mRl;
    View mImage;
    View mContent;
    @Override
    public void setContentView(int layoutResID) {
        setContentView(LayoutInflater.from(this).inflate(layoutResID, null));
    }
    @Override
    public void setContentView(View view) {
        setContentView(view, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
    }
    @Override
    public void setContentView(View view, LayoutParams params) {
        mContent = view;
        fix(view);
        push(view);
    }
    protected void refix() {
        fix(mContent);
    }
    private void push(View view) {
        mRl = new RelativeLayout(this);
        RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT);
        mRl.addView(view, params);
        mImage = new View(this);
        mImage.setVisibility(View.GONE);
        params = new RelativeLayout.LayoutParams(0, 0);
        mRl.addView(mImage, params);
        super.setContentView(mRl);
    }
    protected void fix(View child) {
        if (child == null)
            return;

        doFix(child);
        if (child instanceof ViewGroup) {
            fix((ViewGroup) child);
        }
    }

    private void fix(ViewGroup parent) {
        for (int i = 0; i < parent.getChildCount(); i++) {
            fix(parent.getChildAt(i));
        }
    }
    private void doFix(View child) {
        if(child.getTag()!=null && child.getTag().getClass() == String.class) {
            String color = (String) child.getTag();
            int theColor;
            try {
                theColor = Color.parseColor(color);
                child.setTag(theColor);
            } catch (Exception e) {
                theColor = -1;
            }
            if(theColor != -1) {
                child.setOnTouchListener(new OnTouchListener() {

                    @Override
                    public boolean onTouch(View v, MotionEvent event) {
                        switch (event.getAction()) {
                        case MotionEvent.ACTION_DOWN:
                            mImage.setBackgroundColor((Integer) v.getTag());
                            RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) mImage.getLayoutParams();
                            params.leftMargin = getLeftWithRespectTo(v, mRl);
                            params.topMargin = getTopWithRespectTo(v, mRl);
                            params.width = v.getWidth();
                            params.height = v.getHeight();
                            mImage.setVisibility(View.VISIBLE);
                            mImage.requestLayout();
                            break;
                        case MotionEvent.ACTION_CANCEL:
                        case MotionEvent.ACTION_UP:
                            mImage.setVisibility(View.GONE);
                        default:
                            break;
                        }
                        return v.onTouchEvent(event);
                    }
                });
            }
        }
    }
    private int getLeftWithRespectTo(View view, View relative) {
        int left = 0;
        View temp = view;
        do {
            left += temp.getLeft();
            temp = (View) temp.getParent();
        }
        while(temp!=relative);
        return left;
    }
    private int getTopWithRespectTo(View view, View relative) {
        int top = 0;
        View temp = view;
        do {
            top += temp.getTop();
            temp = (View) temp.getParent();
        }
        while(temp!=relative);
        return top;
    }
}

Now extend this BaseActivity and any view you want to overlay on it a color use android:tag="#AARRGGBB" like this:

<TextView
    android:id="@+id/textView1"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:tag="#331111ff"
    android:text="@string/hello_world" />

TEST: If you use the xml up in activity_main.xml like this:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >

    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginRight="17dp"
        android:layout_marginTop="90dp"
        android:tag="#331111ff"
        android:text="TextView" />

</RelativeLayout>

And use this as your MainActivity.java:

package mobi.sherif.overlay;

import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Toast;

public class MainActivity extends BaseActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        setContentView(R.layout.activity_main);
        findViewById(R.id.textView1).setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                Toast.makeText(MainActivity.this, "text tpicjed", Toast.LENGTH_SHORT).show();
            }
        });
    }
}
Sherif elKhatib
  • 45,786
  • 16
  • 89
  • 106
  • 1
    Another good answer, man. Its impressive that you can write this kind of a code without even testing it. I looked through it, the logic is sound. I'll be disappointed if `ephramd` does not go with this answer. Also, this ain't true: `I do not think it is practical`. – Vikram Sep 26 '13 at 19:05
  • Thanks. I tested it a bit. All what am saying is that this is not the optimal solution, nor a good one. It might work though :P – Sherif elKhatib Sep 26 '13 at 19:45
  • I guess I see your answer a bit differently then. To me, this looks a one-time effort to afford functionality to the entire application. I can also see how this would avoid code repetition. One thing I want to ask: You are not using the `LayoutParams` being supplied in `setContentView(View view, LayoutParams params)`. So, will it be ok if you have `setContentView(view, null);` inside `setContentView(View view)`? – Vikram Sep 26 '13 at 19:57
  • 1
    Ok that's what I mean. That is one mistake, the param passed down is lost somewhere. I will not touch the code now. By the way, I just remembered @user2558882 http://stackoverflow.com/a/18874519/833622 – Sherif elKhatib Sep 26 '13 at 20:03
  • 2
    Amazing @SherifelKhatib! Is an excellent solution. Many answers have been very helpful but your answer is the simplest solution to my problem. Thank you all, I give you my reputation points. – ephramd Sep 26 '13 at 20:12
  • I edited the answer to state some problems with it. Anyway, I am glad I was able to help. – Sherif elKhatib Sep 26 '13 at 20:32
  • @SherifelKhatib One thing I'm not convinced of this code is the use of `default tag`. I'm trying to do: `... textview.setTag(R.id.colorOverlay,"#331111ff"); ...` And in class "BaseActivity" replacement all `getTag()` by `getTag(R.id.colorOverlay)`. However that does not work. – ephramd Sep 26 '13 at 20:38
  • 1
    @ephramd Notice that the fix function is called in the beginning after inflation. If you want to use `setTag` programatically, fix should be called afterwards. If you do not want to call fix for each view, I will edit the answer and create a `refix()` that you can call after finishing the setTag calls. Gimme 1 minute – Sherif elKhatib Sep 26 '13 at 20:44
  • 1
    @ephramd I added the `refix` function. I made `refix` and `fix` protected instead of private. – Sherif elKhatib Sep 26 '13 at 20:46
  • I don't understand how to properly apply this class on a custom listview. I understand that I need to use `fix()`. I apply the setTag on listview ... – ephramd Sep 27 '13 at 08:19
  • In your getView, before returning the view, call fix(view) – Sherif elKhatib Sep 27 '13 at 08:49
1

Lets say you want to achieve this effect on a Button:

  1. Create a class which extends Android Button class
  2. Have a boolean flag as a class member which will be set to true when you want this effect to appear (I would say that you can set it's value according to the touch events detected in onTouchEvent(MotionEvent event) - just override it in your class and analyse those events).
  3. Override onDraw() method, and after the call for the super, add an if clause which will paint this colored rect over the whole view (using Paint) if the boolean flag is true.

Few important points:

  • Alternative to step 3: You can achieve the same effect by having a (semi transparent, blue) drawable which you will add or remove according to the flag.
  • You may need to invalidate your view after changing the boolean value.
Daniel L.
  • 5,060
  • 10
  • 36
  • 59
  • Thank you very much for responding. I'm trying to apply your instructions but this is a bit complicated. Especially because I would like to apply it to any view, and this signfica create a button class, LinearLayout, imageview, etc ... Is not there something simpler like an over view? – ephramd Sep 11 '13 at 18:52
  • 1
    I don't think this would be useful for layouts (from your example of Google Play it seems that you want to use it to emphasise that a view can be clicked). You can also add this as an overlay image (and play with it's visibility), but that would be less convenient in my opinion. – Daniel L. Sep 11 '13 at 19:29
1

While this may not be exactly what you're asking for, I did come up with an easily reusable way to make controls semi-transparent when pressing on them, then have them return to their normal display when you stop pressing on them.

Here's the code:

public class ViewTouchHighlighter implements OnTouchListener 
{

    @Override
    public boolean onTouch(View v, MotionEvent event) 
    {
        if (event.getAction() == MotionEvent.ACTION_DOWN)
        {
            AlphaAnimation animation = new AlphaAnimation(1, 0.5f);
            animation.setDuration(0);
            v.startAnimation(animation);

        }
        else
        {
            AlphaAnimation animation = new AlphaAnimation(0.5f, 1);
            animation.setDuration(0);
            v.startAnimation(animation);
        }
        return false;
    }

}

Then, you can simply grab any view, and give it this behavior like so:

view.setOnTouchListener(new ViewTouchHighlighter());

I realize this is a bit off from what you're looking for, but if highlighting is all you want, this could serve as a nice solution.

If you're insisting on the blue effect, and/or you want something that appears blue when selected, not when simply touched, then I'm afraid I'm going to agree with @Daniel & suggest that you inherit from any component you might want to do this with and add your custom code there.

Gil Moshayof
  • 16,633
  • 4
  • 47
  • 58
1

From what I can tell, the container layout (for individual items) being used on Google play is a RelativeLayout. And achieving this effect is fairly simple using a RelativeLayout. You already have most of the components ready. Here's the final step:

Layout:

<?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:background="@android:color/white" >

    <ImageView 
    ........../>

    <TextView 
    ........../>

    <WhateverView 
    ........../>

    <!--     The final piece -->

    <View
        android:id="@+id/clickableView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:clickable="true"
        android:background="@drawable/state_selector_drawable" />

</RelativeLayout>

And, in your activity, you will be adding an OnClickListener to this View:

View clickableView = findViewById(R.id.clickableView);

clickableView.setOnClickListener(new OnClickListener() {

    @Override
    public void onClick(View arg0) {

        Toast.makeText(MainActivity.this,
                 "Click registered.", Toast.LENGTH_SHORT).show();

    }

});

Let's say you already have a LinearLayout set up and do not wish to switch over to RelativeLayout. That's fine. Place the LinearLayout along with its children inside a RelativeLayout. And add the View defined above:

<?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" >

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/white" >


        <ImageView 
        ........../>

        <TextView 
        ........../>

        <WhateverView 
        ........../>

    </LinearLayout>

    <!--     The final piece -->

    <View
        android:id="@+id/clickableView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:clickable="true"
        android:background="@drawable/state_selector_drawable" />

</RelativeLayout>
Vikram
  • 51,313
  • 11
  • 93
  • 122
  • Probably this this is the easiest solution. However, touch events will be consumed by the overlay. – Sherif elKhatib Sep 26 '13 at 11:58
  • @SherifelKhatib That shouldn't be a problem. OP said: `[this] LinearLayout acts as a "button"`. In this case, the `View` will act as a button. – Vikram Sep 26 '13 at 12:43