RecyclerView.ItemDecoration实现指示器效果

参考:https://github.com/bleeding182/recyclerviewItemDecorations/blob/master/app/src/main/java/com/github/bleeding182/recyclerviewdecorations/viewpager/LinePagerIndicatorDecoration.java

该效果是一个长线型的指示器,在此基础上修改定制成圆形指示器。


Screenrecorder-2020-03-16-13-50-29-163.gif
package com.gujingli.recycler.util;

import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.Rect;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.Interpolator;

public class DropIndicator extends RecyclerView.ItemDecoration {

    private int mIndicatorHeight;//指示器所占高度
    private int indicatorCount;
    private final Interpolator mInterpolator = new AccelerateDecelerateInterpolator();//插值器:先加速再减速
    private static final float DP = Resources.getSystem().getDisplayMetrics().density;//屏幕密度
    private Paint indicatorPaint;//指示器画笔
    private Paint selectorPaint;//选中器画笔
    private Path mPath = new Path();//选中器的形状
    private float radius = DP * 3;//圆直径
    private final double c = 0.552284749831;//path画圆固定值
    private float startX;//记录选中器的中心点X坐标
    private float startY;//记录指示器及选中器的中心点Y坐标
    private float firstX;//记录指示器及选中器的起始中心点X坐标
    private XPoint p2, p4;//右左切线
    private YPoint p1, p3;//下上切线
    private float mc;//切线半长
    private float div = DP * 12;//指示器间距
    private int currentPos = 0;//当前
    private int toPos = -1;
    private boolean direction = true;//方向
    private float distance;//位移
    private float mCurrentTime;//当前状态
    private float lastCurrentTime = 0;//最后状态
    private static final String TAG = "DropIndicator";//日志

    public DropIndicator() {
        //初始化
        mc = (float) (c * radius);
        mIndicatorHeight = (int) (radius * 2 + div);
        //未选中
        indicatorPaint = new Paint();
        indicatorPaint.setColor(0xFFE5E5E5);
        indicatorPaint.setStyle(Paint.Style.FILL);
        indicatorPaint.setAntiAlias(true);
        indicatorPaint.setStrokeWidth(1);
        //选中
        selectorPaint = new Paint();
        selectorPaint.setColor(0xFF000000);
        selectorPaint.setStyle(Paint.Style.FILL);
        selectorPaint.setStrokeWidth(1);
        selectorPaint.setAntiAlias(true);
        //初始化选中点
        p1 = new YPoint(0, radius, mc);//下
        p3 = new YPoint(0, -radius, mc);//上
        p2 = new XPoint(radius, 0, mc);//右
        p4 = new XPoint(-radius, 0, mc);//左
    }

    //初始化选中点
    private void resetP() {
        p1.setY(radius);
        p1.setX(0);
        p1.setMc(mc);

        p3.setY(-radius);
        p3.setX(0);
        p3.setMc(mc);

        p2.setY(0);
        p2.setX(radius);
        p2.setMc(mc);

        p4.setY(0);
        p4.setX(-radius);
        p4.setMc(mc);
    }

