当前位置:网站首页>自定义控件之下载控件1(DownloadView1)
自定义控件之下载控件1(DownloadView1)
2022-06-29 09:16:00 【禽兽先生不禽兽】
前段时间在干货集中营看到了两个炫酷的下载按钮:

可惜是隔壁 iOS 的孩子,怎么办,我也好喜欢,emmm,某该,只能自己模仿着实现一下了。先从第一个入手(第二个波浪效果暂时还不会)。
1 准备动作
public class DownloadView1 extends View {
private Paint mPaint;
private float radius = 150; //圆半径
public DownloadView1(Context context) {
this(context, null);
}
public DownloadView1(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public DownloadView1(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mPaint = new Paint();
mPaint.setStrokeWidth(radius / 10);
mPaint.setStrokeCap(Paint.Cap.ROUND);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setBackgroundColor(Color.rgb(10, 39, 46));
}
}2 画圆和箭头
public class DownloadView1 extends View {
private Paint mPaint;
private Path mPath;
private float radius = 150; //圆半径
private float lineLength = radius / 2; //下载的标志中间的竖线的长度
private float arrowTurningPointY = radius / 2; //下载的标志下面箭头的转折点的 Y 轴坐标
private float arrowLength = radius / 2; //下载的标志下面箭头变成水平线后的长度
public DownloadView1(Context context) {
this(context, null);
}
public DownloadView1(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public DownloadView1(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mPaint = new Paint();
mPaint.setStrokeWidth(radius / 10);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPath = new Path();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setBackgroundColor(Color.rgb(10, 39, 46));
}
@Override
protected void onDraw(Canvas canvas) {
mPaint.setColor(Color.rgb(10, 39, 46));
mPaint.setStyle(Paint.Style.FILL);
mPaint.setAntiAlias(true);
canvas.drawCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2, radius, mPaint);
mPaint.setColor(Color.rgb(37, 66, 73));
mPaint.setStyle(Paint.Style.STROKE);
canvas.drawCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2, radius, mPaint);
mPaint.setColor(Color.rgb(255, 255, 255));
mPaint.setStyle(Paint.Style.STROKE);
canvas.drawLine(getMeasuredWidth() / 2, getMeasuredHeight() / 2 - lineLength
, getMeasuredWidth() / 2, getMeasuredHeight() / 2 + lineLength
, mPaint);
mPaint.setColor(Color.rgb(255, 255, 255));
mPaint.setStyle(Paint.Style.STROKE);
mPath.reset();
mPath.moveTo(getMeasuredWidth() / 2 - arrowLength, getMeasuredHeight() / 2);
mPath.lineTo(getMeasuredWidth() / 2, getMeasuredHeight() / 2 + arrowTurningPointY);
mPath.lineTo(getMeasuredWidth() / 2 + arrowLength, getMeasuredHeight() / 2);
canvas.drawPath(mPath, mPaint);
}
}
3 下载箭头竖线收缩成一个点
//画下载箭头
mPaint.setColor(Color.rgb(255, 255, 255));
mPaint.setStyle(Paint.Style.STROKE);
if (lineLength == 0) {
//下载箭头的竖线收缩成点后应该画点
canvas.drawPoint(getMeasuredWidth() / 2, linePointY, mPaint);
} else {
//否则应该画线
canvas.drawLine(getMeasuredWidth() / 2, getMeasuredHeight() / 2 - lineLength
, getMeasuredWidth() / 2, getMeasuredHeight() / 2 + lineLength
, mPaint);
}private void startAnimation() {
//每个动画设置不同的时长
lineLengthAnimator.setDuration(500);
//添加动画监听,实际上就是一直重绘
lineLengthAnimator.addUpdateListener(mAnimatorUpdateListener);
//设置动画的播放顺序
AnimatorSet mAnimatorSet = new AnimatorSet();
mAnimatorSet.play(lineLengthAnimator);
mAnimatorSet.start();
} private ValueAnimator.AnimatorUpdateListener mAnimatorUpdateListener = new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
invalidate();
}
};
4 下载箭头的箭头部分变成横线,同时竖线的圆点上移
private void startAnimation() {
//下载箭头竖线收缩成点的动画
ObjectAnimator lineLengthAnimator = ObjectAnimator.ofFloat(this, "lineLength", radius / 2, 0);
//下载箭头竖线收缩成点后的动画1(下载箭头的箭头变成横线)
ObjectAnimator arrowTurningPointYAnimator1 = ObjectAnimator.ofFloat(this, "arrowTurningPointY", radius / 2, 0);
//下载箭头竖线收缩成点后的动画2(下载箭头的箭头抖一下的效果)
ObjectAnimator arrowTurningPointYAnimator2 = ObjectAnimator.ofFloat(this, "arrowTurningPointY", 0, radius / 4, 0);
//下载箭头竖线收缩成点后的在 Y 轴的坐标变化的动画
ObjectAnimator linePointYAnimator = ObjectAnimator.ofFloat(this, "linePointY", getMeasuredHeight() / 2, getMeasuredHeight() / 2 - radius);
//每个动画设置不同的时长
lineLengthAnimator.setDuration(500);
arrowTurningPointYAnimator1.setDuration(500);
arrowTurningPointYAnimator2.setDuration(500);
linePointYAnimator.setDuration(500);
//添加动画监听,实际上就是一直重绘
lineLengthAnimator.addUpdateListener(mAnimatorUpdateListener);
arrowTurningPointYAnimator1.addUpdateListener(mAnimatorUpdateListener);
arrowTurningPointYAnimator2.addUpdateListener(mAnimatorUpdateListener);
linePointYAnimator.addUpdateListener(mAnimatorUpdateListener);
//设置动画的播放顺序
AnimatorSet mAnimatorSet = new AnimatorSet();
mAnimatorSet.play(arrowTurningPointYAnimator1).after(lineLengthAnimator);
mAnimatorSet.play(arrowTurningPointYAnimator2).after(arrowTurningPointYAnimator1);
mAnimatorSet.play(linePointYAnimator).after(arrowTurningPointYAnimator1);
mAnimatorSet.start();
}
5 画两个半圆,下载箭头变成的横线收缩成一个点
//画两个半圆
mPaint.setColor(Color.rgb(255, 255, 255));
mPaint.setStyle(Paint.Style.STROKE);
canvas.drawArc(mRectF, -90, sweepAngle, false, mPaint);
canvas.drawArc(mRectF, -90, 0 - sweepAngle, false, mPaint); mPaint.setColor(Color.rgb(255, 255, 255));
mPaint.setStyle(Paint.Style.STROKE);
if (arrowLength == 0) {
//下载箭头的箭头部分收缩成点后应该画点
canvas.drawPoint(getMeasuredWidth() / 2, tickTurningPointY, mPaint);
drawOk = true;
} else {
//否则应该画箭头
mPath.reset();
mPath.moveTo(getMeasuredWidth() / 2 - arrowLength, getMeasuredHeight() / 2);
mPath.lineTo(getMeasuredWidth() / 2, getMeasuredHeight() / 2 + arrowTurningPointY);
mPath.lineTo(getMeasuredWidth() / 2 + arrowLength, getMeasuredHeight() / 2);
canvas.drawPath(mPath, mPaint);
} private void startAnimation() {
//下载箭头竖线收缩成点的动画
ObjectAnimator lineLengthAnimator = ObjectAnimator.ofFloat(this, "lineLength", radius / 2, 0);
//下载箭头竖线收缩成点后的动画1(下载箭头的箭头变成横线)
ObjectAnimator arrowTurningPointYAnimator1 = ObjectAnimator.ofFloat(this, "arrowTurningPointY", radius / 2, 0);
//下载箭头竖线收缩成点后的动画2(下载箭头的箭头抖一下的效果)
ObjectAnimator arrowTurningPointYAnimator2 = ObjectAnimator.ofFloat(this, "arrowTurningPointY", 0, radius / 4, 0);
//下载箭头竖线收缩成点后的在 Y 轴的坐标变化的动画
ObjectAnimator linePointYAnimator = ObjectAnimator.ofFloat(this, "linePointY", getMeasuredHeight() / 2, getMeasuredHeight() / 2 - radius);
//画半圆的动画
ObjectAnimator sweepAngleAnimator = ObjectAnimator.ofFloat(this, "sweepAngle", 0, 180);
//下载箭头的箭头变成横线后收缩成点的动画
ObjectAnimator arrowLengthAnimator = ObjectAnimator.ofFloat(this, "arrowLength", radius / 2, 0);
//每个动画设置不同的时长
lineLengthAnimator.setDuration(500);
arrowTurningPointYAnimator1.setDuration(500);
arrowTurningPointYAnimator2.setDuration(500);
linePointYAnimator.setDuration(500);
sweepAngleAnimator.setDuration(3000);
arrowLengthAnimator.setDuration(3000);
//添加动画监听,实际上就是一直重绘
lineLengthAnimator.addUpdateListener(mAnimatorUpdateListener);
arrowTurningPointYAnimator1.addUpdateListener(mAnimatorUpdateListener);
arrowTurningPointYAnimator2.addUpdateListener(mAnimatorUpdateListener);
linePointYAnimator.addUpdateListener(mAnimatorUpdateListener);
sweepAngleAnimator.addUpdateListener(mAnimatorUpdateListener);
arrowLengthAnimator.addUpdateListener(mAnimatorUpdateListener);
//设置动画的播放顺序
AnimatorSet mAnimatorSet = new AnimatorSet();
mAnimatorSet.play(arrowTurningPointYAnimator1).after(lineLengthAnimator);
mAnimatorSet.play(arrowTurningPointYAnimator2).after(arrowTurningPointYAnimator1);
mAnimatorSet.play(linePointYAnimator).after(arrowTurningPointYAnimator1);
mAnimatorSet.play(arrowLengthAnimator).after(arrowTurningPointYAnimator2);
mAnimatorSet.play(sweepAngleAnimator).after(arrowTurningPointYAnimator2);
mAnimatorSet.start();
}
6 画对勾

