当前位置: 首页 > news >正文

Android自定义View实现横向的双水波纹进度条

效果图:

网上垂直的水波纹进度条很多,但横向的很少,将垂直的水波纹改为水平的还遇到了些麻烦,现在完善后发布出来,希望遇到的人少躺点坑。

思路分析

整体效果可分为三个,绘制圆角背景和圆角矩形,绘制第一条和第二条水波浪,根据自定义进度变化效果。

功能实现

1、绘制圆角背景和圆角矩形边框

圆角矩形边框:

private RectF rectBorder;
if (rectBorder == null) {rectBorder = new RectF(0.5f * dp1, 0.5f * dp1, waveActualSizeWidth - 0.5f * dp1, waveActualSizeHeight - 0.5f * dp1);
}
canvas.drawRoundRect(rectBorder, dp27, dp27, borderPaint);

我们创建一个新的画布,然后在画布里画上圆角矩形背景和第一条和第二条水波浪:

//这里用到了缓存 根据参数创建新位图
if (circleBitmap == null) {circleBitmap = Bitmap.createBitmap(waveActualSizeWidth, waveActualSizeHeight, Bitmap.Config.ARGB_8888);
}
//以该bitmap为底创建一块画布
if (bitmapCanvas == null) {bitmapCanvas = new Canvas(circleBitmap);
}
// 圆角矩形背景,为了能让波浪填充完整个圆形背景
if (rectBg == null) {rectBg = new RectF(0, 0, waveActualSizeWidth, waveActualSizeHeight);
}
bitmapCanvas.drawRoundRect(rectBg, dp27, dp27, backgroundPaint);
//裁剪图片
canvas.drawBitmap(circleBitmap, 0, 0, null);

2、通过贝塞尔曲线实现双水波

1)实现第一条水波

/*** 绘制波浪线*/
private Path canvasWavePath() {//要先清掉路线wavePath.reset();//起始点移至(0,0) p0 -p1 的高度随着进度的变化而变化wavePath.moveTo((currentPercent) * waveActualSizeWidth, -moveDistance);//最多能绘制多少个波浪//其实也可以用 i < getWidth() ;i+=waveLength来判断 这个没那么完美//绘制p0 - p1 绘制波浪线 这里有一段是超出View的,在View右边距的右边 所以是* 2for (int i = 0; i < waveNumber * 2; i++) {wavePath.rQuadTo(waveHeight, waveLength / 2, 0, waveLength);wavePath.rQuadTo(-waveHeight, waveLength / 2, 0, waveLength);}//连接p1 - p2wavePath.lineTo(0, waveActualSizeHeight);//连接p2 - p0wavePath.lineTo(0, 0);//封闭起来填充wavePath.close();return wavePath;
}

moveDistance为水波垂直方向移动的距离。

waveLength为水波长度,一个上弧加一个下弧为一个波长。

path的起始点为(0,0)可根据进度动态改变,然后循环画曲线,长度是有几个波浪就是多长,然后连接到view高度的位置,最后到(0,0),形成一个封闭的区域,这样就实现了一个填充的水波效果。

2)绘制第二条水波,第二条水波和第一条类似,只是起始点变了:

/*** 绘制第二层波浪*/
private Path canvasSecondPath() {secondWavePath.reset();//初始点移动到下方secondWavePath.moveTo((currentPercent) * waveActualSizeWidth, waveActualSizeHeight + moveDistance);for (int i = 0; i < waveNumber * 2; i++) {secondWavePath.rQuadTo(waveHeight, -waveLength / 2, 0, -waveLength);secondWavePath.rQuadTo(-waveHeight, -waveLength / 2, 0, -waveLength);}secondWavePath.lineTo(0, 0);secondWavePath.lineTo(0, waveActualSizeHeight);secondWavePath.close();return secondWavePath;
}

3、设置动画使进度和水波纹变化

