自定义购物车控件,使用起来就是这么丝滑

本购物车自定义控件包括了购物车的抛物线动画效果,根据需求可设置是否需要。本控件自定义view,详细实现,代码注释较详细,请参考注释。

首先过下效果图


按照自定义控件三部曲,首先创建attr属性文件,定义相关属性。

res/values/attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="ShopCartView">
        <!--加按钮是否开启fill模式 默认是stroke(false)-->
        <attr name="isAddFillMode" format="boolean"/>
        <!--加按钮的背景色前景色-->
        <attr name="addEnableBgColor" format="color"/>
        <attr name="addEnableFgColor" format="color"/>
        <!--加按钮不可用时的背景色前景色-->
        <attr name="addDisableBgColor" format="color"/>
        <attr name="addDisableFgColor" format="color"/>

        <!--减按钮是否开启fill模式 默认是stroke(false)-->
        <attr name="isDelFillMode" format="boolean"/>
        <!--减按钮的背景色前景色-->
        <attr name="delEnableBgColor" format="color"/>
        <attr name="delEnableFgColor" format="color"/>
        <!--减按钮不可用时的背景色前景色-->
        <attr name="delDisableBgColor" format="color"/>
        <attr name="delDisableFgColor" format="color"/>

        <!--圆的半径-->
        <attr name="radius" format="dimension"/>
        <!--圆圈的宽度-->
        <attr name="circleStrokeWidth" format="dimension"/>
        <!--线(+ - 符号)的宽度-->
        <attr name="lineWidth" format="dimension"/>

        <!--两个圆之间的间距-->
        <attr name="gapBetweenCircle" format="dimension"/>
        <!--绘制数量的textSize-->
        <attr name="numTextSize" format="dimension"/>
        <!--最大数量和当前数量-->
        <attr name="maxCount" format="integer"/>
        <attr name="count" format="integer"/>

        <!-- 增加一个开关 ignoreHintArea:UI显示、动画是否忽略hint收缩区域-->
        <attr name="ignoreHintArea" format="boolean"/>

        <!--数量为0时,hint文字 背景色前景色 大小,圆角值-->
        <attr name="hintText" format="string"/>
        <attr name="hintBgColor" format="color"/>
        <attr name="hintFgColor" format="color"/>
        <attr name="hintTextSize" format="dimension"/>
        <attr name="hintBgRoundValue" format="dimension"/>

        <attr name="perAnimDuration" format="integer"/>

        <!--in replenish 补货-->
        <attr name="replenishTextColor" format="color"/>
        <attr name="replenishTextSize" format="dimension"/>
        <attr name="replenishText" format="string"/>
    </declare-styleable>
</resources>
自定义View代码

ShopCartView.java

package androidbus.com.shopcartlib;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.app.Activity;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull;
import android.util.AttributeSet;
import android.util.Log;
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.LinearInterpolator;
import android.widget.ImageView;
import android.widget.LinearLayout;

/*
 *  @项目名:  ShopCartViewLib 
 *  @包名:    androidbus.com.shopcartlib
 *  @文件名:   ShopCartView
 *  @创建者:   Administrator
 *  @创建时间:  2017/4/17 0017 上午 9:46
 *  @描述:    TODO
 */
public class ShopCartView extends View {
    private static final String TAG = "ShopCartView";
    private boolean mIsAddFillMode;
    private int mAddEnableBgColor;
    private int mAddEnableFgColor;
    private int mAddDisableBgColor;
    private int mAddDisableFgColor;
    private boolean mIsDelFillMode;
    private int mDelEnableBgColor;
    private int mDelEnableFgColor;
    private int mDelDisableBgColor;
    private int mDelDisableFgColor;
    private float mRadius;
    private float mCircleStrokeWidth;
    private float mLineWidth;
    private float mGapBetweenCircle;
    private float mNumTextSize;
    private int mMaxCount;
    private int mCount;
    /**
     * 是否需要显示hint提示文本
     */
    private boolean mIgnoreHintArea;
    private String mHintText;
    private int mHintBgColor;
    private int mHintFgColor;
    private float mHintTextSize;
    private float mHintBgRoundValue;
    private int mPerAnimDuration;
    private int mReplenishTextColor;
    private float mReplenishTextSize;
    private String mReplenishText;
    private Paint mHintPaint;
    private Paint mHintTextPaint;
    private Paint mAddPaint;
    private Paint mDelPaint;
    private Paint mNumPaint;
    private Region mAddRegion;
    private Region mDelRegion;
    private Path mAddPath;
    private Path mDelPath;
    private float mDelAnimatorFraction;
    private float mHintAnimatorFraction;
    private boolean showHintMode;
    private boolean showHintText;
    private ValueAnimator mDelExpandAnim;
    private ValueAnimator mHintExpandAnim;
    private ValueAnimator mHintClospAnim;
    private ValueAnimator mDelClospAnim;
    private int mWidth;
    private int mLeft;
    private int mHeight;
    private int mTop;
    private boolean isReplenishText = false;
    private Paint mReplenshTextPaint;
    private Rect mHintTextBound;
    private Rect mReplenshTextBound;
    private RectF mRectF;
    private Region mRegion;
    private OnAddOrDelListner mOnAddOrDelListner;
    private boolean isBind;
    private Context context;
    private View startView;
    private View endView;

