RecyclerView.ItemDecoration實現(xiàn)指示器效果

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

該效果是一個長線型的指示器硼瓣,在此基礎(chǔ)上修改定制成圓形指示器怨酝。


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;//當(dāng)前
    private int toPos = -1;
    private boolean direction = true;//方向
    private float distance;//位移
    private float mCurrentTime;//當(dāng)前狀態(tài)
    private float lastCurrentTime = 0;//最后狀態(tài)
    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);//設(shè)置圓點位置
            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);//設(shè)置圓點位置
            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);//設(shè)置圓點位置
            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) {//第二階段缸榄,繼續(xù)移動變形,逐漸恢復(fù)
            canvas.translate(startX + (mCurrentTime - 0.2f) * distance / 0.7f, startY);//設(shè)置圓點位置
            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回復(fù)呈圓形
            p4.setMc(mc);//mc回復(fù)呈圓形
            canvas.translate(startX + (mCurrentTime - 0.2f) * distance / 0.7f, startY);//設(shè)置圓點位置
            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);//設(shè)置圓點位置
            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);
            }
        }
        //結(jié)束回復(fù)圓形狀態(tài)
        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);//繪制選中器
    }


    //更新選中器狀態(tài)
    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);
        //獲取個數(shù)
        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);
        //預(yù)留出指示器高度
        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 +
                    '}';
        }
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市庆械,隨后出現(xiàn)的幾起案子铛纬,更是在濱河造成了極大的恐慌嫉戚,老刑警劉巖蹬挺,帶你破解...
    沈念sama閱讀 206,723評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件维贺,死亡現(xiàn)場離奇詭異,居然都是意外死亡巴帮,警方通過查閱死者的電腦和手機溯泣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來榕茧,“玉大人垃沦,你說我怎么就攤上這事∮醚海” “怎么了肢簿?”我有些...
    開封第一講書人閱讀 152,998評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長只恨。 經(jīng)常有香客問我译仗,道長抬虽,這世上最難降的妖魔是什么官觅? 我笑而不...
    開封第一講書人閱讀 55,323評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮阐污,結(jié)果婚禮上休涤,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好功氨,可當(dāng)我...
    茶點故事閱讀 64,355評論 5 374
  • 文/花漫 我一把揭開白布序苏。 她就那樣靜靜地躺著,像睡著了一般捷凄。 火紅的嫁衣襯著肌膚如雪忱详。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,079評論 1 285
  • 那天跺涤,我揣著相機與錄音匈睁,去河邊找鬼。 笑死桶错,一個胖子當(dāng)著我的面吹牛航唆,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播院刁,決...
    沈念sama閱讀 38,389評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼糯钙,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了退腥?” 一聲冷哼從身側(cè)響起任岸,我...
    開封第一講書人閱讀 37,019評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎阅虫,沒想到半個月后演闭,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,519評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡颓帝,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,971評論 2 325
  • 正文 我和宋清朗相戀三年米碰,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片购城。...
    茶點故事閱讀 38,100評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡吕座,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出瘪板,到底是詐尸還是另有隱情吴趴,我是刑警寧澤,帶...
    沈念sama閱讀 33,738評論 4 324
  • 正文 年R本政府宣布侮攀,位于F島的核電站锣枝,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏兰英。R本人自食惡果不足惜撇叁,卻給世界環(huán)境...
    茶點故事閱讀 39,293評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望畦贸。 院中可真熱鬧陨闹,春花似錦楞捂、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至君账,卻和暖如春繁堡,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背乡数。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評論 1 262
  • 我被黑心中介騙來泰國打工帖蔓, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人瞳脓。 一個月前我還...
    沈念sama閱讀 45,547評論 2 354
  • 正文 我出身青樓塑娇,卻偏偏與公主長得像,于是被迫代替她去往敵國和親劫侧。 傳聞我的和親對象是個殘疾皇子埋酬,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,834評論 2 345

推薦閱讀更多精彩內(nèi)容