/*** 设置进度** @param currentProgress 进度* @param duration        达到进度需要的时间*/
public void setProgress(int currentProgress, long duration, AnimatorListenerAdapter listenerAdapter) {float percent = currentProgress * 1f / maxProgress;this.currentProgress = currentProgress;//从0开始变化currentPercent = 0;moveDistance = 0;mProgressAnimator = ValueAnimator.ofFloat(0, percent);//设置动画时间mProgressAnimator.setDuration(duration);//让动画匀速播放,避免出现波浪平移停顿的现象mProgressAnimator.setInterpolator(new LinearInterpolator());mProgressAnimator.addUpdateListener(listener);mProgressAnimator.addListener(listenerAdapter);mProgressAnimator.start();// 波浪线startWaveAnimal();
}/*** 波浪动画*/
private void startWaveAnimal() {//动画实例化if (waveProgressAnimator == null) {waveProgressAnimator = new WaveProgressAnimal();//设置动画时间waveProgressAnimator.setDuration(2000);//设置循环播放waveProgressAnimator.setRepeatCount(Animation.INFINITE);//让动画匀速播放,避免出现波浪平移停顿的现象waveProgressAnimator.setInterpolator(new LinearInterpolator());//当前视图开启动画this.startAnimation(waveProgressAnimator);}
}

其中波浪动画是通过改变moveDistance的值改变纵坐标达到,进度主要是通过改变百分比currentPercent改变波浪的横坐标达到。

完整源码:

