RecyclerView.ItemDecoration实现指示器
2020-03-16 本文已影响0人
gu_jingli
该效果是一个长线型的指示器,在此基础上修改定制成圆形指示器。
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 +
'}';
}
}
}