如何应对Android面试官->实战高级UI,用自定义View画一条锦鲤(下)

2024-02-19 19:28

本文主要是介绍如何应对Android面试官->实战高级UI,用自定义View画一条锦鲤(下),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言


上一章我们用自定义View绘制了一条小鱼,本章我们让这条小鱼游动起来;

涉及的知识点

infoflow 2024-02-19 10-05-04.png

小鱼的原地摆动


实现小鱼的摆动,我们可以通过属性动画 ValueAnimator 来实现,这里先简单介绍下属性动画

属性动画(ValueAnimator)

  1. ValueAnimator 没有重绘,所以需要自己调用 addUpdateListener 方法,结合 AnimatorUpdateListener 使用;
  2. 操作的对象的属性不一定要有 get set 方法;
  3. 默认插值器为 AccelerateDecelerateInterpolator;

基础用法

public void init() {//...// // 动画周期ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1f);// 动画时长valueAnimator.setDuration(1000);// 重复模式,重新开始valueAnimator.setRepeatMode(ValueAnimator.RESTART);// 重复次数 无线循环valueAnimator.setRepeatCount(ValueAnimator.INFINITE);valueAnimator.setInterpolator(new LinearInterpolator());valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animator) {animatedValue = animator.getAnimatedValue();invalidateSelef();}});valueAnimator.start();
}

鱼头的摆动

上一章节我们有讲到,使用

// 鱼的主要朝向角度
private float fishMainAngle = 0;

来控制鱼的朝向,那么我们就可以通过它来让我们的鱼头摆动起来,假设我们让鱼头摆动 10 度左右,只需要让 fishMainAngle * animatedValue * 10 就可以了,我们来运行看下效果

@Override
public void draw(@NonNull Canvas canvas) {float fishAngle = fishMainAngle + animatedValue * 10;
}

SVID_20240219_103258_1.gif

可以看到,鱼头已经摆动了起来,但是整体还不符合我们的预期,我们接着来调整鱼尾的摆动;

鱼尾的摆动

实现鱼尾的摆动,思路和鱼头一样,只需要让鱼尾的角度改变就行;

/*** 画节肢** @param canvas               画布* @param bottomCenterPoint    大圆中心点坐标* @param findMiddleCircleLength 中圆长度* @param bigCircleRadius 大圆半径* @param middleCircleRadius 中圆半径* @param fishAngle 角度*/
private PointF makeSegment(Canvas canvas, PointF bottomCenterPoint, float findMiddleCircleLength,float bigCircleRadius, float middleCircleRadius, float fishAngle, boolean hasBigCircle) {float segmentAngle = fishAngle + animatedValue * 20;// 计算中圆坐标(梯形上底圆的圆心)PointF upperCenterPoint = calculatePoint(bottomCenterPoint, findMiddleCircleLength, fishAngle - 180);// 计算梯形的四个点PointF bottomLeftPoint = calculatePoint(bottomCenterPoint, bigCircleRadius, segmentAngle + 90);PointF bottomRightPoint = calculatePoint(bottomCenterPoint, bigCircleRadius, segmentAngle - 90);PointF upperLeftPoint = calculatePoint(upperCenterPoint, middleCircleRadius, segmentAngle + 90);PointF upperRightPoint = calculatePoint(upperCenterPoint, middleCircleRadius, segmentAngle - 90);if (hasBigCircle) {// 画大圆canvas.drawCircle(bottomCenterPoint.x, bottomCenterPoint.y, bigCircleRadius, mPaint);}// 画小圆canvas.drawCircle(upperCenterPoint.x, upperCenterPoint.y, middleCircleRadius, mPaint);// 画梯形mPath.reset();mPath.moveTo(upperLeftPoint.x, upperLeftPoint.y);mPath.lineTo(upperRightPoint.x, upperRightPoint.y);mPath.lineTo(bottomRightPoint.x, bottomRightPoint.y);mPath.lineTo(bottomLeftPoint.x, bottomLeftPoint.y);canvas.drawPath(mPath, mPaint);return upperCenterPoint;
}

