自定义ViewPager指示器动效从分析到实现

1,016 阅读3分钟

准备工作

  最近接到需求,要实现一个ViewPager的指示器。拿到视觉稿,就三个小点感觉分分钟的事。然而,仔细一看发现不简单啊,要实现顺滑切换,两种颜色自然过渡还需要体力。

这里写图片描述

  • 对动效进行拆解分析

  第一个点切换到第二个点时,第一个点往右缩短,第二个点往右增长;同时第一个点由红色平滑过渡到蓝色,第二个点反之。注:点的消长随手势方向,但消长的边有所不同

  • 大体思路

  实现原理很简单,就是画图、动效细节需要花时间调。三个点用RoundRect画,监听ViewPager的滑动;传入滑动距离,根据滑动距离计算RoundRect的left/right值实现消长;同时根据滑动距离比例计算过渡颜色值,给画笔赋值过渡颜色。

动手开撸

  • 监听ViewPager

  这些动效的动作来源当然是ViewPager了。我们需要监听到ViewPager的滑动比例positionOffset及切换position,position决定圆点的切换方向,positionOffset*圆点间距mDistance就是圆点的消长距离了;然后重新绘图,形成动效。

viewpager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
    //记录上一次滑动的positionOffsetPixels值
    private int lastValue = -1;
    
    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
        boolean isLeft = mIsLeft;
        if (lastValue / 10 > positionOffsetPixels / 10) {
            //右滑
            isLeft = false;
        } else if (lastValue / 10 < positionOffsetPixels / 10) {
            //左滑
            isLeft = true;
        }
        if (mNum > 0) {
            move(positionOffset, position % mNum, isLeft);
        }
        lastValue = positionOffsetPixels;       
    }
});
  • 绘图及动效实现

  点分为普通点和长方形点,假设有mNum个点,可以看做mNum+1个普通点,长点占两个坑位,如图。

这里写图片描述
position为当前所在点即长点的位置。左滑时,position位置的点与右边的点联动;右滑时与左边的点联动。

先画普通的点。mIsLeft-是否左滑,mRadius*2-矩形边长

RectF tip = new RectF(0, -mRadius, 0, mRadius);
int offset = mIsLeft ? 1 : 0;
//右边的点
for (int i = mPosition + 2 + offset; i <= mNum; i++) {
    tip.left = -(mNum) * 0.5f * mDistance + i * mDistance - mRadius;
    tip.right = -(mNum) * 0.5f * mDistance + i * mDistance + mRadius;
    canvas.drawRoundRect(tip, mRadius / 2, mRadius / 2, paintDefault);
}
//左边的点
for (int i = mPosition - 1 + offset; i >= 0; i--) {
    tip.left = -(mNum) * 0.5f * mDistance + i * mDistance - mRadius;
    tip.right = -(mNum) * 0.5f * mDistance + i * mDistance + mRadius;
    canvas.drawRoundRect(tip, mRadius / 2, mRadius / 2, paintDefault);
}

再画动效联动的两点

//第一个点消失
float leftClose = -(mNum) * 0.5f * mDistance + mPosition * mDistance - mRadius;
float rightClose = leftClose + 2 * mRadius + mDistance - mOffset;
RectF rectClose = new RectF(leftClose, -mRadius, rightClose, mRadius);// 设置个新的长方形
canvas.drawRoundRect(rectClose, mRadius / 2, mRadius / 2, paintSelect);
//第二个点增长
float rightOpen = -(mNum) * 0.5f * mDistance + (mPosition + 2) * mDistance + mRadius;
float leftOpen = rightOpen - 2 * mRadius - mOffset;
RectF rectOpen = new RectF(leftOpen, -mRadius, rightOpen, mRadius);// 设置个新的长方形
canvas.drawRoundRect(rectOpen, mRadius / 2, mRadius / 2, paintSelect);
  • 颜色的平滑过渡

  此消彼长的动效实现了,颜色的平滑过渡就简单了,原理类似。将选中色与未选中色变为数值,根据移动比例percent来从两个颜色值中取过渡色。

  假设选中色1的值为A1,R1,G1,B1。未选中色2的值为A2,R2,G2,B2。求A1过渡到A2百分比为P的颜色3。

A3 = (A2 - A1)*P + A1;    G3 = (G2 - G1)*P + G1

R3 = (R2 - R1)*P + R1;    B3 = (B2 - B1)*P + B1

  得到所需颜色ARGB值后,还不能直接使用。ARGB是四个十进制的值,而xml中可用颜色为一个十六进制值。对应关系如下

这里写图片描述
将ARGB四个值分别转十六进制然后按顺序拼接,就得到可用的颜色值

public static String caculateColor(String startColor, String endColor, float franch){

        int startAlpha = Integer.parseInt(startColor.substring(1, 3), 16);
        int startRed = Integer.parseInt(startColor.substring(3, 5), 16);
        int startGreen = Integer.parseInt(startColor.substring(5, 7), 16);
        int startBlue = Integer.parseInt(startColor.substring(7), 16);

        int endAlpha = Integer.parseInt(endColor.substring(1, 3), 16);
        int endRed = Integer.parseInt(endColor.substring(3, 5), 16);
        int endGreen = Integer.parseInt(endColor.substring(5, 7), 16);
        int endBlue = Integer.parseInt(endColor.substring(7), 16);

        int currentAlpha = (int) ((endAlpha - startAlpha) * franch + startAlpha);
        int currentRed = (int) ((endRed - startRed) * franch + startRed);
        int currentGreen = (int) ((endGreen - startGreen) * franch + startGreen);
        int currentBlue = (int) ((endBlue - startBlue) * franch + startBlue);

        return "#" + getHexString(currentAlpha) + getHexString(currentRed)
                + getHexString(currentGreen) + getHexString(currentBlue);

    }
    /**
     * 将10进制颜色值转换成16进制。
     */
    public static String getHexString(int value) {
        String hexString = Integer.toHexString(value);
        if (hexString.length() == 1) {
            hexString = "0" + hexString;
        }
        return hexString;
    }

  最后将得到的颜色值赋给对应画笔,appearColor赋给第一个点增长,dismissColor赋给第二个消失的点。大功告成!

int appearColor = GradientColorUtil.caculateColor(mDefault_color, mSelected_color, mPercent);
int dismissColor = GradientColorUtil.caculateColor(mSelected_color, mDefault_color, mPercent);

GitHub地址:https://github.com/VDshixiaoming/SmoothTransIndicator