stefanjaindl

Checkout blog posts about various topics helpful to software developers everyday life

blog

Google Maps is a very much-used library on Android. This blog post demonstrates how a Two-Finger-Scroll can be implemented.

Two-Finger scrolling and hint to use 2 fingers:

First we define an interface to show hints:

interface MapScrollInterceptor {
    fun showHint()
}

The core logic lies in the custom MapView class that is designed to react on double finger movements only. Movements with one finger only are intercepted with requestDisallowInterceptTouchEvent. The pointerCount indicates how many fingers are tapping the MapView:

class DoubleTouchableMapView : MapView {
    constructor(context: Context) : super(context)
    constructor(context: Context, options: GoogleMapOptions) : super(context, options)

    var interceptorCallback: MapScrollInterceptor? = null

    override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
        val touchEvent = ev ?: return false

        parent.requestDisallowInterceptTouchEvent(touchEvent.pointerCount > 1)
        return super.dispatchTouchEvent(ev)
    }

    override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
        val touchEvent = ev ?: return false

        if (touchEvent.action == MotionEvent.ACTION_MOVE) {
            if (touchEvent.pointerCount < 2) {
                //Intercept move with one finger only
                interceptorCallback?.showHint()
            }

            return touchEvent.pointerCount < 2
        }

        return super.onInterceptTouchEvent(ev)
    }
}

The GoogleMapsFragment implements the MapScrollInterceptor interface and contains the MapView. It shows (and hides again) the hint using animations:

class GoogleMapsFragment : Fragment(), MapScrollInterceptor {

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    setupMap()
    mapView.onCreate(savedInstanceState)
    mapView.interceptorCallback = this
}

override fun showHint() {
    if (isAnimating) {
        return
    }

    isAnimating = true

    map_scroll_hint.animate()
            .setStartDelay(0L)
            .setDuration(250L)
            .alpha(0.8f)
            .setListener(object : AnimatorListenerAdapter() {
                override fun onAnimationEnd(animation: Animator?) {
                    super.onAnimationEnd(animation)
                    hideHint()
                }
            })
            .start()
}

private fun hideHint() {
    map_scroll_hint.animate()
            .setStartDelay(2000L)
            .setDuration(250L)
            .alpha(0.0f)
            .setListener(object : AnimatorListenerAdapter() {
                override fun onAnimationEnd(animation: Animator?) {
                    super.onAnimationEnd(animation)

                    isAnimating = false
                }
            })
            .start()
}

The layout of GoogleMapsFragment looks the following:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <FrameLayout
            android:id="@+id/map_view_container"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

        <TextView
            android:id="@+id/map_scroll_hint"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:alpha="0.0"
            android:background="@android:color/darker_gray"
            android:gravity="center"
            android:text="@string/use_two_fingers_to_scroll_the_map"
            android:textColor="@android:color/white"
            android:textSize="@dimen/textSizeButton"
            app:layout_constraintBottom_toBottomOf="@+id/map_view_container" />

    </androidx.constraintlayout.widget.ConstraintLayout>

</layout>

Next Post Previous Post