| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> 移动开发 -> 仿饿了么加入购物车旋转控件 - 自带闪转腾挪动画 的按钮 -> 正文阅读 |
|
[移动开发]仿饿了么加入购物车旋转控件 - 自带闪转腾挪动画 的按钮 |
android:layout_height=“wrap_content” app:count=“3” app:gapBetweenCircle=“90dp” app:maxCount=“99”/> <com.mcxtzhang.lib.AnimShopButton android:id="@+id/btnEle" android:layout_width=“wrap_content” android:layout_height=“wrap_content” app:addEnableBgColor="#3190E8" app:addEnableFgColor="#ffffff" app:hintBgColor="#3190E8" app:hintBgRoundValue=“15dp” app:hintFgColor="#ffffff" app:maxCount=“99”/> 注意: 加减点击后,具体的操作,要根据业务的不同来编写了,设计到实际的购物车可能还有写数据库操作,或者请求接口等,要操作成功后才执行动画、或者修改count,这一块代码每个人写法可能不同。 使用时,可以重写 效果图如图2. 支持的属性| name | format | description | 中文解释 | | :-: | :-: | :-: | :-: | | isAddFillMode | boolean | Plus button is opened Fill mode default is stroke (false) | 加按钮是否开启fill模式 默认是stroke(false) | | addEnableBgColor | color | The background color of the plus button | 加按钮的背景色 | | addEnableFgColor | color | The foreground color of the plus button | 加按钮的前景色 | | addDisableBgColor | color | The background color when the button is not available | 加按钮不可用时的背景色 | | addDisableFgColor | color | The foreground color when the button is not available | 加按钮不可用时的前景色 | | isDelFillMode | boolean | Plus button is opened Fill mode default is stroke (false) | 减按钮是否开启fill模式 默认是stroke(false) | | delEnableBgColor | color | The background color of the minus button | 减按钮的背景色 | | delEnableFgColor | color | The foreground color of the minus button | 减按钮的前景色 | | delDisableBgColor | color | The background color when the button is not available | 减按钮不可用时的背景色 | | delDisableFgColor | color | The foreground color when the button is not available | 减按钮不可用时的前景色 | | radius | dimension | The radius of the circle | 圆的半径 | | circleStrokeWidth | dimension | The width of the circle | 圆圈的宽度 | | lineWidth | dimension | The width of the line (+ - sign) | 线(+ - 符号)的宽度 | | gapBetweenCircle | dimension | The spacing between two circles | 两个圆之间的间距 | | numTextSize | dimension | The textSize of draws the number | 绘制数量的textSize | | maxCount | integer | max count | 最大数量 | | count | integer | current count | 当前数量 | | hintText | string | The hint text when number is 0 | 数量为0时,hint文字 | | hintBgColor | color | The hint background when number is 0 | 数量为0时,hint背景色 | | hintFgColor | color | The hint foreground when number is 0 | 数量为0时,hint前景色 | | hingTextSize | dimension | The hint text size when number is 0 | 数量为0时,hint文字大小 | | hintBgRoundValue | dimension | The background fillet value when number is 0 | 数量为0时,hint背景圆角值 | 这么多属性够你用了吧。 下面看重点的实现吧,Let’s Go!. 实现解剖 关于自定义 如果阅读时有不明白的,建议下载源码边看边读,或者学习自定义 代码传送门:喜欢的话,随手点个star。多谢 https://github.com/mcxtzhang/AnimShopButton 我们捡重点说,无非是绘制。 绘制的重点,这里分三块:
除了绘制以外的重点是:
静态绘制静态绘制就是最基本的自定义 要考虑到 避免overDraw和动画的需求, 我们要绘制的两层应该是互斥关系。 剥离掉动画代码,大致如下(基本都是draw代码,可以快速阅读): @Override protected void onDraw(Canvas canvas) { if (isHintMode) { //hint 展开 //背景 mHintPaint.setColor(mHintBgColor); RectF rectF = new RectF(mLeft, mTop , mWidth - mCircleWidth, mHeight - mCircleWidth); canvas.drawRoundRect(rectF, mHintBgRoundValue, mHintBgRoundValue, mHintPaint); //前景文字 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 (mCount > 0) { mDelPaint.setColor(mDelEnableBgColor); } else { mDelPaint.setColor(mDelDisableBgColor); } mDelPaint.setStrokeWidth(mCircleWidth); mDelPath.reset(); mDelPath.addCircle(mLeft + mRadius, mTop + mRadius, mRadius, Path.Direction.CW); mDelRegion.setPath(mDelPath, new Region(mLeft, mTop, mWidth - getPaddingRight(), mHeight - getPaddingBottom())); canvas.drawPath(mDelPath, mDelPaint); //前景 - if (mCount > 0) { mDelPaint.setColor(mDelEnableFgColor); } else { mDelPaint.setColor(mDelDisableFgColor); } mDelPaint.setStrokeWidth(mLineWidth); canvas.drawLine(-mRadius / 2, 0, +mRadius / 2, 0, mDelPaint); //数量 //是没有动画的普通写法,x left, y baseLine canvas.drawText(mCount + “”, mLeft + mRadius * 2, mTop + mRadius - (mFontMetrics.top + mFontMetrics.bottom) / 2, mTextPaint); //右边 //背景 圆 if (mCount < mMaxCount) { mAddPaint.setColor(mAddEnableBgColor); } else { mAddPaint.setColor(mAddDisableBgColor); } mAddPaint.setStrokeWidth(mCircleWidth); float left = mLeft + mRadius * 2 + 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.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); } } 根据 绘制第二层时没啥好说的,就是利用 (如果圆角的值足够大,矩形的宽度足够小,就变成了圆形。) 绘制第一层时,要根据当前的数量选择不同的颜色,注意在绘制加减按钮的圆圈时,我们是用 点击事件的监听 在讲解动画之前,我们先说说如何监听点击的区域,因为本控件的动画是和加减数量息息相关的,而数量的加减是由点击相应”+ - 所以我们的监听按钮的点击事件,其实就是监听相应的”+ - 按钮”区域。 上一节中,我们在绘制”+ - 按钮”区域时,通过 /**
*/ public native boolean contains(int x, int y); 知道了这一点,再写这部分代码就相当简单了: @Override public boolean onTouchEvent(MotionEvent event) { int action = event.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: //hint模式 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); } hint模式时,我们可以认为控件所有范围都是“+”的有效区域。 而在非hint模式时,根据上一节构建的 判断确认点击后,具体的操作,要根据业务的不同来编写了,设计到实际的购物车可能还有写数据库操作,或者请求接口等,要操作成功后才执行动画、或者修改count,这一块代码每个人写法可能不同。 使用时,可以重写 本文如下编写: protected void onDelClick() { if (mCount > 0) { mCount–; onCountDelSuccess(); } } protected void onAddClick() { if (mCount < mMaxCount) { mCount++; onCountAddSuccess(); } else { } } /**
*/ public void onCountAddSuccess() { if (mCount == 1) { cancelAllAnim(); mAnimReduceHint.start(); } else { mAnimFraction = 0; invalidate(); } } /**
*/ public void onCountDelSuccess() { if (mCount == 0) { cancelAllAnim(); mAniDel.start(); } else { mAnimFraction = 0; invalidate(); } } 动画的实现 这里会用到两个变量: //动画的基准值 动画:减 0~1, 加 1~0 // 普通状态下是0 protected float mAnimFraction; //提示语收缩动画 0-1 展开1-0 //普通模式时,应该是1, 只在 isHintMode true 才有效 protected float mAnimExpandHintFraction; 依次分析有哪些动画: Hint动画主要是圆角矩形的展开、收缩。 固定right、bottom,当展开时,不断减少矩形的左起点left坐标值,则整个矩形宽度变大,呈现展开。收缩时相反。 代码: //背景 mHintPaint.setColor(mHintBgColor); RectF rectF = new RectF(mLeft + (mWidth - mRadius * 2) * mAnimExpandHintFraction, mTop , mWidth - mCircleWidth, mHeight - mCircleWidth); canvas.drawRoundRect(rectF, mHintBgRoundValue, mHintBgRoundValue, mHintPaint); 减按钮动画看起来是旋转、位移、透明度。 那么对于背景的圆圈来说,我们只需要位移、透明度。因为它本身是个圆,就不要旋转了。 代码: //动画 mAnimFraction :减 0~1, 加 1~0 , //动画位移Max, float animOffsetMax = (mRadius * 2 +mGapBetweenCircle); //透明度动画的基准 int animAlphaMax = 255; int animRotateMax = 360; //左边 //背景 圆 mDelPaint.setAlpha((int) (animAlphaMax * (1 - mAnimFraction))); mDelPath.reset(); //改变圆心的X坐标,实现位移 mDelPath.addCircle(animOffsetMax * mAnimFraction + mLeft + mRadius, mTop + mRadius, mRadius, Path.Direction.CW); canvas.drawPath(mDelPath, mDelPaint); 对于前景的“-”号来说,旋转、位移、透明度都需要做。 这里我们利用 //前景 - //旋转动画 canvas.save(); canvas.translate(animOffsetMax * mAnimFraction + mLeft + mRadius, mTop + mRadius); canvas.rotate((int) (animRotateMax * (1 - mAnimFraction))); canvas.drawLine(-mRadius / 2, 0, +mRadius / 2, 0, mDelPaint); canvas.restore(); 数量的动画看起来也是旋转、位移、透明度。同样是利用 //数量 canvas.save(); //平移动画 canvas.translate(mAnimFraction * (mGapBetweenCircle / 2 - mTextPaint.measureText(mCount + “”) / 2 + mRadius), 0); //旋转动画,旋转中心点,x 是绘图中心,y 是控件中心 canvas.rotate(360 * mAnimFraction, mGapBetweenCircle / 2 + mLeft + mRadius * 2 , mTop + mRadius); //透明度动画 mTextPaint.setAlpha((int) (255 * (1 - mAnimFraction))); //是没有动画的普通写法,x left, y baseLine canvas.drawText(mCount + “”, mGapBetweenCircle / 2 - mTextPaint.measureText(mCount + “”) / 2 + mLeft + mRadius * 2, mTop + mRadius - (mFontMetrics.top + mFontMetrics.bottom) / 2, mTextPaint); canvas.restore(); 动画的定义:动画是在View初始化时就定义好的,执行顺序:
代码如下: //动画 + mAnimAdd = ValueAnimator.ofFloat(1, 0); mAnimAdd.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mAnimFraction = (float) animation.getAnimatedValue(); invalidate(); } }); 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) { //然后底色也不显示了 mAnimAdd.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mAnimFraction = (float) animation.getAnimatedValue(); invalidate(); } }); 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) { //然后底色也不显示了 |
|
移动开发 最新文章 |
Vue3装载axios和element-ui |
android adb cmd |
【xcode】Xcode常用快捷键与技巧 |
Android开发中的线程池使用 |
Java 和 Android 的 Base64 |
Android 测试文字编码格式 |
微信小程序支付 |
安卓权限记录 |
知乎之自动养号 |
【Android Jetpack】DataStore |
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/24 12:45:44- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |