自定义购物车控件,使用起来就是这么丝滑
2017-04-19 15:09
363 查看
本购物车自定义控件包括了购物车的抛物线动画效果,根据需求可设置是否需要。本控件自定义view,详细实现,代码注释较详细,请参考注释。
首先过下效果图
按照自定义控件三部曲,首先创建attr属性文件,定义相关属性。
res/values/attrs.xml
ShopCartView.java
附上代码中的Drawable文件 trant.xml
购物车自定义控件到这里就写完了,接下来看下如何使用
activity_main.xml布局文件
MainActivity.java代码
OK,如上,实现购物车效果只需几句代码,使用起来就是这么丝滑!代码传送门:https://github.com/ItStepMore/shopcartview/tree/master
首先过下效果图
按照自定义控件三部曲,首先创建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.isRunn ff89 ing()) { 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
相关文章推荐
- 用户控件的使用就是这么简单!
- ASP.NET中使用自定义验证控件(原作)
- 开发和使用自定义服务器控件
- 如何在ASP.NET中制作Web用户自定义控件,并在aspx页面中使用它的方法和属性?
- Visual studio.net 2003中使用自定义(用户)控件(服务器控件)的方法(原创)
- JS 动态增删元素 自定义验证控件的使用
- [转] 使用模板自定义 WPF 控件
- 开发和使用自定义服务器控件
- 开发和使用自定义服务器控件 (二)
- [转] 使用模板自定义 WPF 控件
- 使用WebBrowser控件时在网页元素上绘制文本或其他自定义内容
- 如何在aspx网页中使用.net编译后的自定义用户控件
- Control Study->AdRotator广告控件显示自定义配置文件中广告以及与DataGrid控件结合使用显示图片(示例代码下载)
- 大量的使用用户自定义控件对web性能有什么影响?
- 最简单的方法在DataGrid中自定义样式列(通过继承可以在Datagrid中使用下拉列表框,日期控件等)
- Control Study->AdRotator广告控件显示自定义配置文件中广告以及与DataGrid控件结合使用显示图片(示例代码下载)
- 使用SendMessage方法对窗体上的控件进行截图,该方法的思想就是把控件的句柄拿到,对控件发送WM_PAINT消息,并且把希望得到图形对象的句柄当作wParam参数传过去,这样就可以在图形对象得到想要得图形。
- 演练:开发和使用自定义服务器控件
- 开发和使用自定义服务器控件(一)
- 使用委托在用户自定义控件中实现事件响应