    public ShopCartView(Context context) {
	this(context, null);
    }

    public ShopCartView(Context context, AttributeSet attrs) {
	this(context, attrs, 0);
    }

    public ShopCartView(Context context, AttributeSet attrs, int defStyleAttr) {
	super(context, attrs, defStyleAttr);
	//初始化默认属性
	initDefaultAttrs(context);
	//获取资源属性
	init(context, attrs, defStyleAttr);
	//初始化画笔
	initPaint();
	//先暂停所有动画
	cancelAnim();
	//根据当前数量初始化UI
	initUIByCount();
    }

    private void initUIByCount() {
	if (mCount == 0) {
	    showHintMode = true;
	    showHintText = true;
	    mHintAnimatorFraction = 0;
	    mDelAnimatorFraction = 1;
	} else {
	    showHintMode = false;
	    showHintText = false;
	    mHintAnimatorFraction = 1;
	    mDelAnimatorFraction = 0;
	}
    }

    private void cancelAnim() {
	if (mHintExpandAnim != null && mHintExpandAnim.isRunning()) {
	    mHintExpandAnim.cancel();
	}

	if (mHintClospAnim != null && mHintClospAnim.isRunning()) {
	    mHintClospAnim.cancel();
	}

	if (mDelExpandAnim != null && mDelExpandAnim.isRunning()) {
	    mDelExpandAnim.cancel();
	}

	if (mDelClospAnim != null && mDelClospAnim.isRunning()) {
	    mDelClospAnim.cancel();
	}
    }

