How can I change the OverScroll color in Android 2.3.1?

Since Android 2.3.1 there is a new feature for ScrollViews and Lists called OverScroll. With

android:overScrollMode="never"

I can turn it off, but if i don't wan't to turn it off how can I change the Color of it?


Solution 1:

Afaik there is no way to do this. So I created one; presenting:

Graeme's Amazing Custom List View v2

I've created a custom view which performs overscroll for ListViews and for GridViews (XML example is for slightly more involved GridView but view works for both):

<CustomGlowListView     android:id="@+id/searches"
                        android:layout_width="match_parent"
                        android:layout_height="match_parent"
                        android:layout_weight="1"
                        android:background="@android:color/black"
                        android:layout_margin="10dp"
                        android:tag="GridView"
                        android:numColumns="3"  
                        android:horizontalSpacing="8dp" 
                        android:verticalSpacing="8dp">
</CustomGlowListView>   

The CustomGlowListView looks like this:

public class CustomGlowListView extends RelativeLayout{

private ImageView underscrollEdge; 
private ImageView underscrollGlow; 
private ImageView overscrollGlow;
private ImageView overscrollEdge;
private AbsListView listView;

private final   static float    MAX_EDGE_SIZE   = 11f;
private final   static float    MAX_GLOW_SIZE   = 93f;  
private         float   scrollDistanceSinceBoundary     = 0;

private Rect paddingRectangle = new Rect();

GestureDetector listViewGestureDetector;    

// Gives the option of short circuiting the overscroll glow fade (Such as by scrolling away from the overscrolled edge)
boolean interruptFade = false;

public CustomGlowListView(Context context, AttributeSet attrs) 
{ 
    super(context, attrs);

    listViewGestureDetector = new GestureDetector(new ListViewGestureDetector());

    if( getTag() == null ||
        getTag().toString().equalsIgnoreCase("ListView"))       {   listView = new ListView(context);   }
    else if(getTag().toString().equalsIgnoreCase("GridView"))   
    {   
        listView = new GridView(context, attrs);
        ((GridView)listView).getSelector().getPadding(paddingRectangle);
    }
    listView.setId(android.R.id.list);
    listView.setOverScrollMode(OVER_SCROLL_NEVER);
    addView(listView, new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));


    underscrollEdge = new ImageView(context);
    underscrollEdge.setImageResource(R.drawable.underscroll_edge);
    underscrollEdge.setScaleType(ScaleType.FIT_XY);
    underscrollGlow = new ImageView(context);
    underscrollGlow.setImageResource(R.drawable.underscroll_glow);
    underscrollGlow.setScaleType(ScaleType.FIT_XY);
    overscrollGlow = new ImageView(context);
    overscrollGlow.setImageResource(R.drawable.overscroll_glow);
    overscrollGlow.setScaleType(ScaleType.FIT_XY);
    overscrollEdge = new ImageView(context);
    overscrollEdge.setImageResource(R.drawable.overscroll_edge);
    overscrollEdge.setScaleType(ScaleType.FIT_XY);

    addView(underscrollGlow, getWideLayout(ALIGN_PARENT_TOP));      
    addView(underscrollEdge, getWideLayout(ALIGN_PARENT_TOP));  

    addView(overscrollGlow, getWideLayout(ALIGN_PARENT_BOTTOM));        
    addView(overscrollEdge, getWideLayout(ALIGN_PARENT_BOTTOM));    
}   

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    if(ev.getAction() == MotionEvent.ACTION_DOWN) interruptFade = true;
    listViewGestureDetector.onTouchEvent(ev);

    if(ev.getAction() == MotionEvent.ACTION_UP) reset();
    return super.dispatchTouchEvent(ev);
}

private RelativeLayout.LayoutParams getWideLayout(int alignment)
{
    RelativeLayout.LayoutParams returnLayout = new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, 0);
    returnLayout.addRule(alignment);
    return returnLayout;
}

public void reset()
{
    interruptFade = false;
    new GlowShrinker().execute(scrollDistanceSinceBoundary);    
    scrollDistanceSinceBoundary = 0;
}   