/*** 横向双水波浪进度条** @author jingbin**/
public class HorizontalWaveProgressView extends View {//绘制波浪画笔private Paint wavePaint;//绘制波浪Pathprivate Path wavePath;//波浪的宽度private final float waveLength;//波浪的高度private final float waveHeight;//波浪组的数量 一个波浪是一低一高private int waveNumber;//自定义View的波浪宽高private int waveDefaultWidth;private int waveDefaultHeight;//测量后的View实际宽高private int waveActualSizeWidth;private int waveActualSizeHeight;//当前进度值占总进度值的占比private float currentPercent;//当前进度值private int currentProgress;//进度的最大值private int maxProgress;//动画对象private WaveProgressAnimal waveProgressAnimator;private ValueAnimator mProgressAnimator;private ValueAnimator mEndAnimator;//波浪平移距离private float moveDistance = 0;//圆形背景画笔private Paint backgroundPaint;// 边框private Paint borderPaint;//bitmapprivate Bitmap circleBitmap;//bitmap画布private Canvas bitmapCanvas;//波浪颜色private final int wave_color;//圆形背景进度框颜色private final int backgroundColor;//进度条显示值监听接口private UpdateTextListener updateTextListener;//是否绘制双波浪线private boolean isShowSecondWave;//第二层波浪的颜色private final int secondWaveColor;//边框色private final int borderColor;//第二层波浪的画笔private Paint secondWavePaint;private Path secondWavePath;private int dp1;// 圆角角度private int dp27;public HorizontalWaveProgressView(Context context) {this(context, null);}public HorizontalWaveProgressView(Context context, @Nullable AttributeSet attrs) {this(context, attrs, 0);}public HorizontalWaveProgressView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);//获取attrs文件下配置属性TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.HorizontalWaveProgressView);//获取波浪宽度 第二个参数,如果xml设置这个属性,则会取设置的默认值 也就是说xml没有指定wave_length这个属性,就会取Density.dip2px(context,25)waveLength = typedArray.getDimension(R.styleable.HorizontalWaveProgressView_wave_length, DensityUtil.dip2px(context, 25));//获取波浪高度waveHeight = typedArray.getDimension(R.styleable.HorizontalWaveProgressView_wave_height, DensityUtil.dip2px(context, 5));//获取波浪颜色wave_color = typedArray.getColor(R.styleable.HorizontalWaveProgressView_wave_color, Color.parseColor("#B76EFF"));//圆形背景颜色backgroundColor = typedArray.getColor(R.styleable.HorizontalWaveProgressView_wave_background_color, Color.WHITE);//当前进度currentProgress = typedArray.getInteger(R.styleable.HorizontalWaveProgressView_currentProgress, 0);//最大进度maxProgress = typedArray.getInteger(R.styleable.HorizontalWaveProgressView_maxProgress, 100);//是否显示第二层波浪isShowSecondWave = typedArray.getBoolean(R.styleable.HorizontalWaveProgressView_second_show, false);//第二层波浪的颜色secondWaveColor = typedArray.getColor(R.styleable.HorizontalWaveProgressView_second_color, Color.parseColor("#DEBCFF"));//边框色borderColor = typedArray.getColor(R.styleable.HorizontalWaveProgressView_border_color, Color.parseColor("#DEBCFF"));//记得把TypedArray回收//程序在运行时维护了一个 TypedArray的池,程序调用时,会向该池中请求一个实例,用完之后,调用 recycle() 方法来释放该实例,从而使其可被其他模块复用。//那为什么要使用这种模式呢?答案也很简单,TypedArray的使用场景之一,就是上述的自定义View,会随着 Activity的每一次Create而Create,//因此,需要系统频繁的创建array,对内存和性能是一个不小的开销,如果不使用池模式,每次都让GC来回收,很可能就会造成OutOfMemory。//这就是使用池+单例模式的原因,这也就是为什么官方文档一再的强调:使用完之后一定 recycle,recycle,recycletypedArray.recycle();init(context);}/*** 初始化一些画笔路径配置*/private void init(Context context) {//设置自定义View的宽高waveDefaultWidth = DensityUtil.dip2px(context, 152);waveDefaultHeight = DensityUtil.dip2px(context, 40);dp1 = DensityUtil.dip2px(getContext(), 1);dp27 = DensityUtil.dip2px(getContext(), 27);wavePath = new Path();wavePaint = new Paint();//设置画笔为取交集模式wavePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));//设置波浪颜色wavePaint.setColor(wave_color);//设置抗锯齿wavePaint.setAntiAlias(true);//矩形背景backgroundPaint = new Paint();backgroundPaint.setColor(backgroundColor);backgroundPaint.setAntiAlias(true);//边框borderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);borderPaint.setColor(borderColor);borderPaint.setAntiAlias(true);borderPaint.setStrokeWidth(dp1);borderPaint.setStyle(Paint.Style.STROKE);if (isShowSecondWave) {//是否绘制双波浪线secondWavePath = new Path();//初始化第二层波浪画笔secondWavePaint = new Paint();secondWavePaint.setColor(secondWaveColor);secondWavePaint.setAntiAlias(true);//因为要覆盖在第一层波浪上,且要让半透明生效,所以选SRC_ATOP模式secondWavePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP));}//占比一开始设置为0currentPercent = currentProgress * 1f / maxProgress;}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);//这里用到了缓存 根据参数创建新位图circleBitmap = Bitmap.createBitmap(waveActualSizeWidth, waveActualSizeHeight, Bitmap.Config.ARGB_8888);//以该bitmap为底创建一块画布bitmapCanvas = new Canvas(circleBitmap);// 绘制背景,为了能让波浪填充完整个圆形背景RectF rectBg = new RectF(0, 0, waveActualSizeWidth, waveActualSizeHeight);bitmapCanvas.drawRoundRect(rectBg, dp27, dp27, backgroundPaint);if (isShowSecondWave) {//绘制第二层波浪bitmapCanvas.drawPath(canvasSecondPath(), secondWavePaint);}
//绘制波浪形bitmapCanvas.drawPath(canvasWavePath(), wavePaint);//裁剪图片canvas.drawBitmap(circleBitmap, 0, 0, null);// 绘制边框RectF rectBorder = new RectF(0.5f * dp1, 0.5f * dp1, waveActualSizeWidth - 0.5f * dp1, waveActualSizeHeight - 0.5f * dp1);canvas.drawRoundRect(rectBorder, dp27, dp27, borderPaint);}/*** 绘制波浪线*/private Path canvasWavePath() {//要先清掉路线wavePath.reset();//起始点移至(0,0) p0 -p1 的高度随着进度的变化而变化wavePath.moveTo((currentPercent) * waveActualSizeWidth, -moveDistance);
//        wavePath.moveTo(-moveDistance,(1-currentPercent) * waveActualSize);//最多能绘制多少个波浪//其实也可以用 i < getWidth() ;i+=waveLength来判断 这个没那么完美//绘制p0 - p1 绘制波浪线 这里有一段是超出View的,在View右边距的右边 所以是* 2for (int i = 0; i < waveNumber * 2; i++) {wavePath.rQuadTo(waveHeight, waveLength / 2, 0, waveLength);wavePath.rQuadTo(-waveHeight, waveLength / 2, 0, waveLength);}//连接p1 - p2wavePath.lineTo(waveActualSizeWidth, waveActualSizeHeight);//连接p2 - p3wavePath.lineTo(0, waveActualSizeHeight);//连接p3 - p0 p3-p0d的高度随着进度变化而变化wavePath.lineTo(0, 0);//封闭起来填充wavePath.close();return wavePath;}/*** 绘制第二层波浪方法*/private Path canvasSecondPath() {float secondWaveHeight = waveHeight;secondWavePath.reset();//移动到右上方,也就是p1点secondWavePath.moveTo((currentPercent) * waveActualSizeWidth, waveActualSizeHeight + moveDistance);//p1 - p0for (int i = 0; i < waveNumber * 2; i++) {secondWavePath.rQuadTo(secondWaveHeight, -waveLength / 2, 0, -waveLength);secondWavePath.rQuadTo(-secondWaveHeight, -waveLength / 2, 0, -waveLength);}//p3-p0的高度随着进度变化而变化secondWavePath.lineTo(0, 0);//连接p3 - p2secondWavePath.lineTo(0, waveActualSizeHeight);secondWavePath.lineTo(waveActualSizeHeight, waveActualSizeWidth);//连接p2 - p1secondWavePath.lineTo(waveActualSizeWidth, waveActualSizeHeight + moveDistance);//封闭起来填充secondWavePath.close();return secondWavePath;}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);int width = measureSize(waveDefaultWidth, widthMeasureSpec);int height = measureSize(waveDefaultHeight, heightMeasureSpec);//把View改为正方形setMeasuredDimension(width, height);//waveActualSize是实际的宽高waveActualSizeWidth = width;waveActualSizeHeight = height;//Math.ceil(a)返回求不小于a的最小整数// 举个例子:// Math.ceil(125.9)=126.0// Math.ceil(0.4873)=1.0// Math.ceil(-0.65)=-0.0//这里是调整波浪数量 就是View中能容下几个波浪 用到ceil就是一定让View完全能被波浪占满 为循环绘制做准备 分母越小就约精准waveNumber = (int) Math.ceil(Double.parseDouble(String.valueOf(waveActualSizeHeight / waveLength / 2)));}/*** 返回指定的值** @param defaultSize 默认的值* @param measureSpec 模式*/private int measureSize(int defaultSize, int measureSpec) {int result = defaultSize;int specMode = MeasureSpec.getMode(measureSpec);int specSize = MeasureSpec.getSize(measureSpec);//View.MeasureSpec.EXACTLY:如果是match_parent 或者设置定值就//View.MeasureSpec.AT_MOST:wrap_contentif (specMode == MeasureSpec.EXACTLY) {result = specSize;} else if (specMode == MeasureSpec.AT_MOST) {result = Math.min(result, specSize);}return result;}//新建一个动画类public class WaveProgressAnimal extends Animation {//在绘制动画的过程中会反复的调用applyTransformation函数,// 每次调用参数interpolatedTime值都会变化,该参数从0渐 变为1,当该参数为1时表明动画结束@Overrideprotected void applyTransformation(float interpolatedTime, Transformation t) {super.applyTransformation(interpolatedTime, t);//左边的距离moveDistance = interpolatedTime * waveNumber * waveLength * 2;//重新绘制invalidate();}}/*** 直接结束** @param duration 结束时间*/public void setProgressEnd(long duration, AnimatorListenerAdapter listenerAdapter) {// 如果是100会不满,因为在波动if (currentProgress == maxProgress) {// 到底了就从头开始currentPercent = 0;}mEndAnimator = ValueAnimator.ofFloat(currentPercent, 1.1f);mEndAnimator.setInterpolator(new DecelerateInterpolator());mEndAnimator.setDuration(duration);mEndAnimator.addUpdateListener(listener);mEndAnimator.addListener(listenerAdapter);mEndAnimator.start();// 波浪线startWaveAnimal();}/*** 设置进度** @param currentProgress 进度* @param duration        达到进度需要的时间*/public void setProgress(int currentProgress, long duration, AnimatorListenerAdapter listenerAdapter) {float percent = currentProgress * 1f / maxProgress;this.currentProgress = currentProgress;//从0开始变化currentPercent = 0;moveDistance = 0;mProgressAnimator = ValueAnimator.ofFloat(0, percent);//设置动画时间mProgressAnimator.setDuration(duration);//让动画匀速播放,避免出现波浪平移停顿的现象mProgressAnimator.setInterpolator(new LinearInterpolator());mProgressAnimator.addUpdateListener(listener);mProgressAnimator.addListener(listenerAdapter);mProgressAnimator.start();// 波浪线startWaveAnimal();}/*** 波浪动画*/private void startWaveAnimal() {//动画实例化if (waveProgressAnimator == null) {waveProgressAnimator = new WaveProgressAnimal();//设置动画时间waveProgressAnimator.setDuration(2000);//设置循环播放waveProgressAnimator.setRepeatCount(Animation.INFINITE);//让动画匀速播放,避免出现波浪平移停顿的现象waveProgressAnimator.setInterpolator(new LinearInterpolator());//当前视图开启动画this.startAnimation(waveProgressAnimator);}}/*** 进度的监听*/ValueAnimator.AnimatorUpdateListener listener = new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {// 当前进度百分比,[0,1]currentPercent = (float) animation.getAnimatedValue();//这里直接根据进度值显示if (updateTextListener != null) {updateTextListener.updateText(currentPercent, maxProgress);}}};public interface UpdateTextListener {/*** 提供接口 给外部修改数值样式 等** @param currentPercent 当前进度百分比* @param maxProgress    进度条的最大数值*/void updateText(float currentPercent, float maxProgress);}/*** 设置监听*/public void setUpdateTextListener(UpdateTextListener updateTextListener) {this.updateTextListener = updateTextListener;}/*** 停止动画,销毁对象*/public void stopAnimal() {if (waveProgressAnimator != null) {waveProgressAnimator.cancel();}if (mProgressAnimator != null && mProgressAnimator.isStarted()) {mProgressAnimator.removeAllListeners();mProgressAnimator.cancel();}if (mEndAnimator != null && mEndAnimator.isStarted()) {mEndAnimator.removeAllListeners();mEndAnimator.cancel();}}
}