    private void initPaint() {
	mHintPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
	mHintPaint.setStyle(Paint.Style.FILL);

	mHintTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
	mHintTextPaint.setStyle(Paint.Style.FILL);
	mHintTextPaint.setTextSize(mHintTextSize);
	mHintTextPaint.setColor(mHintFgColor);

	mReplenshTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
	mReplenshTextPaint.setStyle(Paint.Style.FILL);
	mReplenshTextPaint.setTextSize(mReplenishTextSize);
	mReplenshTextPaint.setColor(mReplenishTextColor);

	mAddPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
	if (mIsAddFillMode) {
	    mAddPaint.setStyle(Paint.Style.FILL);
	} else {
	    mAddPaint.setStyle(Paint.Style.STROKE);
	}

	mDelPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
	if (mIsDelFillMode) {
	    mDelPaint.setStyle(Paint.Style.FILL);
	} else {
	    mDelPaint.setStyle(Paint.Style.STROKE);
	}

	mNumPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
	mNumPaint.setStyle(Paint.Style.FILL);
	mNumPaint.setTextSize(mNumTextSize);
	mNumPaint.setColor(mReplenishTextColor);

	mAddRegion = new Region();
	mDelRegion = new Region();
	mAddPath = new Path();
	mDelPath = new Path();

	//初始化动画属性
	//减少按钮的伸展动画
	mDelExpandAnim = ValueAnimator.ofFloat(1, 0);
	mDelExpandAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
	    @Override
	    public void onAnimationUpdate(ValueAnimator valueAnimator) {
		mDelAnimatorFraction = (float) valueAnimator.getAnimatedValue();
		invalidate();
	    }
	});
	mDelExpandAnim.setDuration(mPerAnimDuration);
	mDelExpandAnim.addListener(new AnimatorListenerAdapter() {
	    @Override
	    public void onAnimationEnd(Animator animation) {
		super.onAnimationEnd(animation);
	    }
	});

	//提示语扩展动画
	mHintExpandAnim = ValueAnimator.ofFloat(1, 0);
	mHintExpandAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
	    @Override
	    public void onAnimationUpdate(ValueAnimator valueAnimator) {
		mHintAnimatorFraction = (float) valueAnimator.getAnimatedValue();
		invalidate();
	    }
	});
	mHintExpandAnim.setDuration(mIgnoreHintArea
				    ? 0
				    : mPerAnimDuration);
	mHintExpandAnim.addListener(new AnimatorListenerAdapter() {
	    @Override
	    public void onAnimationStart(Animator animation) {
		//提示语扩展动画开始时要显示提示语背景
		if (mCount == 0) {
		    showHintMode = true;
		}
	    }

	    @Override
	    public void onAnimationEnd(Animator animation) {
		//提示语扩展动画结束时要显示提示语文本
		if (mCount == 0) {
		    showHintText = true;
		}
	    }
	});

	//提示语收缩动画
	mHintClospAnim = ValueAnimator.ofFloat(0, 1);
	mHintClospAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
	    @Override
	    public void onAnimationUpdate(ValueAnimator valueAnimator) {
		mHintAnimatorFraction = (float) valueAnimator.getAnimatedValue();
		invalidate();
	    }
	});
	mHintClospAnim.setDuration(mPerAnimDuration);
	mHintClospAnim.addListener(new AnimatorListenerAdapter() {
	    @Override
	    public void onAnimationStart(Animator animation) {
		//提示语收缩动画开始时先隐藏提示语文本
		if (mCount > 0) {
		    showHintText = false;
		}
	    }

	    @Override
	    public void onAnimationEnd(Animator animation) {
		//提示语收缩动画结束时先隐藏提示语背景
		if (mCount > 0) {
		    showHintMode = false;
		    //提示语收缩动画结束时开启减少按钮扩展动画
		    if (mDelExpandAnim != null && !mDelExpandAnim.isRunning()) {
			mDelExpandAnim.start();
		    }
		}
	    }
	});


	//减少按钮的收缩动画
	mDelClospAnim = ValueAnimator.ofFloat(0, 1);
	mDelClospAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
	    @Override
	    public void onAnimationUpdate(ValueAnimator valueAnimator) {
		mDelAnimatorFraction = (float) valueAnimator.getAnimatedValue();
		invalidate();
	    }
	});
	mDelClospAnim.setDuration(mPerAnimDuration);
	mDelClospAnim.addListener(new AnimatorListenerAdapter() {
	    @Override
	    public void onAnimationEnd(Animator animation) {
		//减少按钮的收缩动画结束后开启提示语扩展动画
		if (mCount == 0) {
		    if (mHintExpandAnim != null && !mHintExpandAnim.isRunning()) {
			mHintExpandAnim.start();
		    }
		}
	    }
	});

	mHintTextBound = new Rect();
	mHintTextPaint.getTextBounds(mHintText, 0, mHintText.length(), mHintTextBound);
	mReplenshTextBound = new Rect();
	mReplenshTextPaint.getTextBounds(mReplenishText, 0, mReplenishText
		.length(), mReplenshTextBound);
    }

    private void init(Context context, AttributeSet attrs, int defStyleAttr) {
	TypedArray ta = context.getTheme()
			       .obtainStyledAttributes(attrs, R.styleable.ShopCartView, defStyleAttr, 0);
	int indexCount = ta.getIndexCount();

	for (int i = 0; i < indexCount; i++) {
	    int attr = ta.getIndex(i);
	    if (attr == R.styleable.ShopCartView_isAddFillMode) {
		mIsAddFillMode = ta.getBoolean(attr, mIsAddFillMode);
	    } else if (attr == R.styleable.ShopCartView_addEnableBgColor) {
		mAddEnableBgColor = ta.getColor(attr, mAddEnableBgColor);
	    } else if (attr == R.styleable.ShopCartView_addEnableFgColor) {
		mAddEnableFgColor = ta.getColor(attr, mAddEnableFgColor);
	    } else if (attr == R.styleable.ShopCartView_addDisableBgColor) {
		mAddDisableBgColor = ta.getColor(attr, mAddDisableBgColor);
	    } else if (attr == R.styleable.ShopCartView_addDisableFgColor) {
		mAddDisableFgColor = ta.getColor(attr, mAddDisableFgColor);
	    } else if (attr == R.styleable.ShopCartView_isDelFillMode) {
		mIsDelFillMode = ta.getBoolean(attr, mIsDelFillMode);
	    } else if (attr == R.styleable.ShopCartView_delEnableBgColor) {
		mDelEnableBgColor = ta.getColor(attr, mDelEnableBgColor);
	    } else if (attr == R.styleable.ShopCartView_delEnableFgColor) {
		mDelEnableFgColor = ta.getColor(attr, mDelEnableFgColor);
	    } else if (attr == R.styleable.ShopCartView_delDisableBgColor) {
		mDelDisableBgColor = ta.getColor(attr, mDelDisableBgColor);
	    } else if (attr == R.styleable.ShopCartView_delDisableFgColor) {
		mDelDisableFgColor = ta.getColor(attr, mDelDisableFgColor);
	    } else if (attr == R.styleable.ShopCartView_radius) {
		mRadius = ta.getDimension(attr, mRadius);
	    } else if (attr == R.styleable.ShopCartView_circleStrokeWidth) {
		mCircleStrokeWidth = ta.getDimension(attr, mCircleStrokeWidth);
	    } else if (attr == R.styleable.ShopCartView_lineWidth) {
		mLineWidth = ta.getDimension(attr, mLineWidth);
	    } else if (attr == R.styleable.ShopCartView_gapBetweenCircle) {
		mGapBetweenCircle = ta.getDimension(attr, mGapBetweenCircle);
	    } else if (attr == R.styleable.ShopCartView_numTextSize) {
		mNumTextSize = ta.getDimension(attr, mNumTextSize);
	    } else if (attr == R.styleable.ShopCartView_maxCount) {
		mMaxCount = ta.getInt(attr, mMaxCount);
	    } else if (attr == R.styleable.ShopCartView_count) {
		mCount = ta.getInt(attr, mCount);
	    } else if (attr == R.styleable.ShopCartView_ignoreHintArea) {
		mIgnoreHintArea = ta.getBoolean(attr, mIgnoreHintArea);
	    } else if (attr == R.styleable.ShopCartView_hintText) {
		mHintText = ta.getString(attr);
	    } else if (attr == R.styleable.ShopCartView_hintBgColor) {
		mHintBgColor = ta.getColor(attr, mHintBgColor);
	    } else if (attr == R.styleable.ShopCartView_hintFgColor) {
		mHintFgColor = ta.getColor(attr, mHintFgColor);
	    } else if (attr == R.styleable.ShopCartView_hintTextSize) {
		mHintTextSize = ta.getDimension(attr, mHintTextSize);
	    } else if (attr == R.styleable.ShopCartView_hintBgRoundValue) {
		mHintBgRoundValue = ta.getDimension(attr, mHintBgRoundValue);
	    } else if (attr == R.styleable.ShopCartView_perAnimDuration) {
		mPerAnimDuration = ta.getInt(attr, mPerAnimDuration);
	    } else if (attr == R.styleable.ShopCartView_replenishTextColor) {
		mReplenishTextColor = ta.getColor(attr, mReplenishTextColor);
	    } else if (attr == R.styleable.ShopCartView_replenishTextSize) {
		mReplenishTextSize = ta.getDimension(attr, mReplenishTextSize);
	    } else if (attr == R.styleable.ShopCartView_replenishText) {
		mReplenishText = ta.getString(attr);
	    }
	}
	ta.recycle();
    }

    private void initDefaultAttrs(Context context) {
	mIsAddFillMode = true; //默认添加按钮为fill模式
	mAddEnableBgColor = Color.CYAN;
	mAddEnableFgColor = Color.WHITE;
	mAddDisableBgColor = Color.GRAY;
	mAddDisableFgColor = Color.WHITE;
	mIsDelFillMode = false;
	mDelEnableBgColor = Color.GRAY;
	mDelEnableFgColor = Color.GRAY;
	mDelDisableBgColor = Color.GRAY;
	mDelDisableFgColor = Color.LTGRAY;
	mRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 12.5f, context
		.getResources().getDisplayMetrics());
	mCircleStrokeWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1f, context
		.getResources().getDisplayMetrics());
	mLineWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 2f, context
		.getResources().getDisplayMetrics());
	mGapBetweenCircle = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 34f, context
		.getResources().getDisplayMetrics());
	mNumTextSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 14f, context
		.getResources().getDisplayMetrics());
	mMaxCount = 100;
	mCount = 1;
	mIgnoreHintArea = false;
	mHintText = getResources().getString(R.string.str_hintText);
	mHintBgColor = Color.CYAN;
	mHintFgColor = Color.WHITE;
	mHintTextSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 14f, context
		.getResources().getDisplayMetrics());
	mHintBgRoundValue = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5f, context
		.getResources().getDisplayMetrics());
	mPerAnimDuration = 500;
	mReplenishTextColor = Color.BLACK;
	mReplenishTextSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 14f, context
		.getResources().getDisplayMetrics());
	mReplenishText = getResources().getString(R.string.str_replenishText);
    }

    private Region getRegion() {
	if (mRegion == null) {
	    mRegion = new Region();
	}
	mRegion.set(mLeft, mTop, mWidth - getPaddingRight(), mHeight - getPaddingBottom());
	return mRegion;
    }

    @NonNull
    private RectF getRectF() {
	if (mRectF == null) {
	    mRectF = new RectF();
	}
	mRectF.left = mLeft + (mWidth - mRadius * 2 - mCircleStrokeWidth * 2) * mHintAnimatorFraction;
	mRectF.top = mTop;
	mRectF.right = mLeft + mWidth;
	mRectF.bottom = mTop + mHeight;
	return mRectF;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
	int action = event.getAction();
	float x = event.getX();
	float y = event.getY();
	if (isReplenishText) {
	    return true;
	}

	if (mHintExpandAnim.isRunning() || mHintClospAnim.isRunning() || mDelExpandAnim
		.isRunning() || mDelClospAnim.isRunning())
	{
	    //如果当前有动画在运行就不能有其他操作
	    return true;
	}

	switch (action) {
	    case MotionEvent.ACTION_DOWN:
		if (mCount == 0) {
		    addClick();
		    return true;
		} else {

		    if (mAddRegion.contains((int) x, (int) y)) {
			addClick();

			return true;
		    } else if (mDelRegion.contains((int) x, (int) y)) {
			delClick();
			return true;
		    }
		}
		break;
	    case MotionEvent.ACTION_MOVE:
		break;
	    case MotionEvent.ACTION_UP:
		break;
	}
	return super.onTouchEvent(event);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
	super.onSizeChanged(w, h, oldw, oldh);
	mWidth = w;
	mLeft = getPaddingLeft();
	mHeight = h;
	mTop = getPaddingTop();
    }

    @Override
    protected void onDraw(Canvas canvas) {
	if (isReplenishText) {
	    //计算"补货中"文本的起终点
	    int startX = mWidth / 2 - mReplenshTextBound.width() / 2;
	    int startY = (int) (mHeight / 2 - (mReplenshTextPaint.ascent() + mReplenshTextPaint
		    .descent()) / 2);
	    //绘制"补货中"文本
	    canvas.drawText(mReplenishText, startX, startY, mReplenshTextPaint);
	    return;
	}

	if (!mIgnoreHintArea && showHintMode) {
	    //绘制提示语背景
	    mHintPaint.setColor(mHintBgColor);
	    RectF rectF = getRectF();
	    canvas.drawRoundRect(rectF, mHintBgRoundValue, mHintBgRoundValue, mHintPaint);
	    //绘制提示语文本
	    if (showHintText) {
		int startX = mWidth / 2 - mHintTextBound.width() / 2;
		int startY = (int) (mHeight / 2 - (mHintTextPaint.ascent() + mHintTextPaint
			.descent()) / 2);
		canvas.drawText(mHintText, startX, startY, mHintTextPaint);
	    }
	} else {
	    //绘制加减按钮
	    //获取按钮动画的最大位移距离
	    int animOffsetMax = (int) (mRadius * 2 + mCircleStrokeWidth * 2 + mGapBetweenCircle);
	    int alphaMax = 255;
	    int animRotateMax = 360;

	    //绘制删除按钮
	    if (mCount > 0) {
		mDelPaint.setColor(mDelEnableBgColor);
	    } else {
		mDelPaint.setColor(mDelDisableBgColor);
	    }
	    mDelPaint.setStrokeWidth(mCircleStrokeWidth);
	    mDelPaint.setAlpha((int) (alphaMax * (1 - mDelAnimatorFraction)));
	    mDelPath.reset();
	    canvas.save();
	    //删除按钮动画
	    canvas.translate(animOffsetMax * mDelAnimatorFraction + mLeft, 0);
	    canvas.rotate((int) (animRotateMax * (1 - mDelAnimatorFraction)), mCircleStrokeWidth + mRadius, mTop + mCircleStrokeWidth + mRadius);
	    mDelPath.addCircle(mLeft + mRadius + mCircleStrokeWidth, mTop + mRadius + mCircleStrokeWidth, mRadius, Path.Direction.CW);
	    mDelRegion.setPath(mDelPath, getRegion());
	    canvas.drawPath(mDelPath, mDelPaint);
	    if (mCount > 0) {
		mDelPaint.setColor(mDelEnableFgColor);
	    } else {
		mDelPaint.setColor(mDelDisableFgColor);
	    }
	    mDelPaint.setStrokeWidth(mLineWidth);
	    //绘制删除按钮中的横线
	    canvas.drawLine(mLeft + mCircleStrokeWidth + mRadius / 2, mTop + mCircleStrokeWidth + mRadius, mLeft + mCircleStrokeWidth + mRadius / 2 + mRadius, mTop + mCircleStrokeWidth + mRadius, mDelPaint);
	    canvas.restore();

	    //绘制数量文本
	    //获取数量文本的最大位移距离
	    mNumPaint.setAlpha((int) (alphaMax * (1 - mDelAnimatorFraction)));
	    canvas.save();

	    canvas.translate((animOffsetMax - mRadius * 2 - mCircleStrokeWidth * 2 - mGapBetweenCircle / 2 + mNumPaint
		    .measureText(mCount + "") / 2) * mDelAnimatorFraction + mLeft + mRadius * 2 + mCircleStrokeWidth * 2 + mGapBetweenCircle / 2 - mNumPaint
		    .measureText(mCount + "") / 2, 0);
	    canvas.rotate((int) (animRotateMax * (1 - mDelAnimatorFraction)), mNumPaint
		    .measureText(mCount + "") / 2, mHeight / 2);
	    canvas.drawText(mCount + "", 0, mHeight / 2 - (mNumPaint.ascent() + mNumPaint
		    .descent()) / 2, mNumPaint);
	    canvas.restore();

	    //绘制添加按钮
	    if (mCount < 100) {
		mAddPaint.setColor(mAddEnableBgColor);
	    } else {
		mAddPaint.setColor(mAddDisableBgColor);
	    }
	    mAddPaint.setStrokeWidth(mCircleStrokeWidth);
	    int left = (int) (mLeft + animOffsetMax + mCircleStrokeWidth);
	    mAddPath.reset();
	    mAddPath.addCircle(left + mRadius, mTop + mCircleStrokeWidth + mRadius, mRadius, Path.Direction.CW);
	    mAddRegion.setPath(mAddPath, getRegion());
	    canvas.drawPath(mAddPath, mAddPaint);

	    //绘制添加按钮间的“+”
	    if (mCount < 100) {
		mAddPaint.setColor(mAddEnableFgColor);
	    } else {
		mAddPaint.setColor(mAddDisableFgColor);
	    }
	    mAddPaint.setStrokeWidth(mLineWidth);
	    canvas.drawLine(left + mRadius / 2, mTop + mCircleStrokeWidth + mRadius, left + mRadius / 2 + mRadius, mTop + mCircleStrokeWidth + mRadius, mAddPaint);
	    canvas.drawLine(left + mRadius, mTop + mCircleStrokeWidth + mRadius / 2, left + mRadius, mTop + mCircleStrokeWidth + mRadius / 2 + mRadius, mAddPaint);
	}
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
	int width = MeasureSpec.getSize(widthMeasureSpec);
	int widthMode = MeasureSpec.getMode(widthMeasureSpec);
	int height = MeasureSpec.getSize(heightMeasureSpec);
	int heightMode = MeasureSpec.getMode(heightMeasureSpec);

	switch (widthMode) {
	    case MeasureSpec.EXACTLY:
		break;
	    case MeasureSpec.AT_MOST:
		int defaultWidth = (int) (getPaddingLeft() + mRadius * 2 + mGapBetweenCircle + mRadius * 2 + mCircleStrokeWidth * 4 + getPaddingRight());
		width = defaultWidth < width
			? defaultWidth
			: width;
		break;
	    case MeasureSpec.UNSPECIFIED:
		defaultWidth = (int) (getPaddingLeft() + mRadius * 2 + mGapBetweenCircle + mRadius * 2 + mCircleStrokeWidth * 4 + getPaddingRight());
		width = defaultWidth;
		break;
	}

	switch (heightMode) {
	    case MeasureSpec.EXACTLY:
		break;
	    case MeasureSpec.AT_MOST:
		int defaultHeight = (int) (getPaddingTop() + mRadius * 2 + mCircleStrokeWidth * 2 + getPaddingBottom());
		height = defaultHeight < height
			 ? defaultHeight
			 : height;
		break;
	    case MeasureSpec.UNSPECIFIED:
		defaultHeight = (int) (getPaddingTop() + mRadius * 2 + mCircleStrokeWidth * 2 + getPaddingBottom());
		width = defaultHeight;
		break;
	}

	setMeasuredDimension(width, height);
	//        cancelAnim();
	//        initUIByCount();
    }

    private void delClick() {
	if (mCount > 0) {
	    mCount--;
	    if (mCount == 0) {
		cancelAnim();
		if (mDelClospAnim != null && !mDelClospAnim.isRunning()) {
		    mDelClospAnim.start();
		}
	    } else {
		mDelAnimatorFraction = 0;
		invalidate();
	    }

	    if (mOnAddOrDelListner != null) {
		mOnAddOrDelListner.onDelClick(mCount);
	    }
	} else {

	}
    }

    private void addClick() {
	if (mCount < mMaxCount) {
	    mCount++;
	    if (mCount == 1) {
		cancelAnim();
		if (mHintClospAnim != null && !mHintClospAnim.isRunning()) {
		    mHintClospAnim.start();
		}
	    } else {
		mDelAnimatorFraction = 0;
		invalidate();
	    }

	    if (mOnAddOrDelListner != null) {
		mOnAddOrDelListner.onAddClick(mCount);
	    }

	    if (isBind) {
		bindToCartAnim();
	    }
	} else {

	}
	invalidate();
    }

    public boolean isAddFillMode() {
	return mIsAddFillMode;
    }

    public void setAddFillMode(boolean addFillMode) {
	mIsAddFillMode = addFillMode;
    }

    public int getAddEnableBgColor() {
	return mAddEnableBgColor;
    }

    public void setAddEnableBgColor(int addEnableBgColor) {
	mAddEnableBgColor = addEnableBgColor;
    }

    public int getAddEnableFgColor() {
	return mAddEnableFgColor;
    }

    public void setAddEnableFgColor(int addEnableFgColor) {
	mAddEnableFgColor = addEnableFgColor;
    }

    public int getAddDisableBgColor() {
	return mAddDisableBgColor;
    }

    public void setAddDisableBgColor(int addDisableBgColor) {
	mAddDisableBgColor = addDisableBgColor;
    }

    public int getAddDisableFgColor() {
	return mAddDisableFgColor;
    }

    public void setAddDisableFgColor(int addDisableFgColor) {
	mAddDisableFgColor = addDisableFgColor;
    }

    public boolean isDelFillMode() {
	return mIsDelFillMode;
    }

    public void setDelFillMode(boolean delFillMode) {
	mIsDelFillMode = delFillMode;
    }

    public int getDelEnableBgColor() {
	return mDelEnableBgColor;
    }

    public void setDelEnableBgColor(int delEnableBgColor) {
	mDelEnableBgColor = delEnableBgColor;
    }

    public int getDelEnableFgColor() {
	return mDelEnableFgColor;
    }

    public void setDelEnableFgColor(int delEnableFgColor) {
	mDelEnableFgColor = delEnableFgColor;
    }

    public int getDelDisableBgColor() {
	return mDelDisableBgColor;
    }

    public void setDelDisableBgColor(int delDisableBgColor) {
	mDelDisableBgColor = delDisableBgColor;
    }

    public int getDelDisableFgColor() {
	return mDelDisableFgColor;
    }

    public void setDelDisableFgColor(int delDisableFgColor) {
	mDelDisableFgColor = delDisableFgColor;
    }

    public float getRadius() {
	return mRadius;
    }

    public void setRadius(float radius) {
	mRadius = radius;
    }

    public float getCircleStrokeWidth() {
	return mCircleStrokeWidth;
    }

    public void setCircleStrokeWidth(float circleStrokeWidth) {
	mCircleStrokeWidth = circleStrokeWidth;
    }

    public float getLineWidth() {
	return mLineWidth;
    }

    public void setLineWidth(float lineWidth) {
	mLineWidth = lineWidth;
    }

    public float getGapBetweenCircle() {
	return mGapBetweenCircle;
    }

    public void setGapBetweenCircle(float gapBetweenCircle) {
	mGapBetweenCircle = gapBetweenCircle;
    }

    public float getNumTextSize() {
	return mNumTextSize;
    }

    public void setNumTextSize(float numTextSize) {
	mNumTextSize = numTextSize;
    }

    public int getMaxCount() {
	return mMaxCount;
    }

    public void setMaxCount(int maxCount) {
	mMaxCount = maxCount;
    }

    public int getCount() {
	return mCount;
    }

    public void setCount(int count) {
	mCount = count;
	cancelAnim();
	initUIByCount();
    }

    public boolean isIgnoreHintArea() {
	return mIgnoreHintArea;
    }

    public void setIgnoreHintArea(boolean ignoreHintArea) {
	mIgnoreHintArea = ignoreHintArea;
    }

    public String getHintText() {
	return mHintText;
    }

    public void setHintText(String hintText) {
	mHintText = hintText;
    }

    public int getHintBgColor() {
	return mHintBgColor;
    }

    public void setHintBgColor(int hintBgColor) {
	mHintBgColor = hintBgColor;
    }

    public int getHintFgColor() {
	return mHintFgColor;
    }

    public void setHintFgColor(int hintFgColor) {
	mHintFgColor = hintFgColor;
    }

    public float getHintTextSize() {
	return mHintTextSize;
    }

    public void setHintTextSize(float hintTextSize) {
	mHintTextSize = hintTextSize;
    }

    public float getHintBgRoundValue() {
	return mHintBgRoundValue;
    }

    public void setHintBgRoundValue(float hintBgRoundValue) {
	mHintBgRoundValue = hintBgRoundValue;
    }

    public int getPerAnimDuration() {
	return mPerAnimDuration;
    }

    public void setPerAnimDuration(int perAnimDuration) {
	mPerAnimDuration = perAnimDuration;
    }

    public int getReplenishTextColor() {
	return mReplenishTextColor;
    }

    public void setReplenishTextColor(int replenishTextColor) {
	mReplenishTextColor = replenishTextColor;
    }

    public float getReplenishTextSize() {
	return mReplenishTextSize;
    }

    public void setReplenishTextSize(float replenishTextSize) {
	mReplenishTextSize = replenishTextSize;
    }

    public String getReplenishText() {
	return mReplenishText;
    }

    public void setReplenishText(String replenishText) {
	mReplenishText = replenishText;
    }

    /**
     * 设置是否绑定购物车动画
     * @param isBind
     * @param context
     * @param startView
     * @param endView
     */

    public void setCartAnim(boolean isBind, Context context, @NonNull View startView,
			    @NonNull View endView) {
	this.isBind = isBind;
	this.context = context;
	this.startView = startView;
	this.endView = endView;
    }

    public void bindToCartAnim() {
	//创建购物车小球
	ImageView target = new ImageView(context);
	target.setImageResource(R.drawable.traint);
	//获取startView和endView在屏幕的坐标
	int startX = getLocation(startView, 0);
	int startY = getLocation(startView, 1);
	int endX = getLocation(endView, 0);
	int endY = getLocation(endView, 1);

	Drawable drawable = context.getResources().getDrawable(R.drawable.traint);
	int intrinsicWidth = 0;
	int intrinsicHeight = 0;
	if (drawable != null) {
	    intrinsicWidth = drawable.getIntrinsicWidth();
	    intrinsicHeight = drawable.getIntrinsicHeight();
	}

	if (startView instanceof ShopCartView) {
	    if (drawable != null) {
		startX = (int) (startX + mWidth - mCircleStrokeWidth - mRadius - intrinsicWidth / 2);
		startY = (int) (startY + mCircleStrokeWidth + mRadius - intrinsicHeight / 2);
	    }
	} else {
	    startX = startX + startView.getWidth() / 2 - intrinsicWidth / 2;
	    startY = startY + startView.getHeight() / 2 - intrinsicHeight / 2;
	}

	endX = endX + endView.getWidth() / 2 - intrinsicWidth / 2;
	endY = endY + endView.getHeight() / 2 - intrinsicHeight / 2;

	//将购物车小球加载入窗体相应位置
	traintImgToWindow(context, startX, startY, target);
	//执行购物车抛物线动画
	invokeAnim(target, startX, startY, endX, endY);
    }

    private void invokeAnim(final ImageView target, int startX, int startY, int endX, int endY) {
	Log.d("getLocation", startX + "," + startY + " " + endX + "," + endY);
	//抛物线动画分解为x轴匀速移动和y轴加速移动
	ObjectAnimator translateX = ObjectAnimator.ofFloat(target, "translationX", endX - startX);
	translateX.setInterpolator(new LinearInterpolator());

	ObjectAnimator translateY = ObjectAnimator.ofFloat(target, "translationY", endY - startY);
	translateY.setInterpolator(new AccelerateInterpolator());
	translateY.addListener(new AnimatorListenerAdapter() {
	    @Override
	    public void onAnimationStart(Animator animation) {
		//动画开始时显示小球
		target.setVisibility(VISIBLE);
	    }

	    @Override
	    public void onAnimationEnd(Animator animation) {
		//动画结束时隐藏小球
		target.setVisibility(GONE);
	    }
	});

	//缩放动画
	ObjectAnimator scaleX = ObjectAnimator.ofFloat(target, "scaleX", 1f, 0.4f);
	ObjectAnimator scaleY = ObjectAnimator.ofFloat(target, "scaleY", 1f, 0.4f);


	AnimatorSet animatorSet = new AnimatorSet();
	animatorSet.playTogether(translateX, translateY,scaleX,scaleY);
	animatorSet.setDuration(500);
	animatorSet.start();

    }

    /**
     * 将购物车小球添加屏幕相应位置
     * @param context
     * @param startX
     * @param startY
     * @param target
     */

    private void traintImgToWindow(Context context, int startX, int startY, ImageView target) {
	ViewGroup rootView = (ViewGroup) ((Activity) context).getWindow().getDecorView();
	LinearLayout layout = new LinearLayout(context);
	LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
	layout.setBackgroundColor(getResources().getColor(android.R.color.transparent));
	layout.setLayoutParams(layoutParams);
	rootView.addView(layout);

	LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
	params.leftMargin = startX;
	params.topMargin = startY;
	target.setLayoutParams(params);
	layout.addView(target);
    }

    /**
     * 获取控件在屏幕相应位置
     * @param target
     * @param i
     * @return
     */
    private int getLocation(View target, int i) {
	int[] location = new int[2];
	target.getLocationInWindow(location);
	return location[i];
    }

    public void setOnAddOrDelListner(OnAddOrDelListner onAddOrDelListner) {
	mOnAddOrDelListner = onAddOrDelListner;
    }

    public interface OnAddOrDelListner {
	void onAddClick(int count);

	void onDelClick(int count);
    }
}

