How can I change the OverScroll color in Android 2.3.1?

11,988

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/

Share:
11,988
alekz
Author by

alekz

Updated on June 15, 2022

Comments

  • alekz
    alekz almost 2 years

    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?

  • Bahadır Yıldırım
    Bahadır Yıldırım about 12 years
    I'm afraid this solution doesn't work very well (at least not on Gingerbread). The effect seemingly randomly displays while scrolling normally, and doesn't appear when hitting a boundary of the list when flung. The effect only appears as expected when attempting to drag beyond a boundary. Perhaps something is wrong with my (simple) implementation?
  • Bahadır Yıldırım
    Bahadır Yıldırım about 12 years
    Adding an additional check in listIsAtTop() for listView.getFirstVisiblePosition() > 0 (and similarly for the end at listIsAtBottom()) resolves the problem of "random overscroll effects." (This is due to Android's recycling of list items, upon which the calculation is performed). I'd still like to see the effect display when the list is flung, but other than that, it looks good!
  • Bahadır Yıldırım
    Bahadır Yıldırım about 12 years
    I've expanded on your code to allow for fling events (only from API level 9) and have posted it here: pastebin.com/CtauD3sh
  • Graeme
    Graeme about 12 years
    I haven't implemented fling over and under scrolling in the above example. I didn't experience "random overscroll effects" but if you've fixed it great.
  • Bahadır Yıldırım
    Bahadır Yıldırım about 12 years
    onFling() in itself won't do you much good, because it won't tell you when a boundary of a list is "hit". API level 9 introduced the function onOverScrolled() for ListViews (line 51 in my pastebin).