相关文章:

Android自定义View实现横向的双水波纹进度条

效果图&#xff1a;网上垂直的水波纹进度条很多&#xff0c;但横向的很少&#xff0c;将垂直的水波纹改为水平的还遇到了些麻烦&#xff0c;现在完善后发布出来&#xff0c;希望遇到的人少躺点坑。思路分析整体效果可分为三个&#xff0c;绘制圆角背景和圆角矩形&#xff0c;绘…...

Python 之 Pandas 分组操作详解和缺失数据处理

文章目录一、groupby 分组操作详解1. Groupby 的基本原理2. agg 聚合操作3. transform 转换值4. apply二、pandas 缺失数据处理1. 缺失值类型1.1 np.nan1.2 None1.3 NA 标量2. 缺失值处理2.1 查看缺失值的情形2.2 缺失值的判断2.3 删除缺失值2.4 缺失值填充在开始之前&#xff…...

【人工智能 AI】什么是人工智能? What is Artificial Intelligence

目录 Introduction to Artificial Intelligence人工智能概论 What is Artificial Intelligence? 什么是人工智能?...

17、触发器

文章目录1 触发器概述2 触发器的创建2.1 创建触发器语法2.2 代码举例3 查看、删除触发器3.1 查看触发器3.2 删除触发器4 触发器的优缺点4.1 优点4.2 缺点4.3 注意点尚硅谷MySQL数据库教程-讲师&#xff1a;宋红康 我们缺乏的不是知识&#xff0c;而是学而不厌的态度 在实际开发…...

