Mayank Mohan Upadhyay Mayank Mohan Upadhyay - 4 months ago 68
Android Question

How to get view of android listView fast scroll thumb programmatically?

I am using this github library https://github.com/amlcurran/ShowcaseView to display overlay on view elements of my app during user onboarding (or first time app open after install).

This library requires the view as input and displays overlay on your activity focusing on that view so that you could tell users more about that view.

I have a listView with fast scroll enabled. I want to display overlay on the fast scroll thumb. Hence I want to get view of my listView's fast scroll thumb, but due to absence of any public method in absListView implementation, I'm unable to do this.

Please help.

Answer

The FastScroller implementation for ListView varies by Android version. Prior to KitKat (API 19), the thumb is a Drawable that's drawn directly on the ListView. Starting with KitKat, the thumb is an ImageView that's added to the ListView's ViewGroupOverlay. In either case, it's easy enough to get what we need through reflection.

Since the ultimate goal is to use this with ShowcaseView, it makes sense to just concern ourselves with the dimensions and coordinates of the thumb, regardless of its specific type. In this way, we can use ShowcaseView's PointTarget, no matter the Android version.

The following reflective method grabs a ListView's FastScroller instance, determines the thumb's size and location using the appropriate type, and returns a Point object with the coordinates of the thumb's center point, if possible.

private Point getFastScrollThumbPoint(final ListView listView) {
    try {
        final Class<?> fastScrollerClass = Class.forName("android.widget.FastScroller");

        final int[] listViewLocation = new int[2];
        listView.getLocationInWindow(listViewLocation);
        int x = listViewLocation[0];
        int y = listViewLocation[1];

        final Field fastScrollerField;
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
            fastScrollerField = AbsListView.class.getDeclaredField("mFastScroll");
        }
        else {
            fastScrollerField = AbsListView.class.getDeclaredField("mFastScroller");
        }
        fastScrollerField.setAccessible(true);

        final Object fastScroller = fastScrollerField.get(listView);
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
            final Field thumbImageViewField = fastScrollerClass.getDeclaredField("mThumbImage");
            thumbImageViewField.setAccessible(true);
            final ImageView thumbImageView = (ImageView) thumbImageViewField.get(fastScroller);

            final int[] thumbViewLocation = new int[2];
            thumbImageView.getLocationInWindow(thumbViewLocation);

            x += thumbViewLocation[0] + thumbImageView.getWidth() / 2;
            y += thumbViewLocation[1] + thumbImageView.getHeight() / 2;
        }
        else {
            final Field thumbDrawableField = fastScrollerClass.getDeclaredField("mThumbDrawable");
            thumbDrawableField.setAccessible(true);
            final Drawable thumbDrawable = (Drawable) thumbDrawableField.get(fastScroller);
            final Rect bounds = thumbDrawable.getBounds();

            final Field thumbYField = fastScrollerClass.getDeclaredField("mThumbY");
            thumbYField.setAccessible(true);
            final int thumbY = thumbYField.get(fastScroller);

            x += bounds.left + bounds.width() / 2;
            y += thumbY + bounds.height() / 2;
        }

        return new Point(x, y);
    }
    catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException e) {
        e.printStackTrace();
    }

    return null;
}

To use this with ShowcaseView, we simply check that the returned Point is not null, and pass the Builder a PointTarget created from the return.

Point thumbPoint = getFastScrollThumbPoint(listView);

if (thumbPoint != null) {
    new ShowcaseView.Builder(this)
        .setTarget(new PointTarget(thumbPoint))
        .setContentTitle("ShowcaseView")
        .setContentText("This is highlighting the fast scroll thumb")
        .hideOnTouchOutside()
        .build();
}