附上代码中的Drawable文件 trant.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
       android:shape="oval">
    <corners android:radius="12.5dp"/>
    <size
        android:width="25dp"
        android:height="25dp"/>
    <solid android:color="@android:color/holo_red_dark"/>

</shape>

购物车自定义控件到这里就写完了,接下来看下如何使用

activity_main.xml布局文件

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="androidbus.com.shopcartviewlib.MainActivity">

    <Button
        android:layout_alignParentRight="true"
        android:id="@+id/endView"
        android:text="endView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>


    <androidbus.com.shopcartlib.ShopCartView
        android:id="@+id/scv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"/>
</RelativeLayout>

MainActivity.java代码

package androidbus.com.shopcartviewlib;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.Button;

import androidbus.com.shopcartlib.ShopCartView;

public class MainActivity extends AppCompatActivity implements ShopCartView.OnAddOrDelListner {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	setContentView(R.layout.activity_main);
	Button endView = (Button) findViewById(R.id.endView);
	ShopCartView scv = (ShopCartView) findViewById(R.id.scv);
	scv.setCount(6); //设置数量
	scv.setOnAddOrDelListner(this); //设置点击添加或减少按钮监听
	scv.setCartAnim(true,this,scv,endView);  //设置是否需要购物车的抛物线动画
	scv.setIgnoreHintArea(false);  //设置在数量为零时,是否需要显示提示文本
    }

    @Override
    public void onAddClick(int count) {
	//数量添加的回调
    }

    @Override
    public void onDelClick(int count) {
	//数量减少的回调
 }}

OK,如上,实现购物车效果只需几句代码,使用起来就是这么丝滑!代码传送门: https://github.com/ItStepMore/shopcartview/tree/master


  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值