package com.cheikh.lazywaimai.widget;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.app.Activity;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.Rect;
import android.support.v4.content.ContextCompat;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.Animation;
import android.view.animation.AnimationSet;
import android.view.animation.ScaleAnimation;
import android.view.animation.TranslateAnimation;
import android.widget.ImageView;
import android.widget.LinearLayout;
import java.util.ArrayList;
import java.util.Collection;
import com.cheikh.lazywaimai.R;
/**
* 购物车的加减号控件
*/
public class ShoppingCountView extends View {
private final static int DEFAULT_DURATION = 300;
private final static int DEFAULT_TEXT_SIZE_SP = 14;
private final static int DEFAULT_SPACE_DP = 5;
private final static int DEFAULT_TEXT_COLOR = Color.parseColor("#424242");
private final static int STATE_NONE_MINUS = 0;
private final static int STATE_PREP_DRAW_MINUS = 1;
private final static int STATE_STRETCH_MINUS = 2;
private final static int STATE_HAVE_MINUS = 3;
private final static int STATE_PREP_HIDE_MINUS = 4;
private Paint mCountPaint;
private Paint mAddBgPaint;
private Paint mAddTextPaint;
private Paint mMinusBgPaint;
private Paint mMinusTextPaint;
private int mState = STATE_NONE_MINUS;
private int mTextColor;
private int mTextSize;
private int mAddBtnBgColor;
private int mAddBtnTextColor;
private int mMinusBtnBgColor;
private int mMinusBtnTextColor;
private int mSpace;
private int mDuration;
private int mWidth;
private int mHeight;
private int mTextPosition;
private int mMinusBtnPosition;
private int mAngle;
private int mAlpha;
private boolean mIsExpand;
private int mShoppingCount;
private View mAnimTargetView;
private ShoppingClickListener mShoppingClickListener;
public ShoppingCountView(Context context) {
this(context, null, 0);
}
public ShoppingCountView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ShoppingCountView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray typeArray = getContext().obtainStyledAttributes(attrs, R.styleable.ShoppingCountView);
mTextColor = typeArray.getColor(R.styleable.ShoppingCountView_scv_text_color, DEFAULT_TEXT_COLOR);
mTextSize = (int) typeArray.getDimension(R.styleable.ShoppingCountView_scv_text_size, sp2px(DEFAULT_TEXT_SIZE_SP));
mAddBtnBgColor = typeArray.getColor(R.styleable.ShoppingCountView_scv_add_btn_bg_color,
ContextCompat.getColor(context, android.R.color.holo_blue_light));
mAddBtnTextColor = typeArray.getColor(R.styleable.ShoppingCountView_scv_add_btn_text_color,
ContextCompat.getColor(context, android.R.color.white));
mMinusBtnBgColor = typeArray.getColor(R.styleable.ShoppingCountView_scv_minus_btn_bg_color,
ContextCompat.getColor(context, android.R.color.white));
mMinusBtnTextColor = typeArray.getColor(R.styleable.ShoppingCountView_scv_minus_btn_text_color,
ContextCompat.getColor(context, android.R.color.holo_blue_light));
mSpace = (int) typeArray.getDimension(R.styleable.ShoppingCountView_scv_space, dp2px(DEFAULT_SPACE_DP));
mDuration = typeArray.getInt(R.styleable.ShoppingCountView_scv_duration, DEFAULT_DURATION);
typeArray.recycle();
initPaints();
}
/**
* 初始化各种画笔
*/
private void initPaints() {
// 绘制数量的画笔
mCountPaint = new Paint();
mCountPaint.setColor(mTextColor);
mCountPaint.setTextSize(mTextSize);
mCountPaint.setStrokeWidth(mTextSize / 6);
mCountPaint.setAntiAlias(true);
// 绘制加号按钮背景的画笔
mAddBgPaint = new Paint();
mAddBgPaint.setColor(mAddBtnBgColor);
mAddBgPaint.setStyle(Paint.Style.FILL);
mAddBgPaint.setAntiAlias(true);
// 绘制加号的画笔
mAddTextPaint = new Paint();
mAddTextPaint.setColor(mAddBtnTextColor);
mAddTextPaint.setStyle(Paint.Style.STROKE);
mAddTextPaint.setAntiAlias(true);
mAddTextPaint.setStrokeWidth(mTextSize / 6);
// 绘制减号按钮背景的画笔
mMinusBgPaint = new Paint();
mMinusBgPaint.setColor(mMinusBtnBgColor);
mMinusBgPaint.setStyle(Paint.Style.FILL);
mMinusBgPaint.setAntiAlias(true);
// 绘制减号的画笔
mMinusTextPaint = new Paint();
mMinusTextPaint.setColor(mMinusBtnTextColor);
mMinusTextPaint.setStyle(Paint.Style.STROKE);
mMinusTextPaint.setAntiAlias(true);
mMinusTextPaint.setStrokeWidth(mTextSize / 6);
}
/**
* 触摸点是否在加号按钮内
* @param event
* @return
*/
private boolean isClickAddBtn(MotionEvent event) {
PointF pointF = new PointF(event.getX(), event.getY());
PointF circle = new PointF(mWidth - mHeight / 2, mHeight / 2);
return Math.pow((pointF.x - circle.x), 2) + Math.pow((pointF.y - circle.y), 2) <= Math.pow(mHeight / 2, 2);
}
/**
* 触摸点是否在减号按钮内
* @param event
* @return
*/
private boolean isClickMinusBtn(MotionEvent event) {
PointF pointF = new PointF(event.getX(), event.getY());
PointF circle = new PointF(mHeight / 2, mHeight / 2);
return Math.pow((pointF.x - circle.x), 2) + Math.pow((pointF.y - circle.y), 2) <= Math.pow(mHeight / 2, 2);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (mState == STATE_HAVE_MINUS && isClickMinusBtn(event)) {
mShoppingCount--;
if (mShoppingClickListener != null) {
mShoppingClickListener.onMinusClick(mShoppingCount);
}
if (mShoppingCount == 0) {
mState = STATE_PREP_HIDE_MINUS;
mIsExpand = false;
}
requestLayout();
}
if (isClickAddBtn(event)) {
mShoppingCount++;
if (mShoppingClickListener != null) {
mShoppingClickListener.onAddClick(mShoppingCount);
}
if (mShoppingCount == 1) {
mState = STATE_PREP_DRAW_MINUS;
mIsExpand = true;
}
if (mAnimTargetView != null) {
startParabolaAnim(this, mAnimTargetView);
}
requestLayout();
}
break;
}
return super.onTouchEvent(event);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
mHeight = mTextSize / 2 * 3;
mWidth = mHeight * 2 + mSpace * 2 + getTextWidth(String.valueOf(mShoppingCount), mCountPaint);
setMeasuredDimension(mWidth, mHeight);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mState == STATE_NONE_MINUS) {
drawAddButton(canvas);
} else if (mState == STATE_PREP_DRAW_MINUS) {
mState = STATE_STRETCH_MINUS;
drawAddButton(canvas);
startStretchAnim();
} else if (mState == STATE_STRETCH_MINUS) {
mMinusBgPaint.setAlpha(mAlpha);
mMinusTextPaint.setAlpha(mAlpha);
mCountPaint.setAlpha(mAlpha);
drawAddButton(canvas);
drawMinusBtn(canvas);
drawCountText(canvas);
} else if (mState == STATE_HAVE_MINUS) {
mMinusBgPaint.setAlpha(255);
mMinusTextPaint.setAlpha(255);
mCountPaint.setAlpha(255);
drawAddButton(canvas);
drawMinusBtn(canvas);
drawCountText(canvas);
} else if (mState == STATE_PREP_HIDE_MINUS) {
mState = STATE_STRETCH_MINUS;
drawAddButton(canvas);
startStretchAnim();
}
}
/**
* 绘制加号按钮
* @param canvas
*/
private void drawAddButton(Canvas canvas) {
// 绘制加号的背景圆形
canvas.drawCircle(mWidth - mHeight / 2, mHeight / 2, mHeight / 2, mAddBgPaint);
// 绘制竖线
canvas.drawLine(mWidth - mHeight / 2, mHeight / 4, mWidth - mHeight / 2, mHeight / 4 * 3, mAddTextPaint);
// 绘制横线
canvas.drawLine(mWidth - mHeight / 2 - mHeight / 4, mHeight / 2, mWidth - mHeight / 4, mHeight / 2, mAddTextPaint);
}
/**
* 绘制选购数量的文本
* @param canvas
*/
private void drawCountText(Canvas canvas) {
if (mState == STATE_STRETCH_MINUS) {
if (mAngle != 0) {
canvas.rotate(mAngle, mTextPosition, mHeight / 2);
}
int x = (int) (mTextPosition - getTextWidth(String.valueOf(mShoppingCount), mCountPaint) / 2f);
int y = (int) (mHeight / 2 + getTextHeight(String.valueOf(mShoppingCount), mCountPaint) / 2f);
canvas.drawText(String.valueOf(mShoppingCount), x, y, mCountPaint);
if (mAngle != 0) {
canvas.rotate(-mAngle, x, y);
}
} else {
int x = (int) (mWidth / 2 - getTextWidth(String.valueOf(mShoppingCount), mCountPaint) / 2f);
int y = (int) (mHeight / 2 + getTextHeight(String.valueOf(mShoppingCount), mCountPaint) / 2f);
canvas.drawText(String.valueOf(mShoppingCount), x, y, mCountPaint);
}
}
/**
* 绘制减号按钮
* @param canvas
*/
private void drawMinusBtn(Canvas canvas) {
if (mState == STATE_STRETCH_MINUS) {
if (mAngle != 0) {
canvas.rotate(mAngle, mMinusBtnPosition, mHeight / 2);
}
canvas.drawCircle(mMinusBtnPosition, mHeight / 2, mHeight / 2, mMinusBgPaint);
mMinusTextPaint.setStrokeWidth(mTextSize / 10);
canvas.drawCircle(mMinusBtnPosition, mHeight / 2, mHeight / 2 - mHeight / 20, mMinusTextPaint);
mMinusTextPaint.setStrokeWidth(mTextSize / 6);
canvas.drawLine(mMinusBtnPosition - mHeight / 4, mHeight / 2, mMinusBtnPosition + mHeight / 4, mHeight / 2, mMinusTextPaint);
if (mAngle != 0) {
canvas.rotate(-mAngle, mMinusBtnPosition, mHeight / 2);
}
} else {
canvas.drawCircle(mHeight / 2, mHeight / 2, mHeight / 2, mMinusBgPaint);
mMinusTextPaint.setStrokeWidth(mTextSize / 10);
canvas.drawCircle(mHeight / 2, mHeight / 2, mHeight / 2 - mHeight / 20, mMinusTextPaint);
mMinusTextPaint.setStrokeWidth(mTextSize / 6);
canvas.drawLine(mHeight / 4, mHeight / 2, mHeight / 4 * 3, mHeight / 2, mMinusTextPaint);
}
}
/**
* 开始伸缩动画
*/
private void startStretchAnim() {
Collection<Animator> animatorList = new ArrayList<>();
// 旋转购物车数量和减号的动画
ValueAnimator animatorTextRotate;
if (mIsExpand) {
animatorTextRotate = ValueAnimator.ofInt(0, 360);
} else {
animatorTextRotate = ValueAnimator.ofInt(360, 0);
}
animatorTextRotate.setDuration(mDuration);
animatorTextRotate.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
mAngle = (Integer) valueAnimator.getAnimatedValue();
if (mIsExpand) {
if (mAngle == 360) {
mState = STATE_HAVE_MINUS;
}
} else {
if (mAngle == 0) {
mState = STATE_NONE_MINUS;
}
}
}
});
animatorList.add(animatorTextRotate);
// 渐变购物车数量和减号的动画
ValueAnimator animatorAlpha;
if (mIsExpand) {
animatorAlpha = ValueAnimator.ofInt(0, 255);
} else {
animatorAlpha = ValueAnimator.ofInt(255, 0);
}
animatorAlpha.setDuration(mDuration);
animatorAlpha.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
mAlpha = (Integer) valueAnimator.getAnimatedValue();
if (mIsExpand) {
if (mAlpha == 255) {
mState = STATE_HAVE_MINUS;
}
} else {
if (mAlpha == 0) {
mState = STATE_NONE_MINUS;
}
}
}
});
animatorList.add(animatorAlpha);
// 购物车数量移动的动画
ValueAnimator animatorTextMove;
if (mIsExpand) {
animatorTextMove = ValueAnimator.ofInt(mWidth - mHeight / 2, mWidth / 2);
} else {
animatorTextMove = ValueAnimator.ofInt(mWidth / 2, mWidth - mHeight / 2);
}
animatorTextMove.setDuration(mDuration);
animatorTextMove.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
mTextPosition = (Integer) valueAnimator.getAnimatedValue();
if (mIsExpand) {
if (mTextPosition == mWidth / 2) {
mState = STATE_HAVE_MINUS;
}
} else {
if (mTextPosition == mWidth - mHeight / 2) {
mState = STATE_NONE_MINUS;
}
}
}
});
animatorList.add(animatorTextMove);
// 减号移动的动画
ValueAnimator animatorBtnMove;
if (mIsExpand) {
animatorBtnMove = ValueAnimator.ofInt(mWidth - mHeight / 2, mHeight / 2);
} else {
animatorBtnMove = ValueAnimator.ofInt(mHeight / 2, mWidth - mHeight / 2);
}
animatorBtnMove.setDuration(mDuration);
animatorBtnMove.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
mMinusBtnPosition = (Integer) valueAnimator.getAnimatedValue();
if (mIsExpand) {
if (mMinusBtnPosition == mHeight / 2) {
mState = STATE_HAVE_MINUS;
}
} else {
if (mMinusBtnPosition == mWidth - mHeight / 2) {
mState = STATE_NONE_MINUS;
}
}
requestLayout();
}
});
animatorList.add(animatorBtnMove);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.setDuration(mDuration);
animatorSet.playTogether(animatorList);
animatorSet.start();
}
/**
* 启动加入到购物车的动画
*/
public void startParabolaAnim(View startView, View endView) {
// 创建一个覆盖在界面上的动画图层并生成对话对象
final ViewGroup animLayout = createAnimLayer();
if (animLayout == null) {
return;
}
final View animView = createAnimView(startView);
animLayout.addView(animView);
// 设置动画对象的初始位置
int[] animStartLoc = getViewLocation(startView);
setAnimStartLoc(animStartLoc, animView);
// 获取动画对象的终点位置
int[] animEndLoc = getViewLocation(endView);
// 根据起始位置和终点位置创建动画的轨迹
int[] offset = getAnimOffset(animStartLoc, animEndLoc);
final Animation animation = buildAnimation(offset);
// 设置动画的执行监听器
animation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationRepeat(Animation animation) {
}
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
// 将动画图层移除
final ViewGroup parent = (ViewGroup) animLayout.getParent();
parent.post(new Runnable() {
@Override
public void run() {
parent.removeView(animLayout);
}
});
}
});
animView.startAnimation(animation);
}
/**
* 创建动画层
*
* @return
*/
private ViewGroup createAnimLayer() {
Context context = getContext();
if (context instanceof Activity) {
final ViewGroup decorView = (ViewGroup) ((Activity) context).getWindow().getDecorView();
LinearLayout ll = new LinearLayout(getContext());
ll.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.MATCH_PARENT));
ll.setBackgroundResource(android.R.color.transparent);
decorView.addView(ll);
return ll;
}
return null;
}
/**
* 创建动画对象
*
* @param v
* @return
*/
private View createAnimView(View v) {
v.buildDrawingCache();
Bitmap animBitmap = v.getDrawingCache();
ImageView ivAnim = new ImageView(getContext());
ivAnim.setImageBitmap(animBitmap);
return ivAnim;
}
/**
* 获得动画开始位置,即价格视图所在位置
*
* @param v
* @return
*/
private int[] getViewLocation(View v) {
int[] location = new int[2];
v.getLocationOnScreen(location);
return location;
}
/**
* 设置动画起始位置
*
* @param startLoc
* @param animView
*/
private void setAnimStartLoc(int[] startLoc, View animView) {
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
lp.leftMargin = startLoc[0];
lp.topMargin = startLoc[1];
animView.setLayoutParams(lp);
}
/**
* 获得动画x,y坐标的偏移量
*
* @param startLoc
* @param endLoc
* @return
*/
private int[] getAnimOffset(int[] startLoc, int[] endLoc) {
int[] offset = new int[2];
offset[0] = endLoc[0] - startLoc[0];
offset[1] = endLoc[1] - startLoc[1];
return offset;
}
/**
* 根据偏移量创建动画
*
* @param offset
* @return
*/
private Animation buildAnimation(int[] offset) {
AnimationSet as = new AnimationSet(false);
TranslateAnimation translateX = new TranslateAnimation(0, offset[0], 0, 0);
TranslateAnimation translateY = new TranslateAnimation(0, 0, 0, offset[1]);
translateY.setInterpolator(new AccelerateInterpolator());
ScaleAnimation scaleAnimation = new ScaleAnimation(1, 0, 1, 0);
scaleAnimation.setInterpolator(new AccelerateInterpolator());
as.addAnimation(scaleAnimation);
as.addAnimation(translateX);
as.addAnimation(translateY);
as.setDuration(500);
return as;
}
private int sp2px(float spValue) {
final float fontScale = getContext().getResources().getDisplayMetrics().scaledDensity;
return (int) (spValue * fontScale + 0.5f);
}
private int dp2px(float dpValue) {
float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
dpValue, getContext().getResources().getDisplayMetrics());
return (int) px;
}
private int getTextWidth(String str, Paint paint) {
int iRet = 0;
if (str != null && str.length() > 0) {
int len = str.length();
float[] widths = new float[len];
paint.getTextWidths(str, widths);
for (int j = 0; j < len; j++) {
iRet += (int) Math.ceil(widths[j]);
}
}
return iRet;
}
private int getTextHeight(String str, Paint paint) {
Rect rect = new Rect();
paint.getTextBounds(str, 0, str.length(), rect);
return (int) (rect.height() / 33f * 29);
}
/////////////////////////////////////////////////////
/// 以下是暴露给外部的公共方法 ///
/////////////////////////////////////////////////////
/**
* 设置购买数量
* @param shoppingCount
*/
public void setShoppingCount(int shoppingCount) {
shoppingCount = shoppingCount > 0 ? shoppingCount : 0;
mShoppingCount = shoppingCount;
if (shoppingCount > 0) {
mState = STATE_HAVE_MINUS;
} else {
mState = STATE_NONE_MINUS;
}
requestLayout();
}
/**
* 设置加入到购物车动画的目标视图
* @param animTargetView
*/
public void setAnimTargetView(View animTargetView) {
mAnimTargetView = animTargetView;
}
/**
* 获取加入到购物车动画的目标视图
* @param animTargetView
*/
public View getAnimTargetView(View animTargetView) {
return mAnimTargetView;
}
/**
* 获取购物车数量
* @return
*/
public int getShoppingCount() {
return mShoppingCount;
}
/**
* 设置监听器
* @param shoppingClickListener
*/
public void setOnShoppingClickListener(ShoppingClickListener shoppingClickListener) {
mShoppingClickListener = shoppingClickListener;
}
public interface ShoppingClickListener {
void onAddClick(int num);
void onMinusClick(int num);
}
}