private class ListViewGestureDetector extends SimpleOnGestureListener
{       
    @Override
    public boolean onScroll(MotionEvent downMotionEvent, MotionEvent currentMotionEvent, float distanceX, float distanceY) 
    {
        float distanceTraveled = downMotionEvent.getY() - currentMotionEvent.getY();
        if(listIsAtTop() && distanceTraveled < 0) // At top and finger moving down
        {
            scrollDistanceSinceBoundary -= distanceY;
            scaleEdges(underscrollEdge, underscrollGlow, scrollDistanceSinceBoundary);
        }
        else if(listIsAtTop() && distanceTraveled > 0 && scrollDistanceSinceBoundary > 0) // At top and finger moving up while in overscroll
        {
            scrollDistanceSinceBoundary -= distanceY;
            scaleEdges(underscrollEdge, underscrollGlow, scrollDistanceSinceBoundary);
        }           
        else if(listIsAtBottom() && distanceTraveled > 0) // At bottom and finger moving up
        {
            scrollDistanceSinceBoundary += distanceY;
            scaleEdges(overscrollEdge, overscrollGlow, scrollDistanceSinceBoundary);
        }
        else if(listIsAtBottom() && distanceTraveled < 0 && scrollDistanceSinceBoundary > 0) // At bottom and finger moving up while in overscroll
        {
            scrollDistanceSinceBoundary += distanceY;
            scaleEdges(overscrollEdge, overscrollGlow, scrollDistanceSinceBoundary);
        }           
        else if(scrollDistanceSinceBoundary != 0) // Neither over scrolling or under scrolling but was at last check. Reset both graphics. 
        {
            reset();
        }           

        Log.v(CustomGlowListView.class.getSimpleName(), "boundaryDistance = " + scrollDistanceSinceBoundary);

        return false;
    }

    private boolean listIsAtTop()   {   return listView.getChildAt(0).getTop() - paddingRectangle.top == 0;     }
    private boolean listIsAtBottom(){ return listView.getChildAt(listView.getChildCount()-1).getBottom() + paddingRectangle.bottom == listView.getHeight(); } 
}

private class GlowShrinker extends AsyncTask<Float, Integer, Void>
{
    ImageView glow;
    ImageView edge;

    private final int SHRINK_SPEED = 4;
    private final int SHRINK_INCREMENT = 50;

    @Override
    protected void onPreExecute() {         
        if(underscrollGlow.getHeight() > 0)
        {
            glow = underscrollGlow;
            edge = underscrollEdge;
        }
        else if (overscrollGlow.getHeight() > 0)
        {
            glow = overscrollGlow;
            edge = overscrollEdge;
        }
        else
        {
            return;
        }
    }

    @Override
    protected Void doInBackground(Float... scrollDistanceSinceBoundary) {
        if(glow != null && edge != null)
        {
            int currentSize = (int) scrollDistanceSinceBoundary[0].floatValue();
            int shrinkRate  = (int) currentSize / SHRINK_INCREMENT;

            for(int i=0; i < SHRINK_INCREMENT; i++)
            {
                if(interruptFade) 
                {                       
                    publishProgress(0);
                    return null;
                }
                currentSize -= shrinkRate;
                publishProgress(currentSize);

                try {       Thread.sleep(SHRINK_SPEED);     } catch (InterruptedException e) {  }
            }               
        }
        return null;
    }

    @Override
    protected void onPostExecute(Void result) {
        if(glow != null && edge != null)
            CustomGlowListView.scaleEdges(edge, glow, 0);
    }

    @Override
    protected void onProgressUpdate(Integer... values) {
        CustomGlowListView.scaleEdges(edge, glow, values[0]);
    }   
}

private static void scaleEdges(ImageView scrollEdge, ImageView scrollGlow, float scrollBy)
{
    float edgeSize = scrollBy / 20;
    float glowSize = scrollBy / 2;
    if(edgeSize > MAX_EDGE_SIZE) edgeSize = MAX_EDGE_SIZE;
    if(glowSize > MAX_GLOW_SIZE) glowSize = MAX_GLOW_SIZE;
    setHeight(scrollEdge, edgeSize);
    setHeight(scrollGlow, glowSize);
}

private static void setHeight(ImageView viewIn, float height)
{
    ViewGroup.LayoutParams params = viewIn.getLayoutParams();
    params.height = (int) height;
    viewIn.setLayoutParams(params);
}   

public AbsListView getListView()
{
    return listView;
}
}

The overscroll images you can grab from platforms\android-10\data\res\drawable-mdpi\ and then change Hue & Saturation to change color.

I hope this can be a useful start for other ListView customisations - Be interesting to hear of any.

Solution 2:

Actually, instead of using a customized ListView, you can simply "hack" your way to changing the color, the glow effect is actually a Drawable embedded in the OS's resources, you can apply a ColorFilter on that:

int glowDrawableId = context.getResources().getIdentifier("overscroll_glow", "drawable", "android");
Drawable androidGlow = context.getResources().getDrawable(glowDrawableId);
androidGlow.setColorFilter(brandColor, PorterDuff.Mode.MULTIPLY);

Read more about it here: http://evendanan.net/android/branding/2013/12/09/branding-edge-effect/