mPath.moveTo((float) (getMeasuredWidth() / 2 - (Math.sin(45) * tickLength / 3))
, (float) (tickTurningPointY - (Math.cos(45) * tickLength / 3)));
mPath.lineTo(getMeasuredWidth() / 2, tickTurningPointY);
mPath.lineTo((float) (getMeasuredWidth() / 2 + (Math.sin(45) * tickLength / 3*2))
, (float) (tickTurningPointY - (Math.cos(45) * tickLength / 3*2)));
mPath.moveTo((float) (getMeasuredWidth() / 2 - (Math.sin(45) * tickLength / 3))
, (float) (tickTurningPointY - (Math.cos(45) * tickLength / 3)-radius/4));
mPath.lineTo(getMeasuredWidth() / 2, tickTurningPointY-radius/4);
mPath.lineTo((float) (getMeasuredWidth() / 2 + (Math.sin(45) * tickLength / 3))
, (float) (tickTurningPointY - (Math.cos(45) * tickLength / 3*2)-radius/4));然后添加动画,代码就不贴了,看最后的整体代码吧。最终的整体效果如下:

7 总结
8 源码
/**
* Description:
* Created by 禽兽先生
* Created on 2017/9/4
*/
public class DownloadView1 extends View {
private Paint mPaint;
private Path mPath;
private float radius = 150; //圆半径
private float lineLength = radius / 2; //下载的标志中间的竖线的长度
private float arrowTurningPointY = radius / 2; //下载的标志下面箭头的转折点的 Y 轴坐标
private float linePointY; //下载的标志中间的竖线收缩成点之后点的 Y 坐标,onMeasured() 方法中初始化
private RectF mRectF;
private float sweepAngle = 0; //两边弧线扫过的角度
private float arrowLength = radius / 2; //下载的标志下面箭头变成水平线后的长度
private float tickTurningPointY; //对钩 Y 坐标,onMeasured() 方法中初始化
private float tickLength = 0; //对钩的总长度;
private boolean drawOk; //是否应该画最后的对钩的标志位
public DownloadView1(Context context) {
this(context, null);
}
public DownloadView1(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public DownloadView1(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mPaint = new Paint();
mPaint.setStrokeWidth(radius / 10);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPath = new Path();
setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
startAnimation();
}
});
}
public void setLineLength(float lineLength) {
this.lineLength = lineLength;
}
public void setArrowTurningPointY(float arrowTurningPointY) {
this.arrowTurningPointY = arrowTurningPointY;
}
public void setLinePointY(float linePointY) {
this.linePointY = linePointY;
}
public void setSweepAngle(float sweepAngle) {
this.sweepAngle = sweepAngle;
}
public void setArrowLength(float arrowLength) {
this.arrowLength = arrowLength;
}
public void setTickTurningPointY(float tickTurningPointY) {
this.tickTurningPointY = tickTurningPointY;
}
public void setTickLength(float tickLength) {
this.tickLength = tickLength;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setBackgroundColor(Color.rgb(10, 39, 46));
linePointY = getMeasuredHeight() / 2;
tickTurningPointY = getMeasuredHeight() / 2;
mRectF = new RectF(getMeasuredWidth() / 2 - radius
, getMeasuredHeight() / 2 - radius
, getMeasuredWidth() / 2 + radius
, getMeasuredHeight() / 2 + radius);
}
@Override
protected void onDraw(Canvas canvas) {
//画整个圆
mPaint.setColor(Color.rgb(10, 39, 46));
mPaint.setStyle(Paint.Style.FILL);
mPaint.setAntiAlias(true);
canvas.drawCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2, radius, mPaint);
//画圆环
mPaint.setColor(Color.rgb(37, 66, 73));
mPaint.setStyle(Paint.Style.STROKE);
canvas.drawCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2, radius, mPaint);
//画两个半圆
mPaint.setColor(Color.rgb(255, 255, 255));
mPaint.setStyle(Paint.Style.STROKE);
canvas.drawArc(mRectF, -90, sweepAngle, false, mPaint);
canvas.drawArc(mRectF, -90, 0 - sweepAngle, false, mPaint);
//根据是否应该画对勾的标志位来决定画什么
if (drawOk) {
//画对勾
mPaint.setColor(Color.rgb(98, 178, 117));
mPaint.setStyle(Paint.Style.STROKE);
mPath.reset();
mPath.moveTo((float) (getMeasuredWidth() / 2 - (Math.sin(45) * tickLength / 3))
, (float) (tickTurningPointY - (Math.cos(45) * tickLength / 3)-radius/4));
mPath.lineTo(getMeasuredWidth() / 2, tickTurningPointY-radius/4);
mPath.lineTo((float) (getMeasuredWidth() / 2 + (Math.sin(45) * tickLength / 3))
, (float) (tickTurningPointY - (Math.cos(45) * tickLength / 3*2)-radius/4));
canvas.drawPath(mPath, mPaint);
} else {
//画下载箭头
mPaint.setColor(Color.rgb(255, 255, 255));
mPaint.setStyle(Paint.Style.STROKE);
if (lineLength == 0) {
//下载箭头的竖线收缩成点后应该画点
canvas.drawPoint(getMeasuredWidth() / 2, linePointY, mPaint);
} else {
//否则应该画线
canvas.drawLine(getMeasuredWidth() / 2, getMeasuredHeight() / 2 - lineLength
, getMeasuredWidth() / 2, getMeasuredHeight() / 2 + lineLength
, mPaint);
}
mPaint.setColor(Color.rgb(255, 255, 255));
mPaint.setStyle(Paint.Style.STROKE);
if (arrowLength == 0) {
//下载箭头的箭头部分收缩成点后应该画点
canvas.drawPoint(getMeasuredWidth() / 2, tickTurningPointY, mPaint);
drawOk = true;
} else {
//否则应该画箭头
mPath.reset();
mPath.moveTo(getMeasuredWidth() / 2 - arrowLength, getMeasuredHeight() / 2);
mPath.lineTo(getMeasuredWidth() / 2, getMeasuredHeight() / 2 + arrowTurningPointY);
mPath.lineTo(getMeasuredWidth() / 2 + arrowLength, getMeasuredHeight() / 2);
canvas.drawPath(mPath, mPaint);
}
}
}
private void startAnimation() {
//下载箭头竖线收缩成点的动画
ObjectAnimator lineLengthAnimator = ObjectAnimator.ofFloat(this, "lineLength", radius / 2, 0);
//下载箭头竖线收缩成点后的动画1(下载箭头的箭头变成横线)
ObjectAnimator arrowTurningPointYAnimator1 = ObjectAnimator.ofFloat(this, "arrowTurningPointY", radius / 2, 0);
//下载箭头竖线收缩成点后的动画2(下载箭头的箭头抖一下的效果)
ObjectAnimator arrowTurningPointYAnimator2 = ObjectAnimator.ofFloat(this, "arrowTurningPointY", 0, radius / 4, 0);
//下载箭头竖线收缩成点后的在 Y 轴的坐标变化的动画
ObjectAnimator linePointYAnimator = ObjectAnimator.ofFloat(this, "linePointY", getMeasuredHeight() / 2, getMeasuredHeight() / 2 - radius);
//画半圆的动画
ObjectAnimator sweepAngleAnimator = ObjectAnimator.ofFloat(this, "sweepAngle", 0, 180);
//下载箭头的箭头变成横线后收缩成点的动画
ObjectAnimator arrowLengthAnimator = ObjectAnimator.ofFloat(this, "arrowLength", radius / 2, 0);
//对勾的转折点在 Y 轴坐标变化的动画
ObjectAnimator tickTurningPointYAnimator = ObjectAnimator.ofFloat(this, "tickTurningPointY", getMeasuredHeight() / 2, getMeasuredHeight() / 2 + radius / 2);
//对勾的总长度的变化动画
ObjectAnimator tickLengthAnimator = ObjectAnimator.ofFloat(this, "tickLength", 0, radius);
//每个动画设置不同的时长
lineLengthAnimator.setDuration(500);
arrowTurningPointYAnimator1.setDuration(500);
arrowTurningPointYAnimator2.setDuration(500);
linePointYAnimator.setDuration(500);
sweepAngleAnimator.setDuration(3000);
arrowLengthAnimator.setDuration(3000);
tickTurningPointYAnimator.setDuration(1000);
tickLengthAnimator.setDuration(1000);
//添加动画监听,实际上就是一直重绘
lineLengthAnimator.addUpdateListener(mAnimatorUpdateListener);
arrowTurningPointYAnimator1.addUpdateListener(mAnimatorUpdateListener);
arrowTurningPointYAnimator2.addUpdateListener(mAnimatorUpdateListener);
linePointYAnimator.addUpdateListener(mAnimatorUpdateListener);
sweepAngleAnimator.addUpdateListener(mAnimatorUpdateListener);
arrowLengthAnimator.addUpdateListener(mAnimatorUpdateListener);
tickTurningPointYAnimator.addUpdateListener(mAnimatorUpdateListener);
tickLengthAnimator.addUpdateListener(mAnimatorUpdateListener);
//设置动画的播放顺序
AnimatorSet mAnimatorSet = new AnimatorSet();
mAnimatorSet.play(arrowTurningPointYAnimator1).after(lineLengthAnimator);
mAnimatorSet.play(arrowTurningPointYAnimator2).after(arrowTurningPointYAnimator1);
mAnimatorSet.play(linePointYAnimator).after(arrowTurningPointYAnimator1);
mAnimatorSet.play(arrowLengthAnimator).after(arrowTurningPointYAnimator2);
mAnimatorSet.play(sweepAngleAnimator).after(arrowTurningPointYAnimator2);
mAnimatorSet.play(tickTurningPointYAnimator).after(sweepAngleAnimator);
mAnimatorSet.play(tickLengthAnimator).after(sweepAngleAnimator);
//添加动画集监听,结束后重置各个变量
mAnimatorSet.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
// lineLength = radius / 2;
// arrowTurningPointY = radius / 2;
// linePointY = getMeasuredHeight() / 2;
// sweepAngle = 0;
// arrowLength = radius / 2;
// tickTurningPointY = getMeasuredHeight() / 2;
// tickLength = 0;
// drawOk = false;
// invalidate();
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
mAnimatorSet.start();
}
private ValueAnimator.AnimatorUpdateListener mAnimatorUpdateListener = new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
invalidate();
}
};
}边栏推荐
- 聊聊你理解的线程与并发
- Summary of PHP memory horse technology research and killing methods
- The 23 most useful elasticsearch search techniques you must know
- Data governance: data standard management (Part III)
- 力扣94二叉树的中序遍历
- Zabbix4.4 configure the indicators of the monitoring server and solve the garbled graphics pages
- container
- Middle order traversal of Li Kou 94 binary tree
- PHP内存马技术研究与查杀方法总结
- Leetcode MySQL database topic 177
猜你喜欢