内核并发消杀器(KCSAN)技术分析

一、KCSAN介绍KCSAN(Kernel Concurrency Sanitizer)是一种动态竞态检测器&#xff0c;它依赖于编译时插装&#xff0c;并使用基于观察点的采样方法来检测竞态&#xff0c;其主要目的是检测数据竞争。KCSAN是一种检测LKMM(Linux内核内存一致性模型)定义的数据竞争(data race)的工…...

蓄水池抽样算法

蓄水池抽样&#xff0c;也称水塘抽样&#xff0c;是随机抽样算法的一种。基本抽样问题有一批数据&#xff08;假设为一个数组&#xff0c;可以逐个读取&#xff09;&#xff0c;要从中随机抽取一个数字&#xff0c;求抽得的数字下标。常规的抽样方法是&#xff0c;先读取所有的…...

数据结构预算法之买股票最好时机动态规划(可买卖多次)

一.题目二.思路在动规五部曲中&#xff0c;这个区别主要是体现在递推公式上&#xff0c;其他都和上一篇文章思路是一样的。所以我们重点讲一讲递推公式。这里重申一下dp数组的含义&#xff1a;dp[i][0] 表示第i天持有股票所得现金。dp[i][1] 表示第i天不持有股票所得最多现金如…...

华为OD机试真题Java实现【蛇形矩阵】真题+解题思路+代码(20222023)

