org.dalol.orthodoxmezmurmedia.utilities.widgets.RecyclerViewFastIndexer.java Source code

Java tutorial

Introduction

Here is the source code for org.dalol.orthodoxmezmurmedia.utilities.widgets.RecyclerViewFastIndexer.java

Source

/*
 * Copyright (c) 2016 Amharic Mezmur Lyrics
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.dalol.orthodoxmezmurmedia.utilities.widgets;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.StateListDrawable;
import android.support.annotation.NonNull;
import android.support.v4.view.ViewCompat;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;

public class RecyclerViewFastIndexer extends LinearLayout {
    private static final int BUBBLE_ANIMATION_DURATION = 100;
    private static final int TRACK_SNAP_RANGE = 5;
    private static final String TAG = RecyclerViewFastIndexer.class.getSimpleName();

    private TextView bubble;
    private View handle;
    private RecyclerView recyclerView;
    private int height;
    private boolean isInitialized = false;
    private ObjectAnimator currentAnimator = null;

    int positionToCapture;

    private final RecyclerView.OnScrollListener onScrollListener = new RecyclerView.OnScrollListener() {
        @Override
        public void onScrolled(final RecyclerView recyclerView, final int dx, final int dy) {
            updateBubbleAndHandlePosition();
        }
    };

    public interface BubbleTextGetter {
        String getTextToShowInBubble(int pos);
    }

    public RecyclerViewFastIndexer(final Context context, final AttributeSet attrs, final int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    public RecyclerViewFastIndexer(final Context context) {
        super(context);
        init(context);
    }

    public RecyclerViewFastIndexer(final Context context, final AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    protected void init(Context context) {
        if (isInitialized)
            return;
        isInitialized = true;
        setOrientation(HORIZONTAL);
        setClipChildren(false);
        setWillNotDraw(false);

        bubble = new TextView(context);
        bubble.setTextColor(Color.WHITE);
        bubble.setTextSize(48);

        GradientDrawable bubbleDrawable = new GradientDrawable();
        bubbleDrawable.setColor(0xFFce891e);
        bubbleDrawable.setSize(getCustomSize(88), getCustomSize(88));
        bubbleDrawable.setCornerRadii(new float[] { getCustomSize(44), getCustomSize(44), getCustomSize(44),
                getCustomSize(44), 0, 0, getCustomSize(44), getCustomSize(44) });

        bubble.setBackgroundDrawable(bubbleDrawable);

        //bubble.setBackgroundResource(R.drawable.recycler_view_fast_scroller__bubble);
        bubble.setGravity(Gravity.CENTER);

        LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
                ViewGroup.LayoutParams.WRAP_CONTENT);
        params.gravity = Gravity.RIGHT | Gravity.END;

        addView(bubble, params);

        if (bubble != null)
            bubble.setVisibility(INVISIBLE);

        handle = new ImageView(context);
        //handle.setBackgroundResource(R.drawable.recycler_view_fast_scroller__handle);

        GradientDrawable drawable = new GradientDrawable();
        drawable.setShape(GradientDrawable.RECTANGLE);
        drawable.setSize(getCustomSize(4), getCustomSize(35));
        drawable.setColor(0xFFce891e);
        drawable.setCornerRadius(getCustomSize(5));

        GradientDrawable drawable2 = new GradientDrawable();
        drawable2.setShape(GradientDrawable.RECTANGLE);
        drawable2.setSize(getCustomSize(4), getCustomSize(35));
        drawable2.setColor(0xFFf3a124);
        drawable2.setCornerRadius(getCustomSize(5));

        StateListDrawable states = new StateListDrawable();
        states.addState(new int[] { android.R.attr.state_selected }, drawable);
        states.addState(new int[] {}, drawable2);

        handle.setBackgroundDrawable(states);

        LayoutParams params2 = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
                ViewGroup.LayoutParams.WRAP_CONTENT);

        int dimension = getCustomSize(3);

        int doubleDimension = dimension * 2;
        params2.leftMargin = doubleDimension;
        params2.rightMargin = doubleDimension;

        handle.setPadding(dimension, 0, dimension, 0);
        handle.setOnFocusChangeListener(new OnFocusChangeListener() {
            @Override
            public void onFocusChange(View v, boolean hasFocus) {
                if (hasFocus) {
                    v.setBackgroundColor(Color.parseColor("#00891e"));
                } else {
                    v.setBackgroundColor(Color.parseColor("#44891e"));
                }
            }
        });

        addView(handle, params2);

    }

    private int getCustomSize(int value) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value,
                getResources().getDisplayMetrics());
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        height = h;
        updateBubbleAndHandlePosition();
    }

    @Override
    public boolean onTouchEvent(@NonNull MotionEvent event) {
        final int action = event.getAction();
        switch (action) {
        case MotionEvent.ACTION_DOWN:
            if (event.getX() < handle.getX() - (ViewCompat.getPaddingStart(handle) * 5))
                return false;
            if (currentAnimator != null)
                currentAnimator.cancel();
            if (bubble != null && bubble.getVisibility() == INVISIBLE)
                showBubble();
            handle.setSelected(true);
        case MotionEvent.ACTION_MOVE:
            final float y = event.getY();
            setBubbleAndHandlePosition(y);
            setRecyclerViewPosition(y);
            return true;
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL:
            handle.setSelected(false);
            hideBubble();
            return true;
        }
        return super.onTouchEvent(event);
    }

    public void initWithRecyclerView(final RecyclerView recyclerView) {
        if (this.recyclerView != recyclerView) {
            if (this.recyclerView != null)
                this.recyclerView.removeOnScrollListener(onScrollListener);
            this.recyclerView = recyclerView;
            if (this.recyclerView == null)
                return;
            recyclerView.setLayoutManager(
                    new LinearLayoutManager(recyclerView.getContext(), LinearLayoutManager.VERTICAL, false) {
                        @Override
                        public void onLayoutChildren(final RecyclerView.Recycler recycler,
                                final RecyclerView.State state) {
                            super.onLayoutChildren(recycler, state);
                            //TODO if the ITEMS are filtered, considered hiding the fast scroller here
                            final int firstVisibleItemPosition = findFirstVisibleItemPosition();
                            if (firstVisibleItemPosition != 0) {
                                // this avoids trying to handle un-needed calls
                                if (firstVisibleItemPosition == -1)
                                    //not initialized, or no ITEMS shown, so hide fast-scroller
                                    setVisibility(View.GONE);
                                return;
                            }
                            final int lastVisibleItemPosition = findLastVisibleItemPosition();
                            int itemsShown = lastVisibleItemPosition - firstVisibleItemPosition + 1;
                            //if all ITEMS are shown, hide the fast-scroller
                            setVisibility(recyclerView.getAdapter().getItemCount() > itemsShown ? View.VISIBLE
                                    : View.GONE);
                        }
                    });
            recyclerView.addOnScrollListener(onScrollListener);
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setColor(Color.parseColor("#2099FF"));
        paint.setStyle(Paint.Style.FILL);

        //handle.getLeft() + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 2, getResources().getDisplayMetrics())
        int bubbleRight = bubble.getRight();

        int center = (getWidth() - bubbleRight) / 2;

        canvas.drawRect(
                bubbleRight + center, 0, center + bubbleRight + TypedValue
                        .applyDimension(TypedValue.COMPLEX_UNIT_DIP, 0.6f, getResources().getDisplayMetrics()),
                getHeight(), paint);
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        if (recyclerView != null) {
            recyclerView.removeOnScrollListener(onScrollListener);
            recyclerView = null;
        }
    }

    public void setColorHandle() {
        GradientDrawable drawable = (GradientDrawable) bubble.getBackground();
        drawable.setColor(Color.parseColor("#01cb56"));
    }

    private void setRecyclerViewPosition(float y) {
        if (recyclerView != null) {
            final int itemCount = recyclerView.getAdapter().getItemCount();
            float proportion;
            if (handle.getY() == 0)
                proportion = 0f;
            else if (handle.getY() + handle.getHeight() >= height - TRACK_SNAP_RANGE)
                proportion = 1f;
            else
                proportion = y / (float) height;
            final int targetPos = getValueInRange(0, itemCount - 1, (int) (proportion * (float) itemCount));
            ((LinearLayoutManager) recyclerView.getLayoutManager()).scrollToPositionWithOffset(targetPos, 0);
            final String bubbleText = ((BubbleTextGetter) recyclerView.getAdapter())
                    .getTextToShowInBubble(targetPos);
            if (bubble != null)
                bubble.setText(bubbleText);
        }
    }

    private int getValueInRange(int min, int max, int value) {
        int minimum = Math.max(min, value);
        return Math.min(minimum, max);
    }

    private void updateBubbleAndHandlePosition() {
        if (bubble == null || handle.isSelected())
            return;

        final int verticalScrollOffset = recyclerView.computeVerticalScrollOffset();
        final int verticalScrollRange = recyclerView.computeVerticalScrollRange();
        float proportion = (float) verticalScrollOffset / ((float) verticalScrollRange - height);
        setBubbleAndHandlePosition(height * proportion);
    }

    private void setBubbleAndHandlePosition(float y) {
        final int handleHeight = handle.getHeight();
        handle.setY(getValueInRange(0, height - handleHeight, (int) (y - handleHeight / 2)));
        if (bubble != null) {
            int bubbleHeight = bubble.getHeight();
            bubble.setY(getValueInRange(0, height - bubbleHeight - handleHeight / 2, (int) (y - bubbleHeight)));
        }
    }

    private void showBubble() {
        if (bubble == null)
            return;
        bubble.setVisibility(VISIBLE);
        if (currentAnimator != null)
            currentAnimator.cancel();

        bubble.setPivotX(bubble.getRight());
        bubble.setPivotY(bubble.getBottom());

        PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat(SCALE_X, 0f, 1f);
        PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat(SCALE_Y, 0f, 1f);
        currentAnimator = ObjectAnimator.ofPropertyValuesHolder(bubble, pvhX, pvhY);
        currentAnimator.setDuration(BUBBLE_ANIMATION_DURATION);

        // currentAnimator = ObjectAnimator.ofFloat(bubble, "alpha", 0f, 1f).setDuration(BUBBLE_ANIMATION_DURATION);
        currentAnimator.start();
    }

    private void hideBubble() {
        if (bubble == null)
            return;
        if (currentAnimator != null)
            currentAnimator.cancel();

        PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat(SCALE_X, 1f, 0f);
        PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat(SCALE_Y, 1f, 0f);
        currentAnimator = ObjectAnimator.ofPropertyValuesHolder(bubble, pvhX, pvhY);
        currentAnimator.setDuration(BUBBLE_ANIMATION_DURATION);

        //currentAnimator = ObjectAnimator.ofFloat(bubble, "alpha", 1f, 0f).setDuration(BUBBLE_ANIMATION_DURATION);
        currentAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                bubble.setVisibility(INVISIBLE);
                currentAnimator = null;
            }

            @Override
            public void onAnimationCancel(Animator animation) {
                super.onAnimationCancel(animation);
                bubble.setVisibility(INVISIBLE);
                currentAnimator = null;
            }
        });
        currentAnimator.start();
    }
}