我们运行看下效果:

SVID_20240219_104208_1.gif

可以看到,鱼尾也能摆动了起来,但是如果我们想让鱼头和鱼尾不同的频率,显然现有的一个属性动画还是不够的,那么我们如何实现让鱼头和鱼尾不同的频率呢?

我们可以使用两个 ValueAnimator 来分别实现鱼头和鱼尾的摆动,但是我们能使用一个 ValueAnimator 来实现鱼头和鱼尾不同频率的摆动吗?让我们来一探究竟;

鱼头鱼尾不同频率的摆动

上一章我们讲了正弦函数,正弦函数的一个周期是360,如果我们的属性动画的周期是 ValueAnimator.ofFloat(0,3600) 那么就相当于是1s 10个周期,如果我们给 animatedValue 取一个正弦函数,并且让它的周期发生改变,那么我们的鱼头和鱼尾的摆动频率就不一样了;

sin(animatedValue) ==> animatedValue 取值是(0-360) 1s执行1次

sin(animatedValue * 2) ==> animatedValue 取值是(0-360)1s执行2次

也就是说 sin(0-360) 的取值是 -1 ~ 1 这样的一个范围,那么我们的鱼头摆动的角度就是 -10 ~ 10 这样的一个范围,鱼头就实现了从 -10 到 10 的一个左右的摆动了;

也就是说 sin((0-360)X2) 的取值是 -1 ~ 1 & -1 ~ 1 这样的一个范围,那么我们的鱼头摆动的角度就是 -10 ~ 10 这样的一个范围,鱼头就实现了从 -10 到 10 的一个左右的摆动了,并且1s内摆动了2次;

同理,鱼尾也是这样的一个范围,我们只需要调整这个sin(x)的值,让它的结果值不一样,那么摆动的频率也就不一样了;

节肢是分为节肢1和节肢2,节肢1和节肢2的摆动频率也不一样,并且节肢1的摆动,带动了节肢2的摆动,根据正余弦函数 sin 和 cos 正好相差 90,节肢1和节肢2可以采用 sin 和 cos 来实现方向和频率上的变化,具体实现如下:

// 动画周期
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 360f);
@Override
public void draw(@NonNull Canvas canvas) {float fishAngle = (float) (fishMainAngle + Math.sin(Math.toRadians(animatedValue)) * 10);
}

节肢1和节肢2的变化

/*** 画节肢** @param canvas               画布* @param bottomCenterPoint    大圆中心点坐标* @param findMiddleCircleLength 中圆长度* @param bigCircleRadius 大圆半径* @param middleCircleRadius 中圆半径* @param fishAngle 角度*/
private PointF makeSegment(Canvas canvas, PointF bottomCenterPoint, float findMiddleCircleLength,float bigCircleRadius, float middleCircleRadius, float fishAngle, boolean hasBigCircle) {float segmentAngle;if (hasBigCircle) {segmentAngle = (float) (fishAngle + Math.cos(Math.toRadians(animatedValue * 1.5)) * 15);} else {segmentAngle = (float) (fishAngle + Math.sin(Math.toRadians(animatedValue * 1.5)) * 20);}// 计算中圆坐标(梯形上底圆的圆心)PointF upperCenterPoint = calculatePoint(bottomCenterPoint, findMiddleCircleLength, segmentAngle - 180);// 计算梯形的四个点PointF bottomLeftPoint = calculatePoint(bottomCenterPoint, bigCircleRadius, segmentAngle + 90);PointF bottomRightPoint = calculatePoint(bottomCenterPoint, bigCircleRadius, segmentAngle - 90);PointF upperLeftPoint = calculatePoint(upperCenterPoint, middleCircleRadius, segmentAngle + 90);PointF upperRightPoint = calculatePoint(upperCenterPoint, middleCircleRadius, segmentAngle - 90);if (hasBigCircle) {// 画大圆canvas.drawCircle(bottomCenterPoint.x, bottomCenterPoint.y, bigCircleRadius, mPaint);}// 画小圆canvas.drawCircle(upperCenterPoint.x, upperCenterPoint.y, middleCircleRadius, mPaint);// 画梯形mPath.reset();mPath.moveTo(upperLeftPoint.x, upperLeftPoint.y);mPath.lineTo(upperRightPoint.x, upperRightPoint.y);mPath.lineTo(bottomRightPoint.x, bottomRightPoint.y);mPath.lineTo(bottomLeftPoint.x, bottomLeftPoint.y);canvas.drawPath(mPath, mPaint);return upperCenterPoint;
}