蛇形矩阵 蛇形矩阵是由1开始的自然数依次排列成的一个矩阵上三角形。 例如,当输入5时,应该输出的三角形为: 1 3 6 10 15 2 5 9 14 4 8 13 7 12 11请注意本题含有多组样例输入。 🔥🔥🔥🔥🔥👉👉👉👉👉👉 华为OD机试(Java)真题目录汇总 输入描述:…...

spring Bean的生命周期 IOC

文章目录 1. 基础知识1.1 什么是 IoC ?2. 扩展方法3. 源码入口1. 基础知识 1.1 什么是 IoC ? IoC,控制反转,想必大家都知道,所谓的控制反转,就是把 new 对象的权利交给容器,所有的对象都被容器控制,这就叫所谓的控制反转。 IoC 很好地体现了面向对象设计法则之一 —…...

详解cors跨域

文章目录同源策略cors基本概念cors跨域方式简单请求 simple request非简单请求- 预检请求CORS兼容情况CORS总结同源策略 在以前的一篇博客中有介绍&#xff0c;同源策略是一种安全机制&#xff0c;为了预防某些恶意的行为&#xff0c;限制浏览器从不同源文档和脚本进行交互的行…...

ARM uboot 源码分析7 - uboot的命令体系

一、uboot 命令体系基础 1、使用 uboot 命令 (1) uboot 启动后进入命令行环境下&#xff0c;在此输入命令按回车结束&#xff0c;uboot 会收取这个命令然后解析&#xff0c;然后执行。 2、uboot 命令体系实现代码在哪里 (1) uboot 命令体系的实现代码在 uboot/common/cmd_xx…...

