該效果是一個長線型的指示器硼瓣,在此基礎(chǔ)上修改定制成圓形指示器怨酝。
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 +
'}';
}
}
}