    //绘制选中点
    protected void dispatchDraw(Canvas canvas) {
        mPath.reset();
        if (mCurrentTime == 0) {//圆
            resetP();
            canvas.translate(startX, startY);//设置圆点位置
            if (toPos > currentPos) {
                p2.setX(radius);
            } else {
                p4.setX(-radius);
            }
        }
        if (mCurrentTime > 0 && mCurrentTime <= 0.2) {//第一阶段,前端变尖
            direction = toPos > currentPos ? true : false;
            canvas.translate(startX, startY);//设置圆点位置
            if (toPos > currentPos) {
                p2.setX(radius + 2 * 5 * mCurrentTime * radius / 2);//改变p2.x坐标
            } else {
                p4.setX(-radius - 2 * 5 * mCurrentTime * radius / 2);//改变p4.x坐标
            }
        } else if (mCurrentTime > 0.2 && mCurrentTime <= 0.5) {//第二阶段,开始移动变形
            canvas.translate(startX + (mCurrentTime - 0.2f) * distance / 0.7f, startY);//设置圆点位置
            if (toPos > currentPos) {
                p2.setX(2 * radius);//p2.x坐标达到最大值
                p1.setX(0.5f * radius * (mCurrentTime - 0.2f) / 0.3f);//p1.x坐标移动
                p3.setX(0.5f * radius * (mCurrentTime - 0.2f) / 0.3f);//p3.x坐标移动
                p2.setMc(mc + (mCurrentTime - 0.2f) * mc / 4 / 0.3f);//p2.mc间距变大
                p4.setMc(mc + (mCurrentTime - 0.2f) * mc / 4 / 0.3f);//p4.mc间距变大
            } else {
                p4.setX(-2 * radius);//p4.x坐标达到最大值
                p1.setX(-0.5f * radius * (mCurrentTime - 0.2f) / 0.3f);//p1.x坐标移动
                p3.setX(-0.5f * radius * (mCurrentTime - 0.2f) / 0.3f);//p3.x坐标移动
                p2.setMc(mc + (mCurrentTime - 0.2f) * mc / 4 / 0.3f);//p2.mc间距变大
                p4.setMc(mc + (mCurrentTime - 0.2f) * mc / 4 / 0.3f);//p4.mc间距变大
            }
        } else if (mCurrentTime > 0.5 && mCurrentTime <= 0.8) {//第二阶段,继续移动变形,逐渐恢复
            canvas.translate(startX + (mCurrentTime - 0.2f) * distance / 0.7f, startY);//设置圆点位置
            if (toPos > currentPos) {
                p1.setX(0.5f * radius + 0.5f * radius * (mCurrentTime - 0.5f) / 0.3f);
                p3.setX(0.5f * radius + 0.5f * radius * (mCurrentTime - 0.5f) / 0.3f);
                p2.setMc(mc + (mCurrentTime - 0.5f) * mc / 4 / 0.3f);//p2.mc间距变大
                p4.setMc(mc + (mCurrentTime - 0.5f) * mc / 4 / 0.3f);//p4.mc间距变大
            } else {
                p1.setX(-0.5f * radius - 0.5f * radius * (mCurrentTime - 0.5f) / 0.3f);
                p3.setX(-0.5f * radius - 0.5f * radius * (mCurrentTime - 0.5f) / 0.3f);
                p2.setMc(mc + (mCurrentTime - 0.5f) * mc / 4 / 0.3f);//p2.mc间距变大
                p4.setMc(mc + (mCurrentTime - 0.5f) * mc / 4 / 0.3f);//p4.mc间距变大
            }
        } else if (mCurrentTime > 0.8 && mCurrentTime <= 0.9) {
            p2.setMc(mc);//mc回复呈圆形
            p4.setMc(mc);//mc回复呈圆形
            canvas.translate(startX + (mCurrentTime - 0.2f) * distance / 0.7f, startY);//设置圆点位置
            if (toPos > currentPos) {
                p4.setX(-radius + 1.6f * radius * (mCurrentTime - 0.8f) / 0.1f);
            } else {
                p2.setX(radius - 1.6f * radius * (mCurrentTime - 0.8f) / 0.1f);
            }
        } else if (mCurrentTime > 0.9 && mCurrentTime < 1) {
            canvas.translate(startX + distance, startY);//设置圆点位置
            if (toPos > currentPos) {
                p1.setX(radius);
                p3.setX(radius);
                p4.setX(0.6f * radius - 0.6f * radius * (mCurrentTime - 0.9f) / 0.1f);
            } else {
                p1.setX(-radius);
                p3.setX(-radius);
                p2.setX(-0.6f * radius + 0.6f * radius * (mCurrentTime - 0.9f) / 0.1f);
            }
        }
        //结束回复圆形状态
        if (mCurrentTime == 1) {
            lastCurrentTime = 0;
            canvas.translate(startX + distance, startY);
            if (direction) {
                p1.setX(radius);
                p3.setX(radius);
                p4.setX(0);
            } else {
                p1.setX(-radius);
                p3.setX(-radius);
                p2.setX(0);
            }
            currentPos = toPos;
            resetP();
            if (direction)
                canvas.translate(radius, 0);
            else
                canvas.translate(-radius, 0);
        }
        mPath.moveTo(p1.x, p1.y);//起始位置下中点
        mPath.cubicTo(p1.right.x, p1.right.y, p2.bottom.x, p2.bottom.y, p2.x, p2.y);//右下弧
        mPath.cubicTo(p2.top.x, p2.top.y, p3.right.x, p3.right.y, p3.x, p3.y);//右上弧
        mPath.cubicTo(p3.left.x, p3.left.y, p4.top.x, p4.top.y, p4.x, p4.y);//左上弧
        mPath.cubicTo(p4.bottom.x, p4.bottom.y, p1.left.x, p1.left.y, p1.x, p1.y);//左下弧
        canvas.drawPath(mPath, selectorPaint);//绘制选中器
    }


    //更新选中器状态
    private void updateDrop(Canvas c, int position, float positionOffset) {
        //判断滑动方向,确定移动位置toPos
        if ((position + positionOffset) - currentPos > 0)//正
            direction = true;
        else if ((position + positionOffset) - currentPos < 0)//反
            direction = false;
        if (direction)
            toPos = currentPos + 1;
        else
            toPos = currentPos - 1;
        //更新圆点
        startX = firstX + radius + (currentPos) * (div + 2 * radius);
        //更新位移
        distance = direction ? ((2 * radius + div) + (direction ? -radius : radius)) : (-(2 * radius + div) + (direction ? -radius : radius));
        //百分比
        mCurrentTime = position + positionOffset - (int) (position + positionOffset);
        if (!direction)
            mCurrentTime = 1 - mCurrentTime;
        if (Math.abs(lastCurrentTime - mCurrentTime) > 0.2) {
            if (lastCurrentTime < 0.1)
                mCurrentTime = 0;
            else if (lastCurrentTime > 0.9)
                mCurrentTime = 1;
        }
        lastCurrentTime = mCurrentTime;
        dispatchDraw(c);
    }