物理服务器与云服务器备份相同吗?

自从云计算兴起以来&#xff0c;服务器备份已经从两阶段的模拟操作演变为由云服务器备份软件执行的复杂的多个过程。但是支持物理服务器和虚拟服务器之间的备份相同吗?主要区别是什么?我们接下来将详细讨论这个问题。 物理服务器与云服务器备份的区别 如果您不熟悉虚拟服务器…...

【Linux】system V共享内存 | 消息队列 | 信号量

​&#x1f320; 作者&#xff1a;阿亮joy. &#x1f386;专栏&#xff1a;《学会Linux》 &#x1f387; 座右铭&#xff1a;每个优秀的人都有一段沉默的时光&#xff0c;那段时光是付出了很多努力却得不到结果的日子&#xff0c;我们把它叫做扎根 目录&#x1f449;system V共…...

FSC的宣传许可 答疑

【FSC的宣传许可 答疑】问&#xff1a;已经采购了认证产品但没有贴FSC标签&#xff0c;是否可以申请宣传许可&#xff1f;答&#xff1a;不可以。要宣传您采用了FSC认证产品的前提条件之一是产品必须是认证且贴有标签的。如果产品没有贴标&#xff0c;则不可申请宣传许可。您的…...

Leetcode力扣秋招刷题路-0100

从0开始的秋招刷题路&#xff0c;记录下所刷每道题的题解&#xff0c;帮助自己回顾总结 100. 相同的树 给你两棵二叉树的根节点 p 和 q &#xff0c;编写一个函数来检验这两棵树是否相同。 如果两个树在结构上相同&#xff0c;并且节点具有相同的值&#xff0c;则认为它们是…...

协作对象死锁及其解决方案

协作对象死锁及其解决方案 1.前言 在遇到转账等的需要保证线程安全的情况时&#xff0c;我们通常会使用加锁的方式来保证线程安全&#xff0c;但如果无法合理的使用锁&#xff0c;很可能导致死锁。或者有时我们使用线程池来进行资源的使用&#xff0c;如调用数据库&#xff0…...

良许也成为砖家啦~

大家好&#xff0c;我是良许。 没错&#xff0c;良许成为砖家啦&#xff0c;绝不是口嗨&#xff0c;有图有真相&#xff01; 有人会说&#xff0c;咦&#xff0c;这明明是严宇啊&#xff0c;跟你良许有啥关系&#xff1f; 额。。老读者应该知道良许的来历—— 鄙人真名严宇&a…...

Java中的编程细节

前言&#xff1a; 学习过程中有不少时候遇到一些看似简单&#xff0c;做起来事倍功半的问题。我也想自己是个聪明人&#xff0c;学东西一听就懂&#xff0c;一学就会&#xff0c;马上就能灵活应用。但这种事不能强求&#xff0c;要么自己要看个十遍二十遍最后理清逻辑&#xf…...

Yolov8从pytorch到caffe (一) 环境搭建

Yolov8从pytorch到caffe (一) 环境搭建 1. 创建虚拟环境2. 安装pytorch与v8相关库3. 测试安装是否成功4. 测试推理图像在windows上配置YOLOv8的环境,训练自己的数据集并转换到caffemodel1. 创建虚拟环境 利用conda创建虚拟环境 conda create -n yolo python=3.8 -y 并进入ac…...

2023年CDGA考试-第16章-数据管理组织与角色期望(含答案)

2023年CDGA考试-第16章-数据管理组织与角色期望(含答案) 单选题 1.在定义任何新组织或尝试改进现有组织之前了解当前组织的哪些方面非常重要? A.企业文化、运营模式和人员 B.业务战略、技术战略、数据战略 C.工具、方法和流程 D.事业环境因素、组织过程资产,行动路线图 …...

零门槛NAS搭建:WinNAS如何让普通电脑秒变私有云?

