Banner的实现方式大同小异,这里我使用banner这个开源插件为例,实现一个和小红书类似的Indicator自定义控件。由于公司项目的原因,效果差不多,这里直接放类似效果demo:
指示器
直接贴代码:
public class CustomizedCircleIndicator extends BaseIndicator {
public static final int INDICATOR_SMALL_WIDTH = (int) BannerUtils.dp2px(3);
int indicatorSize;
boolean startAnim=false;
int mPagetate=0;
private int mNormalRadius;
private int mSelectedRadius;
private int mSmallRadius;
private int maxRadius;
private int lastValue = -1;
private boolean isLeft = true;
private boolean begainScrool_anim = false;
private long DxanimTime;
private ValueAnimator mDxscrollAnimator;
private float curX;
private int mSlideDx;
public CustomizedCircleIndicator(Context context) {
this(context, null);
}
public CustomizedCircleIndicator(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomizedCircleIndicator(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mSmallRadius=INDICATOR_SMALL_WIDTH / 2;
mNormalRadius = config.getNormalWidth() / 2;
mSelectedRadius = config.getSelectedWidth() / 2;
DxanimTime=500;
mDxscrollAnimator = new ValueAnimator();
mDxscrollAnimator.setDuration(500);
mDxscrollAnimator.addUpdateListener(animation -> {
// Log.e("渐变","jjj"+curX);
if (Math.abs((int)(float) animation.getAnimatedValue())<=mSlideDx*3){
curX = (float) animation.getAnimatedValue();
invalidate();
}
});
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (config.getIndicatorSize()>5){
indicatorSize = config.getIndicatorSize();
// config.setIndicatorSize(5);
}
// int count = config.getIndicatorSize();
int count =5;
if (count <= 1) {
return;
}
mNormalRadius = config.getNormalWidth() / 2;
mSelectedRadius = config.getSelectedWidth() / 2;
//考虑当 选中和默认 的大小不一样的情况
maxRadius = Math.max(mSelectedRadius, mNormalRadius);
//间距*(总数-1)+选中宽度+默认宽度*(总数-1)
int width = (count - 1) * config.getIndicatorSpace() + config.getSelectedWidth() + config.getNormalWidth() * (count - 1);
setMeasuredDimension(width, Math.max(config.getNormalWidth(), config.getSelectedWidth()));
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// if(mPagetate == ViewPager2.SCROLL_STATE_DRAGGING || mPagetate == ViewPager2.SCROLL_STATE_SETTLING){
// return;
// }
int count = config.getIndicatorSize();
if (count <= 1) {
return;
}
float left = curX;
if (indicatorSize<=5){
for (int i = 0; i < count; i++) {
mPaint.setColor(config.getCurrentPosition() == i ? config.getSelectedColor() : config.getNormalColor());
int radius = mSelectedRadius;
int indicatorWidth =config.getNormalWidth();
canvas.drawCircle(left + radius, maxRadius, radius, mPaint);
left += indicatorWidth + config.getIndicatorSpace();
}
return;
}
//---------------------------
int currentPosition = config.getCurrentPosition();
if (currentPosition>2&¤tPosition<indicatorSize-2){
//中间选中
if (isLeft){
int startpoint=0;
if (startAnim)startpoint=1;
for (int i = startpoint; i < count; i++) {
mPaint.setColor(currentPosition == i ? config.getSelectedColor() : config.getNormalColor());
int radius = mNormalRadius;
//左大右小
int indicatorWidth =config.getNormalWidth();
if (i<=currentPosition)radius=mSelectedRadius;
if (i==currentPosition+1)radius=mNormalRadius;
if (i==currentPosition+2)radius=mSmallRadius;
canvas.drawCircle(left + radius, maxRadius, radius, mPaint);
left += indicatorWidth + config.getIndicatorSpace();
}
}else {
for (int i = 0; i < count; i++) {
mPaint.setColor(currentPosition == i ? config.getSelectedColor() : config.getNormalColor());
int radius = mNormalRadius;
int indicatorWidth =config.getNormalWidth();
//左小右大
if (i>=currentPosition)radius=mSelectedRadius;
if (i==currentPosition-1)radius=mNormalRadius;
if (i==currentPosition-2)radius=mSmallRadius;
canvas.drawCircle(left + radius, maxRadius, radius, mPaint);
left += indicatorWidth + config.getIndicatorSpace();
}
}
}else if ((currentPosition>=2&¤tPosition>=indicatorSize-2)){
//后面两个选中
for (int i = 0; i < count; i++) {
mPaint.setColor(currentPosition == i ? config.getSelectedColor() : config.getNormalColor());
int radius = mNormalRadius;
int indicatorWidth =config.getNormalWidth();
if (i>=indicatorSize-3)radius=mSelectedRadius;
if(i==indicatorSize-4)radius=mNormalRadius;
if(i==indicatorSize-5)radius=mSmallRadius;
canvas.drawCircle(left + radius, maxRadius, radius, mPaint);
left += indicatorWidth + config.getIndicatorSpace();
}
}else{
//前面两个选中
if (isLeft){
for (int i = 0; i < count; i++) {
mPaint.setColor(currentPosition == i ? config.getSelectedColor() : config.getNormalColor());
int radius = mNormalRadius;
int indicatorWidth =config.getNormalWidth();
if (i<3)radius=mSelectedRadius;
if (i==3)radius=mNormalRadius;
if (i==4)radius=mSmallRadius;
canvas.drawCircle(left + radius, maxRadius, radius, mPaint);
left += indicatorWidth + config.getIndicatorSpace();
}
}else{
for (int i = 0; i < count; i++) {
mPaint.setColor(currentPosition == i ? config.getSelectedColor() : config.getNormalColor());
int radius = mNormalRadius;
int indicatorWidth =config.getNormalWidth();
if (i<3)radius=mSelectedRadius;
if (i==3)radius=mNormalRadius;
if (i==4)radius=mSmallRadius;
//如果是重新滑回来并且初始滑动个数大于3
if (currentPosition==1&&begainScrool_anim){
if (i==0)radius=mNormalRadius;
if (i>0&&i<4)radius=mSelectedRadius;
if (i==4)radius=mNormalRadius;
}
canvas.drawCircle(left + radius, maxRadius, radius, mPaint);
left += indicatorWidth + config.getIndicatorSpace();
}
}
}
//---------------------------
// Log.e("渐变","hah"+left);
}
private void startDxScrollAnim(Duration duration, ScrollEndListener listener) {
if (mDxscrollAnimator.isRunning()) {
mDxscrollAnimator.end();
if (duration==Duration.LEFT){
// Log.e("渐变冲突左边","jjj"+(int)curX);
int j=(int)Math.abs(curX)/mSlideDx+1;
if (j<indicatorSize-4){
curX=-j*mSlideDx;
// Log.e("渐变冲突左边1","jjj"+j);
invalidate();
}
}else{
// Log.e("渐变冲突右边","jjj"+(int)curX);
int j=(int)Math.abs(curX)/mSlideDx;
if(j<indicatorSize-4){
curX=-j*mSlideDx;
// Log.e("渐变冲突右边1","jjj"+j);
invalidate();
}
}
}
mDxscrollAnimator.setDuration(DxanimTime);
float endValues = duration == Duration.LEFT ? curX - config.getNormalWidth() -config.getIndicatorSpace()
: curX + config.getNormalWidth() +config.getIndicatorSpace();
mSlideDx=(config.getNormalWidth() +config.getIndicatorSpace());//默认滑动间距
mDxscrollAnimator.setFloatValues(curX, endValues);
mDxscrollAnimator.removeAllListeners();
mDxscrollAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
if (listener != null) {
listener.scrollEnd();
}
}
@Override
public void onAnimationCancel(Animator animation) {
}
});
mDxscrollAnimator.start();
}
@Override
public void onPageSelected(int position) {
// super.onPageSelected(position);
config.setCurrentPosition(position);
if (position>2&&position<indicatorSize-2&&isLeft){
startDxScrollAnim(Duration.LEFT, () -> {});
return;
}
if (position>=2&&position<indicatorSize-3&&!isLeft){
startDxScrollAnim(Duration.RIGHT, () -> {});
return;
}
resetScroolDx(position);
}
private void resetScroolDx(int position) {
if (position==0)curX=0;
if (position==1)curX=0;
if (isLeft){
if (position==2)curX=0;
if (position==indicatorSize-2)curX=-(mSlideDx*(indicatorSize-5));
if (position==indicatorSize-1)curX=-(mSlideDx*(indicatorSize-5));
}else{
if (position==indicatorSize-3)curX=-(mSlideDx*(indicatorSize-5));
if (position==indicatorSize-2)curX=-(mSlideDx*(indicatorSize-5));
if (position==indicatorSize-1)curX=-(mSlideDx*(indicatorSize-5));
}
invalidate();
}
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
// super.onPageScrolled(position, positionOffset, positionOffsetPixels);
offset = positionOffset;
if (positionOffset != 0) {
if (lastValue >= positionOffsetPixels) {
//右滑
isLeft = false;
} else if (lastValue < positionOffsetPixels) {
//左滑
isLeft = true;
//---------------
}
}
lastValue = positionOffsetPixels;
}
@Override
public void onPageScrollStateChanged(int state) {
super.onPageScrollStateChanged(state);
mPagetate=state;
}
/**
* 滑动方向
*/
private enum Duration {
LEFT,
RIGHT
}
private interface ScrollEndListener {
void scrollEnd();
}
}