我们运行看下效果:

SVID_20240219_111823_1.gif

可以看到 鱼头、鱼尾不同的摆动频率;

三角形鱼尾的摆动

接下来我们来让三角形也摆动起来,三角形的摆动频率是和节肢2的摆动频率一样的,所以我们可以让这两个值保持一致来看下效果

private void makeTriangle(Canvas canvas, PointF startPoint, float findTriangleLength,float bigCircleRadius, float fishAngle) {float triangleAngle = (float) (fishAngle + Math.sin(Math.toRadians(animatedValue * 1.5)) * 20);// 三角形底边中心点坐标PointF centerPoint = calculatePoint(startPoint, findTriangleLength, triangleAngle);// 三角形底边两点PointF leftPoint = calculatePoint(centerPoint, bigCircleRadius, triangleAngle + 90);PointF rightPoint = calculatePoint(centerPoint, bigCircleRadius, triangleAngle - 90);mPath.reset();mPath.moveTo(startPoint.x, startPoint.y);mPath.lineTo(leftPoint.x,  leftPoint.y);mPath.lineTo(rightPoint.x, rightPoint.y);canvas.drawPath(mPath, mPaint);
}

SVID_20240219_124315_1.gif

可以看到,三角形的尾巴也摆动了起来;但是效果一卡一卡的,我们来优化一下;首先我们来看下为什么会一卡一卡的,这个是因为动画结束之后并没有回到起点开始,这个是因为我们在频率变化的地方乘以了1.5
也就是 360 * 1.5 / 360 = 1.5,这个结果不是整数,也就是说不是 360 的整数倍,我们需要把它设置成 360 的整数倍,修改如下

ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 720f);

720 * 1.5 / 360 = 3 是360的整数倍,我们来看下效果:

SVID_20240219_125055_1.gif

卡顿效果没有了;

小鱼的三角形尾巴旋转

这个就比较简单了,周期性的改变这个值即可

// 画三角形
float findTriangleAngle = (float) Math.abs(Math.sin(Math.toRadians(animatedValue * 1.5)) * BIG_CIRCLE_RADIUS);
makeTriangle(canvas, middlePointF, FIND_TRIANGLE_LENGTH, findTriangleAngle, fishAngle);
makeTriangle(canvas, middlePointF, FIND_TRIANGLE_LENGTH - 10,findTriangleAngle - 20, fishAngle);

运行看下效果:

SVID_20240219_132806_1.gif

三角形尾巴旋转了起来;

小鱼的游动


我们接下来实现小鱼的游动,想要实现小鱼的游动,我们需要把这个 Drawable 放到 ViewGroup 中,并且在这个 ViewGroup 中实现点击画一个水波纹,同时让小鱼游动过去,我们先来搭建一个简单的架子;

