package com.mcxtzhang.cstviewdemo.adddelview.widget;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.graphics.Region;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
/**
* 介绍:仿饿了么加减Button
* 作者:zhangxutong
* 邮箱:mcxtzhang@163.com
* 主页:http://blog.csdn.net/zxt0601
* 时间: 2016/11/18.
*/
public class AddDelView extends View implements IAddDelViewInterface {
private static final String TAG = "zxt/" + AddDelView.class.getName();
//控件 paddingLeft paddingTop + paint的width
private int mLeft, mTop;
//宽高
private int mWidth, mHeight;
/**
* 没有删除按钮(xml)
*/
private boolean isNoDelFunc;
//加减的圆的Path的Region
private Region mAddRegion, mDelRegion;
private Path mAddPath, mDelPath;
/**
* 加按钮
*/
private Paint mAddPaint;
//加按钮是否开启fill模式 默认是stroke(xml)
private boolean isAddFillMode;
//加按钮的背景色前景色(xml)
private int mAddEnableBgColor;
private int mAddEnableFgColor;
//加按钮不可用时的背景色前景色(xml)
private int mAddDisableBgColor;
private int mAddDisableFgColor;
/**
* 减按钮
*/
private Paint mDelPaint;
//按钮是否开启fill模式 默认是stroke(xml)
private boolean isDelFillMode;
//按钮的背景色前景色(xml)
private int mDelEnableBgColor;
private int mDelEnableFgColor;
//按钮不可用时的背景色前景色(xml)
private int mDelDisableBgColor;
private int mDelDisableFgColor;
//最大数量和当前数量(xml)
private int mMaxCount;
private int mCount;
//圆的半径
private float mRadius;
//圆圈的宽度
private float mCircleWidth;
//线的宽度
private float mLineWidth;
/**
* 两个圆之间的间距(xml)
*/
private float mGapBetweenCircle;
private float mTextSize;
private Paint mTextPaint;
private Paint.FontMetrics mFontMetrics;
//动画的基准值 动画:减 0~1, 加 1~0 ,
// 普通状态下都显示时是0
private ValueAnimator mAnimAdd, mAniDel;
private float mAnimFraction;
//展开 加入购物车动画
private ValueAnimator mAnimExpandHint;
private ValueAnimator mAnimReduceHint;
private float mAnimExpandHintFraction;
//是否处于HintMode下 count = 0 时,且第一段收缩动画做完了,是true
private boolean isHintMode;
//展开动画结束后 才显示文字
private boolean isShowHintText;
//hint文字 背景色前景色(xml) 大小
private Paint mHintPaint;
private int mHintBgColor;
private int mHingTextSize;
private String mHintText;
private int mHintFgColor;
/**
* 圆角值(xml)
*/
private int mHintBgRoundValue;
//点击回调
private IAddDelViewInterface.onAddDelListener mOnAddDelListener;
public AddDelView(Context context) {
this(context, null);
}
public AddDelView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public AddDelView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs, defStyleAttr);
}
public int getCount() {
return mCount;
}
/**
* 设置当前数量
*
* @param count
* @return
*/
public AddDelView setCount(int count) {
mCount = count;
//复用机制的处理
if (mCount == 0) {
if (mAnimAdd != null && mAnimAdd.isRunning()) {
mAnimAdd.cancel();
}
if (mAniDel != null && mAniDel.isRunning()) {
mAniDel.cancel();
}
mAnimFraction = 1;
}
return this;
}
public onAddDelListener getOnAddDelListener() {
return mOnAddDelListener;
}
public int getMaxCount() {
return mMaxCount;
}
/**
* 设置最大数量
*
* @param maxCount
* @return
*/
public AddDelView setMaxCount(int maxCount) {
mMaxCount = maxCount;
return this;
}
/**
* 设置加减监听器
*
* @param onAddDelListener
* @return
*/
public AddDelView setOnAddDelListener(onAddDelListener onAddDelListener) {
mOnAddDelListener = onAddDelListener;
return this;
}
public boolean isNoDelFunc() {
return isNoDelFunc;
}
public AddDelView setNoDelFunc(boolean noDelFunc) {
isNoDelFunc = noDelFunc;
return this;
}
private void init(Context context, AttributeSet attrs, int defStyleAttr) {
//模拟参数传入
mGapBetweenCircle = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 34, context.getResources().getDisplayMetrics());
isAddFillMode = true;
mAddEnableBgColor = 0xFFFFDC5B;
mAddEnableFgColor = Color.BLACK;
mAddDisableBgColor = 0xff979797;
mAddDisableFgColor = Color.BLACK;
isDelFillMode = false;
mDelEnableBgColor = 0xff979797;
mDelEnableFgColor = 0xff979797;
mDelDisableBgColor = 0xff979797;
mDelDisableFgColor = 0xff979797;
mMaxCount = 4;
mCount = 1;
mHintText = "加入购物车";
mHintBgColor = mAddEnableBgColor;
mHintFgColor = mAddEnableFgColor;
mHingTextSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 12, context.getResources().getDisplayMetrics());
mHintBgRoundValue = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5, context.getResources().getDisplayMetrics());
//end
mAddRegion = new Region();
mDelRegion = new Region();
mAddPath = new Path();
mDelPath = new Path();
mAddPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
if (isAddFillMode) {
mAddPaint.setStyle(Paint.Style.FILL);
} else {
mAddPaint.setStyle(Paint.Style.STROKE);
}
mDelPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
if (isDelFillMode) {
mDelPaint.setStyle(Paint.Style.FILL);
} else {
mDelPaint.setStyle(Paint.Style.STROKE);
}
mHintPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mHintPaint.setStyle(Paint.Style.FILL);
mHintPaint.setTextSize(mHingTextSize);
mRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 12.5f, getResources().getDisplayMetrics());
mCircleWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1f, getResources().getDisplayMetrics());
mLineWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 2f, getResources().getDisplayMetrics());
mTextSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 14.5f, getResources().getDisplayMetrics());
mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mTextPaint.setTextSize(mTextSize);
mFontMetrics = mTextPaint.getFontMetrics();
//动画 +
mAnimAdd = ValueAnimator.ofFloat(1, 0);
mAnimAdd.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mAnimFraction = (float) animation.getAnimatedValue();
invalidate();
}
});
mAnimAdd.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
}
});
mAnimAdd.setDuration(350);
//提示语收缩动画 0-1
mAnimReduceHint = ValueAnimator.ofFloat(0, 1);
mAnimReduceHint.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mAnimExpandHintFraction = (float) animation.getAnimatedValue();
invalidate();
}
});
mAnimReduceHint.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (mCount == 1) {
//然后底色也不显示了
isHintMode = false;
}
if (mCount == 1) {
Log.d(TAG, "现在还是1 开始收缩动画");
if (mAnimAdd != null && !mAnimAdd.isRunning()) {
mAnimAdd.start();
}
}
}
@Override
public void onAnimationStart(Animator animation) {
if (mCount == 1) {
//先不显示文字了
isShowHintText = false;
}
}
});
mAnimReduceHint.setDuration(350);
//动画 -
mAniDel = ValueAnimator.ofFloat(0, 1);
mAniDel.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mAnimFraction = (float) animation.getAnimatedValue();
invalidate();
}
});
//1-0的动画
mAniDel.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (mCount == 0) {
Log.d(TAG, "现在还是0onAnimationEnd() called with: animation = [" + animation + "]");
if (mAnimExpandHint != null && !mAnimExpandHint.isRunning()) {
mAnimExpandHint.start();
}
}
}
});
mAniDel.setDuration(350);
//提示语展开动画
//分析这个动画,最初是个圆。 就是left 不断减小
mAnimExpandHint = ValueAnimator.ofFloat(1, 0);
mAnimExpandHint.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mAnimExpandHintFraction = (float) animation.getAnimatedValue();
invalidate();
}
});
mAnimExpandHint.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (mCount == 0) {
isShowHintText = true;
}
}
@Override
public void onAnimationStart(Animator animation) {
if (mCount == 0) {
isHintMode = true;
}
}
});
mAnimExpandHint.setDuration(350);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int wMode = MeasureSpec.getMode(widthMeasureSpec);
int wSize = MeasureSpec.getSize(widthMeasureSpec);
int hMode = MeasureSpec.getMode(heightMeasureSpec);
int hSize = MeasureSpec.getSize(heightMeasureSpec);
switch (wMode) {
case MeasureSpec.EXACTLY:
break;
case MeasureSpec.AT_MOST:
int computeSize = (int) (getPaddingLeft() + mRadius * 2 +/* mGap * 2 + mTextPaint.measureText(mCount + "")*/mGapBetweenCircle + mRadius * 2 + getPaddingRight() + mCircleWidth * 2);
wSize = computeSize < wSize ? computeSize : wSize;
break;
case MeasureSpec.UNSPECIFIED:
computeSize = (int) (getPaddingLeft() + mRadius * 2 + /*mGap * 2 + mTextPaint.measureText(mCount + "")*/mGapBetweenCircle + mRadius * 2 + getPaddingRight() + mCircleWidth * 2);
wSize = computeSize;
break;
}
switch (hMode) {
case MeasureSpec.EXACTLY:
break;
case MeasureSpec.AT_MOST:
int computeSize = (int) (getPaddingTop() + mRadius * 2 + getPaddingBottom() + mCircleWidth * 2);
hSize = computeSize < hSize ? computeSize : hSize;
break;
case MeasureSpec.UNSPECIFIED:
computeSize = (int) (getPaddingTop() + mRadius * 2 + getPaddingBottom() + mCircleWidth * 2);
hSize = computeSize;
break;
}
setMeasuredDimension(wSize, hSize);
if (mCount == 0) {
isHintMode = true;
isShowHintText = true;
} else {
isHintMode = false;
isShowHintText = false;
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mLeft = (int) (getPaddingLeft() + mCircleWidth);
mTop = (int) (getPaddingTop() + mCircleWidth);
mWidth = w;
mHeight = h;
}
@Override
protected void onDraw(Canvas canvas) {
if (isHintMode) {
//add hint 展开动画
//if (mCount == 0) {
//背景
mHintPaint.setColor(mHintBgColor);
RectF rectF = new RectF((mWidth - mRadius * 2) * mAnimExpandHintFraction, 0, getWidth(), getHeight());
canvas.drawRoundRect(rectF, mHintBgRoundValue, mHintBgRoundValue, mHintPaint);
if (isShowHintText) {
//前景文字
mHintPaint.setColor(mHintFgColor);
// 计算Baseline绘制的起点X轴坐标
int baseX = (int) (mWidth / 2 - mHintPaint.measureText(mHintText) / 2);
// 计算Baseline绘制的Y坐标
int baseY = (int) ((mHeight / 2) - ((mHintPaint.descent() + mHintPaint.ascent()) / 2));
canvas.drawText(mHintText, baseX, baseY, mHintPaint);
}
//}
} else {
//如果没有删除按钮
if (!isNoDelFunc) {
//动画 mAnimFraction :减 0~1, 加 1~0 ,
//动画位移Max,
float animOffsetMax = (mRadius * 2 + /*mGap * 2 + mTextPaint.measureText(mCount + "")*/mGapBetweenCircle);
//透明度动画的基准
int animAlphaMax = 255;
int animRotateMax = 360;
//左边
//背景 圆
if (mCount > 0) {
mDelPaint.setColor(mDelEnableBgColor);
} else {
mDelPaint.setColor(mDelDisableBgColor);
}
mDelPaint.setAlpha((int) (animAlphaMax * (1 - mAnimFraction)));
mDelPaint.setStrokeWidth(mCircleWidth);
mDelPath.reset();
mDelPath.addCircle(animOffsetMax * mAnimFraction + mLeft + mRadius, mTop + mRadius, mRadius, Path.Direction.CW);
mDelRegion.setPath(mDelPath, new Region(mLeft, mTop, mWidth - getPaddingRight(), mHeight - getPaddingBottom()));
//canvas.drawCircle(mAnimOffset + mLeft + mRadius, mTop + mRadius, mRadius, mPaint);
canvas.drawPath(mDelPath, mDelPaint);
//前景 +
if (mCount > 0) {
mDelPaint.setColor(mDelEnableFgColor);
} else {
mDelPaint.setColor(mDelDisableFgColor);
}
mDelPaint.setStrokeWidth(mLineWidth);
//旋转动画
canvas.save();
canvas.translate(animOffsetMax * mAnimFraction + mLeft + mRadius, mTop + mRadius);
canvas.rotate((int) (animRotateMax * (1 - mAnimFraction)));
/*canvas.drawLine(mAnimOffset + mLeft + mRadius / 2, mTop + mRadius,
mAnimOffset + mLeft + mRadius / 2 + mRadius, mTop + mRadius,
mPaint);*/
canvas.drawLine(-mRadius / 2, 0,
+mRadius / 2, 0,
mDelPaint);
canvas.restore();
}
//数量
canvas.save();
//平移动画
canvas.translate(mAnimFraction * (/*mGap*/mGapBetweenCircle / 2 - mTextPaint.measureText(mCount + "") / 2 + mRadius), 0);
//旋转动画,旋转中心点,x 是绘图中心,y 是控件中心
canvas.rotate(360 * mAnimFraction,
/*mGap*/ mGapBetweenCircle / 2 + mLeft + mRadius * 2 /*+ mTextPaint.measureText(mCount + "") / 2*/,
mTop + mRadius);
//透明度动画
mTextPaint.setAlpha((int) (255 * (1 - mAnimFraction)));
//是没有动画的普通写法,x left, y baseLine
canvas.drawText(mCount + "", /*mGap*/ mGapBetweenCircle / 2 - mTextPaint.measureText(mCount + "") / 2 + mLeft + mRadius * 2, mTop + mRadius - (mFontMetrics.top + mFontMetrics.bottom) / 2, mTextPaint);
canvas.restore();
//右边
//背景 圆
if (mCount < mMaxCount) {
mAddPaint.setColor(mAddEnableBgColor);
} else {
mAddPaint.setColor(mAddDisableBgColor);
}
mAddPaint.setStrokeWidth(mCircleWidth);
float left = mLeft + mRadius * 2 + /*mGap * 2 + mTextPaint.measureText(mCount + "")*/ mGapBetweenCircle;
mAddPath.reset();
mAddPath.addCircle(left + mRadius, mTop + mRadius, mRadius, Path.Direction.CW);
mAddRegion.setPath(mAddPath, new Region(mLeft, mTop, mWidth - getPaddingRight(), mHeight - getPaddingBottom()));
//canvas.drawCircle(left + mRadius, mTop + mRadius, mRadius, mPaint);
canvas.drawPath(mAddPath, mAddPaint);
//前景 +
if (mCount < mMaxCount) {
mAddPaint.setColor(mAddEnableFgColor);
} else {
mAddPaint.setColor(mAddDisableFgColor);
}
mAddPaint.setStrokeWidth(mLineWidth);
canvas.drawLine(left + mRadius / 2, mTop + mRadius, left + mRadius / 2 + mRadius, mTop + mRadius, mAddPaint);
canvas.drawLine(left + mRadius, mTop + mRadius / 2, left + mRadius, mTop + mRadius / 2 + mRadius, mAddPaint);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
//文字模式
if (isHintMode) {
onAddClick();
return true;
} else {
if (mAddRegion.contains((int) event.getX(), (int) event.getY())) {
onAddClick();
return true;
} else if (mDelRegion.contains((int) event.getX(), (int) event.getY())) {
onDelClick();
return true;
}
}
break;
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
break;
}
return super.onTouchEvent(event);
}
private void onDelClick() {
if (mCount > 0) {
mCount--;
onCountDelListener();
if (null != mOnAddDelListener) {
mOnAddDelListener.onDelSuccess(mCount);
}
} else {
if (null != mOnAddDelListener) {
mOnAddDelListener.onDelFaild(mCount, onAddDelListener.FailType.COUNT_MIN);
}
}
}
private void onAddClick() {
if (mCount < mMaxCount) {
mCount++;
onCountAddListener();
if (null != mOnAddDelListener) {
mOnAddDelListener.onAddSuccess(mCount);
}
} else {
if (null != mOnAddDelListener) {
mOnAddDelListener.onAddFailed(mCount, onAddDelListener.FailType.COUNT_MAX);
}
}
}
private void onCountAddListener() {
if (mCount == 1) {
if (mAniDel != null && mAniDel.isRunning()) {
mAniDel.cancel();
}
mAnimReduceHint.start();
} else {
mAnimFraction = 0;
invalidate();
}
}
private void onCountDelListener() {
if (mCount == 0) {
if (mAnimAdd != null && mAnimAdd.isRunning()) {
mAnimAdd.cancel();
}
mAniDel.start();
} else {
mAnimFraction = 0;
invalidate();
}
}
}