Demonsoul Demonsoul - 1 month ago 14
Android Question

FAB Ripple effect persisting outside of viewbounds

On all of my layouts, my FloatingActionButtons' ripple effects are persisting if the user lifts their finger outside of the FAB view. i.e. User presses down on white FAB, ripple happens turning FAB gray, user drags finger away from button and lifts it, and the button stays gray.

Here is a sample fab XML:

<android.support.design.widget.FloatingActionButton
android:id="@+id/fab_pinfav"
android:layout_width="@dimen/fab_size"
android:layout_height="@dimen/fab_size"
android:src="@drawable/icon_unfavd"
app:elevation="8dp"
android:elevation="8dp"
android:layout_gravity="bottom|end"
android:layout_marginRight="@dimen/spacingMedium"
android:layout_marginEnd="@dimen/spacingMedium"/>


And all FABs are direct children of varying basic CoordinatorLayouts, i.e.

<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">


Tested on various devices ranging from Android 5.1 to Android 7.1, with the same issue on each.

I have not seen any similar occurrences on my other views as they respond to touch effects.

EDIT: Related Google Issue https://code.google.com/p/android/issues/detail?id=218956 (see 'Issue #2')

Answer

I believe this is due to a fix merged to correct FloatingActionButton's clickable area in pre-Lollipop versions. This fix overrides the onTouchEvent() method to return false if the touch occurs outside of the content area. Unfortunately, this has the effect of stopping any further touch events being delivered to the View, so it never receives the ACTION_UP, and therefore does not update its pressed state accordingly.

We should be able to fix this fix by subclassing FloatingActionButton, overriding onTouchEvent() ourselves, checking the return from the super call, and setting the pressed state correctly if the event has left the content area.

public class CustomFAB extends FloatingActionButton {

    public CustomFAB(Context c, AttributeSet a) {
        super(c, a);
    }

    // Additional constructors as needed
    // ...

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        boolean result = super.onTouchEvent(ev);
        if (!result) {
            setPressed(false);
        }
        return result;
    }
}
Comments