public class FishLayout extends RelativeLayout {private Paint mPaint;private ImageView ivFish;private Fish fishDrawable;public FishLayout(Context context) {this(context, null);}public FishLayout(Context context, AttributeSet attrs) {this(context, attrs, 0);}public FishLayout(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init(context);}private void init(Context context) {// ViewGroup 默认不执行 onDraw 方法setWillNotDraw(false);mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);mPaint.setDither(true);mPaint.setStyle(Paint.Style.STROKE);mPaint.setStrokeWidth(8);ivFish = new ImageView(context);LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);fishDrawable = new Fish();ivFish.setLayoutParams(layoutParams);ivFish.setImageDrawable(fishDrawable);addView(ivFish);}
}

水波纹的绘制

水波纹的绘制,我们同样采用属性动画(ObjectAnimator)来实现,区别于 ValueAnimator,ObjectAnimator 在使用的过程中要求属性必须实现 get 和 set 方法;然后我们接入 canvas 的 drawCircle 方法来实现水波纹的绘制

private float touchX;
private float touchY;
private float ripple;
private int alpha;@Override
protected void onDraw(Canvas canvas) {super.onDraw(canvas);mPaint.setAlpha(alpha);canvas.drawCircle(touchX, touchY, ripple * 150, mPaint);invalidate();
}@Override
public boolean onTouchEvent(MotionEvent event) {touchX = event.getX();touchY = event.getY();ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(this, "ripple", 0, 1f);objectAnimator.setDuration(1000);objectAnimator.start();return super.onTouchEvent(event);
}public float getRipple() {return ripple;
}public void setRipple(float ripple) {alpha = (int) (100 * (1 - ripple));this.ripple = ripple;
}

我们运行看下效果:

SVID_20240219_135332_1.gif

我们实现了点击水波纹的效果;

小鱼的移动

小鱼的游动,其实是一个三阶贝塞尔曲线,三阶贝塞尔曲线,需要四个控制点,我们这里采用鱼头圆心点,鱼的身体中心点,点击点,以及这三个点击组成的夹角的一半

infoflow 2024-02-19 14-02-33.png

也就是这四个点,我们来分别计算这四个点的坐标,然后绘制三阶贝塞尔曲线;这里我们就用到了向量夹角的计算公式

向量的夹角公式计算夹角cosAOB = (OA x OB)/(|OA|*|OB|)其中 OA x OB 是向量的数量积,计算过程如下:

OA = (Ax-Ox,Ay-Oy)

OB = (Bx-Ox,By-Oy)

OA x OB = (Ax-Ox)(Bx-Ox)+(Ay-Oy)x(By-Oy)

|OA|表示线段OA的模即OA的长度

所以实现如下:

// cosAOB
// OA*OB = (Ax-Ox)*(Bx-Ox) + (Ay-Oy)*(By-Oy)
float AOB = (A.x - O.x) * (B.x - O.x) + (A.y - O.y) * (B.y-O.y);
float OALength = (float) Math.sqrt((A.x - O.x) * (A.x - O.x) + (A.y - O.y) * (A.y - O.y));
float OBLength = (float) Math.sqrt((B.x - O.x) * (B.x - O.x) + (B.y - O.y) * (B.y - O.y));
float cosAOB = AOB / (OALength * OBLength);
// 反余弦
float angleAOB = (float) Math.toDegrees(Math.acos(cosAOB));

我们还需要决定鱼的转向,也就是说点击点在鱼的右侧,鱼头转向右边游动,点击在鱼的左侧,鱼头转向左边游动;

鱼朝哪边转向.png

也就是说:我们以AO中线为界限,分为四个区域来看

infoflow 2024-02-19 16-19-41.png

也就是 鱼的右边小于0,鱼的左边大于0,所以计算如下:

// AB连线与x轴的夹角的 tan 值 - OB连线与x轴的夹角的 tan 值
float direction = (A.y - B.y) / (A.x - B.x) - (O.y - B.y) / (O.x - B.x);
if (direction == 0) {if (AOB >= 0) {return 0f;} else {return 180f;}
} else {if (direction > 0) {return -angleAOB;} else {return angleAOB;}
}

所以我们的起始点坐标为:

// 鱼的重心,相当于ImageView的坐标
PointF fishRelativeMiddle = fishDrawable.getMiddlePoint();
// 鱼的重心,绝对坐标 -- 起始点坐标
PointF fishMiddle = new PointF(ivFish.getX() + fishRelativeMiddle.x, ivFish.getY() + fishRelativeMiddle.y);

控制点1的坐标为:

// 鱼头圆心坐标 -- 控制点1坐标
PointF headPoint = fishDrawable.getHeadPoint();
PointF fishHead = new PointF(ivFish.getX() + headPoint.x, ivFish.getY() + headPoint.y);

点击点坐标,结束点坐标:

// 点击坐标 -- 结束点坐标
PointF touch = new PointF(touchX, touchY);
float angle = includeAngle(fishMiddle, fishHead, touch) / 2;
float delta = includeAngle(fishMiddle, new PointF(fishMiddle.x + 1, fishMiddle.y), fishHead);

控制点2的坐标为:

// 控制点2的坐标
PointF controlPoint = fishDrawable.calculatePoint(fishMiddle, fishDrawable.getHEAD_RADIUS() * 1.6f, angle + delta);

三阶贝塞尔曲线绘制

Path path = new Path();
path.moveTo(fishMiddle.x - fishRelativeMiddle.x, fishMiddle.y - fishRelativeMiddle.y);
path.cubicTo(fishHead.x - fishRelativeMiddle.x, fishHead.y - fishRelativeMiddle.y,controlPoint.x - fishRelativeMiddle.x, controlPoint.y - fishRelativeMiddle.y,touchX - fishRelativeMiddle.x, touchY - fishRelativeMiddle.y);
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(ivFish, "x", "y", path);
objectAnimator.setDuration(2000);
objectAnimator.start();

小鱼移动的完整实现如下:

private void move() {// 鱼的重心,相当于ImageView的坐标PointF fishRelativeMiddle = fishDrawable.getMiddlePoint();// 鱼的重心,绝对坐标 -- 起始点坐标PointF fishMiddle = new PointF(ivFish.getX() + fishRelativeMiddle.x, ivFish.getY() + fishRelativeMiddle.y);// 鱼头圆心坐标 -- 控制点1坐标PointF headPoint = fishDrawable.getHeadPoint();PointF fishHead = new PointF(ivFish.getX() + headPoint.x, ivFish.getY() + headPoint.y);// 点击坐标 -- 结束点坐标PointF touch = new PointF(touchX, touchY);float angle = includeAngle(fishMiddle, fishHead, touch) / 2;float delta = includeAngle(fishMiddle, new PointF(fishMiddle.x + 1, fishMiddle.y), fishHead);// 控制点2的坐标PointF controlPoint = fishDrawable.calculatePoint(fishMiddle, fishDrawable.getHEAD_RADIUS() * 1.6f, angle + delta);Path path = new Path();path.moveTo(fishMiddle.x - fishRelativeMiddle.x, fishMiddle.y - fishRelativeMiddle.y);path.cubicTo(fishHead.x - fishRelativeMiddle.x, fishHead.y - fishRelativeMiddle.y,controlPoint.x - fishRelativeMiddle.x, controlPoint.y - fishRelativeMiddle.y,touchX - fishRelativeMiddle.x, touchY - fishRelativeMiddle.y);ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(ivFish, "x", "y", path);objectAnimator.setDuration(2000);objectAnimator.start();
}public float includeAngle(PointF O, PointF A, PointF B) {// cosAOB// OA*OB = (Ax-Ox)*(Bx-Ox) + (Ay-Oy)*(By-Oy)float AOB = (A.x - O.x) * (B.x - O.x) + (A.y - O.y) * (B.y-O.y);float OALength = (float) Math.sqrt((A.x - O.x) * (A.x - O.x) + (A.y - O.y) * (A.y - O.y));float OBLength = (float) Math.sqrt((B.x - O.x) * (B.x - O.x) + (B.y - O.y) * (B.y - O.y));float cosAOB = AOB / (OALength * OBLength);// 反余弦float angleAOB = (float) Math.toDegrees(Math.acos(cosAOB));// AB连线与x轴的夹角的 tan 值 - OB连线与x轴的夹角的 tan 值float direction = (A.y - B.y) / (A.x - B.x) - (O.y - B.y) / (O.x - B.x);if (direction == 0) {if (AOB >= 0) {return 0f;} else {return 180f;}} else {if (direction > 0) {return -angleAOB;} else {return angleAOB;}}
}

在 onTouchEvent 中调用这个 move 方法

@Override
public boolean onTouchEvent(MotionEvent event) {touchX = event.getX();touchY = event.getY();ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(this, "ripple", 0, 1f);objectAnimator.setDuration(1000);objectAnimator.start();move();return super.onTouchEvent(event);
}

之前设置的鱼的半径比较大,我们调小一些,方便看一屏下看运行效果

/*** 鱼的长度值*/
// 绘制鱼头的半径
private float HEAD_RADIUS = 50;

我们来运行看下效果:

SVID_20240219_182339_1.gif

可以看到,我们实现了小鱼的点击移动

小鱼游动的时候,鱼尾摆动频率增加

接下来我们来实现,点击移动的时候,鱼尾的摆动频率增加,这里依然借助于属性动画

objectAnimator.addListener(new AnimatorListenerAdapter() {@Overridepublic void onAnimationEnd(Animator animation) {super.onAnimationEnd(animation);}@Overridepublic void onAnimationStart(Animator animation) {super.onAnimationStart(animation);}
});

我们在游动动画开始的时候,给鱼尾的值增加一个倍数,在所有使用 animatedValue 的地方使用这个倍数即可

float fishAngle = (float) (fishMainAngle + Math.sin(Math.toRadians(animatedValue * frequence)) * 10);
float findTriangleAngle = (float) Math.abs(Math.sin(Math.toRadians(animatedValue * 1.5 * frequence)) * BIG_CIRCLE_RADIUS);
float triangleAngle = (float) (fishAngle + Math.sin(Math.toRadians(animatedValue * 1.5 * frequence)) * 20);
if (hasBigCircle) {segmentAngle = (float) (fishAngle + Math.cos(Math.toRadians(animatedValue * 1.5 * frequence)) * 15);
} else {segmentAngle = (float) (fishAngle + Math.sin(Math.toRadians(animatedValue * 1.5 * frequence)) * 20);
}

然后在我们动画开始的时候,调大这个值,动画结束的时候恢复这个值

objectAnimator.addListener(new AnimatorListenerAdapter() {@Overridepublic void onAnimationEnd(Animator animation) {super.onAnimationEnd(animation);fishDrawable.setFrequence(1f);}@Overridepublic void onAnimationStart(Animator animation) {super.onAnimationStart(animation);fishDrawable.setFrequence(3f);}
});

我们运行看下效果:

SVID_20240219_183151_1.gif

可以看到在游动的时候,鱼尾的摆动频率加快了;

鱼头转向

接下来我们来实现鱼的转向,在游动的时候先转向,然后游到点击的位置;鱼头的转向其实也是一个贝塞尔曲线

图片 1.png

转向的实现其实就是鱼头的切线轨迹和游动路线轨迹重合,鱼头切线变化的角度和路径的角度保持一致,我们就能实现鱼头的转向了;

而 切线其实就是我们的 tan 值;切线值的获取需要用到系统提供的 PathMeasure;

final PathMeasure pathMeasure = new PathMeasure(path, false);
final float[] tan = new float[2];
objectAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {float fraction = animation.getAnimatedFraction();pathMeasure.getPosTan(pathMeasure.getLength() * fraction, null, tan);float angle = (float) Math.toDegrees(Math.atan2(-tan[1], tan[0]));fishDrawable.setFishMainAngle(angle);}
});