一、核心优势&#xff1a;专为Windows用户设计的极简NAS WinNAS由深圳耘想存储科技开发&#xff0c;是一款收费低廉但功能全面的Windows NAS工具&#xff0c;主打“无学习成本部署” 。与其他NAS软件相比&#xff0c;其优势在于&#xff1a; 无需硬件改造&#xff1a;将任意W…...

<6>-MySQL表的增删查改

目录 一&#xff0c;create&#xff08;创建表&#xff09; 二&#xff0c;retrieve&#xff08;查询表&#xff09; 1&#xff0c;select列 2&#xff0c;where条件 三&#xff0c;update&#xff08;更新表&#xff09; 四&#xff0c;delete&#xff08;删除表&#xf…...

【人工智能】神经网络的优化器optimizer(二):Adagrad自适应学习率优化器

一.自适应梯度算法Adagrad概述 Adagrad&#xff08;Adaptive Gradient Algorithm&#xff09;是一种自适应学习率的优化算法&#xff0c;由Duchi等人在2011年提出。其核心思想是针对不同参数自动调整学习率&#xff0c;适合处理稀疏数据和不同参数梯度差异较大的场景。Adagrad通…...

Cesium1.95中高性能加载1500个点

一、基本方式&#xff1a; 图标使用.png比.svg性能要好 <template><div id"cesiumContainer"></div><div class"toolbar"><button id"resetButton">重新生成点</button><span id"countDisplay&qu…...

如何在看板中体现优先级变化

在看板中有效体现优先级变化的关键措施包括&#xff1a;采用颜色或标签标识优先级、设置任务排序规则、使用独立的优先级列或泳道、结合自动化规则同步优先级变化、建立定期的优先级审查流程。其中&#xff0c;设置任务排序规则尤其重要&#xff0c;因为它让看板视觉上直观地体…...

LeetCode - 394. 字符串解码

题目 394. 字符串解码 - 力扣&#xff08;LeetCode&#xff09; 思路 使用两个栈&#xff1a;一个存储重复次数&#xff0c;一个存储字符串 遍历输入字符串&#xff1a; 数字处理&#xff1a;遇到数字时&#xff0c;累积计算重复次数左括号处理&#xff1a;保存当前状态&a…...

HTML 列表、表格、表单

1 列表标签 作用&#xff1a;布局内容排列整齐的区域 列表分类&#xff1a;无序列表、有序列表、定义列表。 例如&#xff1a; 1.1 无序列表 标签&#xff1a;ul 嵌套 li&#xff0c;ul是无序列表&#xff0c;li是列表条目。 注意事项&#xff1a; ul 标签里面只能包裹 li…...

剑指offer20_链表中环的入口节点

链表中环的入口节点 给定一个链表&#xff0c;若其中包含环&#xff0c;则输出环的入口节点。 若其中不包含环&#xff0c;则输出null。 数据范围 节点 val 值取值范围 [ 1 , 1000 ] [1,1000] [1,1000]。 节点 val 值各不相同。 链表长度 [ 0 , 500 ] [0,500] [0,500]。 …...

跨链模式:多链互操作架构与性能扩展方案

跨链模式&#xff1a;多链互操作架构与性能扩展方案 ——构建下一代区块链互联网的技术基石 一、跨链架构的核心范式演进 1. 分层协议栈&#xff1a;模块化解耦设计 现代跨链系统采用分层协议栈实现灵活扩展&#xff08;H2Cross架构&#xff09;&#xff1a; 适配层&#xf…...

CocosCreator 之 JavaScript/TypeScript和Java的相互交互

引擎版本&#xff1a; 3.8.1 语言&#xff1a; JavaScript/TypeScript、C、Java 环境&#xff1a;Window 参考&#xff1a;Java原生反射机制 您好&#xff0c;我是鹤九日&#xff01; 回顾 在上篇文章中&#xff1a;CocosCreator Android项目接入UnityAds 广告SDK。 我们简单讲…...