Automatic Multi-Organ SegmVentation on Abdominal CT With Dense V-Networks

另类实现 ScrollView 下拉头部放大

Kicad learning notes - shortcut keys

KDevelop new project

JVM之对象的内存布局

Deep Learning-based Automated Delineation of Head and Neck Malignant Lesions from PET Images

Generic paging framework

RecyclerView 粘性(悬浮)头部

MySQL configuring master-slave databases

Cisco ASA、FTD和HyperFlex HX的漏洞分析复现
随机推荐
leetcode MYSQL数据库题目181
Constructing SQL statements by sprintf() function in C language
linux环境下安装配置redis,并设置开机自启动
mysql修改自动递增初始值
leetcode MYSQL数据库题目176
MySQL modify auto increment initial value
Student addition / deletion gaih
遍历vector容器中的对象的方式
Data governance: Metadata Management (Part 2)
2020-09-18 referer认证 url转义
Do you know what BFD is? This article explains the principle and usage scenarios of BFD protocol in detail
Deep Learning-based Automated Delineation of Head and Neck Malignant Lesions from PET Images
MySQL configuring master-slave databases
Wechat applet rewrites the page function to realize global logging
力扣85题最大矩形
Gmail:如何快速将邮件全部已读
LiferayPortal JSONWS反序列化漏洞(CVE-2020-7961)分析
PHP内存马技术研究与查杀方法总结
A 2.5D Cancer Segmentation for MRI Images Based on U-Net
JVM之方法返回地址