我们运行看下效果:

12e0e065-7315-43f7-8aec-6e81a39a9397.gif

我们实现了点击的时候鱼的转向逻辑;

好了,到这里我们就实现了开篇的时候的完整逻辑;

下一章预告


Handler 原理解析;

欢迎三连


来都来了,点个关注,点个赞吧,你的支持是我最大的动力~~~

这篇关于如何应对Android面试官->实战高级UI,用自定义View画一条锦鲤(下)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/725699

相关文章

SpringBoot实现RSA+AES自动接口解密的实战指南

《SpringBoot实现RSA+AES自动接口解密的实战指南》在当今数据泄露频发的网络环境中,接口安全已成为开发者不可忽视的核心议题,RSA+AES混合加密方案因其安全性高、性能优越而被广泛采用,本... 目录一、项目依赖与环境准备1.1 Maven依赖配置1.2 密钥生成与配置二、加密工具类实现2.1

Nginx进行平滑升级的实战指南(不中断服务版本更新)

《Nginx进行平滑升级的实战指南(不中断服务版本更新)》Nginx的平滑升级(也称为热升级)是一种在不停止服务的情况下更新Nginx版本或添加模块的方法,这种升级方式确保了服务的高可用性,避免了因升... 目录一.下载并编译新版Nginx1.下载解压2.编译二.替换可执行文件,并平滑升级1.替换可执行文件