    @Override
    public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
        super.onDrawOver(c, parent, state);
        //绘制选中器
        LinearLayoutManager layoutManager = (LinearLayoutManager) parent.getLayoutManager();
        int activePosition = layoutManager.findFirstVisibleItemPosition();
        if (activePosition == RecyclerView.NO_POSITION) {
            return;
        }
        // find offset of active page (if the user is scrolling)
        final View activeChild = layoutManager.findViewByPosition(activePosition);
        int left = activeChild.getLeft();
        int width = activeChild.getWidth();
        // on swipe the active item will be positioned from [-width, 0]
        // interpolate offset for smooth animation
        float progress = mInterpolator.getInterpolation(left * -1 / (float) width);
        //更新选中点
        updateDrop(c, activePosition, progress);
        Log.e(TAG, "方法:onDrawOver");
    }

    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        super.onDraw(c, parent, state);
        //获取个数
        indicatorCount = parent.getAdapter().getItemCount();
        //指示圆点所占的长度
        float totalLength = radius * 2 * indicatorCount;
        //间距所占长度
        float paddingBetweenItems = Math.max(0, indicatorCount - 1) * div;
        //指示器总长度
        float indicatorTotalWidth = totalLength + paddingBetweenItems;
        //居中指示器开始位置
        firstX = (parent.getWidth() - indicatorTotalWidth) / 2F;
        //高度的中点
        startY = parent.getHeight() - mIndicatorHeight / 2;
        //绘制指示器
        drawInactiveIndicators(c);
        Log.e(TAG, "方法:onDraw");
    }

    //绘制指示器
    private void drawInactiveIndicators(Canvas c) {
        for (int i = 0; i < indicatorCount; i++) {
            c.drawCircle(firstX + radius + i * (div + 2 * radius), startY, radius, indicatorPaint);
        }
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        //预留出指示器高度
        outRect.bottom = mIndicatorHeight;
        Log.e(TAG, "方法:getItemOffsets");
    }


    class XPoint {//用来存储X轴相等的做坐标点,这里用来存p2,p4,其中p2代表左面3个点,p4代表右面三个点
        public float x;//中间点X坐标
        public float y;//中间点Y坐标
        public float mc;//上下到中间的间距
        public PointF bottom;//下面点
        public PointF top;//上面点

        public XPoint(float x, float y, float mc) {
            this.x = x;
            this.y = y;
            this.mc = mc;
            if (bottom == null)
                bottom = new PointF();
            if (top == null)
                top = new PointF();
            bottom.y = y + mc;
            top.y = y - mc;
            bottom.x = x;
            top.x = x;
        }

        public void setMc(float mc) {
            this.mc = mc;
            bottom.y = y + mc;
            top.y = y - mc;
        }

        public void setY(float y) {
            this.y = y;
            bottom.y = y + mc;
            top.y = y - mc;
        }

        public void setX(float x) {
            this.x = x;
            bottom.x = x;
            top.x = x;
        }

        @Override
        public String toString() {
            return "XPoint{" +
                    "x=" + x +
                    ", y=" + y +
                    ", mc=" + mc +
                    ", bottom=" + bottom +
                    ", top=" + top +
                    '}';
        }
    }

    class YPoint {//用来存储Y轴相等的做坐标点,这里用来存p1,p3,其中p1代表上面3个点,p3代表下面三个点
        public float x;//中间点X坐标
        public float y;//中间点坐标
        public float mc;//左右距中点间距
        public PointF left;//左点
        public PointF right;//右点

        public YPoint(float x, float y, float mc) {
            this.x = x;
            this.y = y;
            this.mc = mc;
            if (left == null)
                left = new PointF();
            if (right == null)
                right = new PointF();
            right.x = x + mc;
            left.x = x - mc;
            left.y = y;
            right.y = y;
        }

        public void setMc(float mc) {
            this.mc = mc;
            right.x = x + mc;
            left.x = x - mc;
        }

        public void setX(float x) {
            this.x = x;
            right.x = x + mc;
            left.x = x - mc;
        }

        public void setY(float y) {
            this.y = y;
            left.y = y;
            right.y = y;
        }

        @Override
        public String toString() {
            return "YPoint{" +
                    "x=" + x +
                    ", y=" + y +
                    ", mc=" + mc +
                    ", left=" + left +
                    ", right=" + right +
                    '}';
        }
    }
}

你可能感兴趣的:(RecyclerView.ItemDecoration实现指示器效果)