精选20个好玩又实用的的Python实战项目(有图文代码)

《精选20个好玩又实用的的Python实战项目(有图文代码)》文章介绍了20个实用Python项目,涵盖游戏开发、工具应用、图像处理、机器学习等,使用Tkinter、PIL、OpenCV、Kivy等库... 目录① 猜字游戏② 闹钟③ 骰子模拟器④ 二维码⑤ 语言检测⑥ 加密和解密⑦ URL缩短⑧ 音乐播放

python panda库从基础到高级操作分析

《pythonpanda库从基础到高级操作分析》本文介绍了Pandas库的核心功能,包括处理结构化数据的Series和DataFrame数据结构,数据读取、清洗、分组聚合、合并、时间序列分析及大数据... 目录1. Pandas 概述2. 基本操作:数据读取与查看3. 索引操作:精准定位数据4. Group

SQL Server跟踪自动统计信息更新实战指南

《SQLServer跟踪自动统计信息更新实战指南》本文详解SQLServer自动统计信息更新的跟踪方法,推荐使用扩展事件实时捕获更新操作及详细信息,同时结合系统视图快速检查统计信息状态,重点强调修... 目录SQL Server 如何跟踪自动统计信息更新:深入解析与实战指南 核心跟踪方法1️⃣ 利用系统目录

Android Paging 分页加载库使用实践

《AndroidPaging分页加载库使用实践》AndroidPaging库是Jetpack组件的一部分,它提供了一套完整的解决方案来处理大型数据集的分页加载,本文将深入探讨Paging库... 目录前言一、Paging 库概述二、Paging 3 核心组件1. PagingSource2. Pager3.

java中pdf模版填充表单踩坑实战记录(itextPdf、openPdf、pdfbox)

《java中pdf模版填充表单踩坑实战记录(itextPdf、openPdf、pdfbox)》:本文主要介绍java中pdf模版填充表单踩坑的相关资料,OpenPDF、iText、PDFBox是三... 目录准备Pdf模版方法1:itextpdf7填充表单(1)加入依赖(2)代码(3)遇到的问题方法2:pd

springboot自定义注解RateLimiter限流注解技术文档详解

《springboot自定义注解RateLimiter限流注解技术文档详解》文章介绍了限流技术的概念、作用及实现方式,通过SpringAOP拦截方法、缓存存储计数器,结合注解、枚举、异常类等核心组件,... 目录什么是限流系统架构核心组件详解1. 限流注解 (@RateLimiter)2. 限流类型枚举 (

PyTorch中的词嵌入层(nn.Embedding)详解与实战应用示例

《PyTorch中的词嵌入层(nn.Embedding)详解与实战应用示例》词嵌入解决NLP维度灾难,捕捉语义关系,PyTorch的nn.Embedding模块提供灵活实现,支持参数配置、预训练及变长... 目录一、词嵌入(Word Embedding)简介为什么需要词嵌入?二、PyTorch中的nn.Em

SpringBoot 异常处理/自定义格式校验的问题实例详解

《SpringBoot异常处理/自定义格式校验的问题实例详解》文章探讨SpringBoot中自定义注解校验问题,区分参数级与类级约束触发的异常类型,建议通过@RestControllerAdvice... 目录1. 问题简要描述2. 异常触发1) 参数级别约束2) 类级别约束3. 异常处理1) 字段级别约束