重学 Android 自定义 View 系列(十一):文字跑马灯剖析
前言
一个可以横向滚动和纵向滚动的自定义文字跑马灯View,支持水平和垂直滚动、多段文本展示、点击事件回调等功能。
该View 由 ScrollTextView,改版而来,效果如下:
1. 功能介绍
ScrollTextView 是基于 SurfaceView 的自定义视图,功能包括:
1.1 水平滚动:
-
文本从右向左滚动,支持多段文本循环展示。
-
每段文本滚动结束后可以设置停留时间。
1.2 垂直滚动:
-
文本从下向上滚动,支持多段文本循环展示。
-
如果文本长度超过视图宽度,会自动触发水平滚动,直到文本完全显示。
1.3 点击事件:
-
支持点击暂停/恢复滚动。
-
提供点击事件回调,返回当前滚动文本的下标和内容。
1.4 自定义属性:
- 支持通过 XML 或代码设置滚动速度、文本颜色、字体大小等属性。
2. 关键技术点解析
2.1. 使用 SurfaceView 实现高效绘制
SurfaceView 是一个独立的绘图表面,适合需要频繁更新的 UI 场景(如滚动文本)。与普通 View 不同,SurfaceView 的绘制在非 UI 线程中进行,因此不会阻塞主线程。
public class ScrollTextView extends SurfaceView implements SurfaceHolder.Callback {private SurfaceHolder surfaceHolder;public ScrollTextView(Context context, AttributeSet attrs) {super(context, attrs);surfaceHolder = getHolder();surfaceHolder.addCallback(this);}@Overridepublic void surfaceCreated(SurfaceHolder holder) {// 启动滚动线程new Thread(new ScrollTextThread()).start();}@Overridepublic void surfaceDestroyed(SurfaceHolder holder) {// 停止滚动线程stopScroll = true;}
}
2.2. 水平滚动逻辑
水平滚动的核心是通过不断更新文本的 X 坐标,实现从右向左的滚动效果。
水平滚动即从初始状态右侧View之外滑入,到左侧完全滑出为止。
if (isHorizontal) { // 水平滚动逻辑if (pauseScroll) {// 暂停sleep(1000);continue;}drawText(viewWidth - textX, textY);// 绘制文本textX += speed;// 文本滚动距离if (textX > viewWidthPlusTextLength) {// 文本从右至左滚动超出屏幕currentTextIndex++;textX = 0;--needScrollTimes;if (currentTextIndex >= textList.size()) {// 文字循环展示if (isScrollForever) {currentTextIndex = 0;} else {stopScroll = true;break;}}measureTextParams(false);}}
2.3. 垂直滚动逻辑
垂直滚动的核心是通过不断更新文本的 Y 坐标,实现从下向上的滚动效果。如果文本长度超过视图宽度,会自动触发水平滚动。
- 阶段1:
-
文本从视图底部之外滚动到基线位置。
-
如果文本超长,准备触发水平滚动。
- 阶段2:
-
(若文本过长)文本从右向左水平滚动,直到文本末尾完全显示。
-
水平滚动完成后,暂停 stayTimes 毫秒。
- 阶段3:
-
文本从基线位置继续向上滚动,直到完全离开视图顶部。
-
切换到下一段文本,重复上述过程。
核心代码:
private void drawVerticalScroll() {if (currentTextIndex >= textList.size()) return;// 计算基线String text = textList.get(currentTextIndex);float fontHeight = paint.getFontMetrics().bottom - paint.getFontMetrics().top;Paint.FontMetrics fontMetrics = paint.getFontMetrics();float distance = (fontMetrics.bottom - fontMetrics.top) / 2 - fontMetrics.bottom;float baseLine = (float) viewHeight / 2 + distance;// 是否需要水平滚动boolean needHorizontalScroll = paint.measureText(text) > viewWidth;// 水平滚动偏移float horizontalOffset = 0;// --- 阶段1:垂直滚动到基线位置(精准停止)---float currentVerticalPos = viewHeight + fontHeight; // 初始位置while (currentVerticalPos > baseLine) {if (stopScroll || isSetNewText) return;if (pauseScroll) {try {Thread.sleep(1000);} catch (InterruptedException ignored) {}continue;}// 计算下一步位置,避免越过基线float nextPos = currentVerticalPos - 3;if (nextPos < baseLine) nextPos = baseLine; // 确保不会越过基线drawText(0, nextPos);currentVerticalPos = nextPos;}// --- 阶段1暂停:基线位置暂停 ---if (needHorizontalScroll) {sleep(1000);} else if (!stopScroll && !isSetNewText) {sleep(stayTimes); // 原基线位置暂停}// --- 阶段2:水平滚动(垂直位置固定为基线)---if (needHorizontalScroll) {// 计算最大水平滚动偏移float maxHorizontalOffset = -(paint.measureText(text) - viewWidth);// 水平滚动while (horizontalOffset > maxHorizontalOffset) {if (stopScroll || isSetNewText) return;horizontalOffset -= speed;drawText(horizontalOffset, baseLine);}// --- 阶段2暂停:水平滚动完成后暂停 ---if (!stopScroll && !isSetNewText) {sleep(stayTimes); // 新增水平滚动完成后的暂停}}// --- 阶段3:继续垂直滚动到视图外 ---for (float i = baseLine; i > -fontHeight; i -= 3) {if (stopScroll || isSetNewText) return;if (pauseScroll) {sleep(1000);continue;}drawText(needHorizontalScroll ? horizontalOffset : 0, i);}// 切换到下一个文本currentTextIndex++;if (currentTextIndex >= textList.size()) {if (isScrollForever) currentTextIndex = 0;else stopScroll = true;}}
2.4. 点击事件处理
@Override
public boolean onTouchEvent(MotionEvent event) {if (clickEnable && event.getAction() == MotionEvent.ACTION_DOWN) {pauseScroll = !pauseScroll; // 切换暂停状态if (onTextClickListener != null) {// 回调当前滚动文本的下标和内容onTextClickListener.onTextClick(currentTextIndex, textList.get(currentTextIndex));}}return true;
}
3. 自定义属性
<declare-styleable name="ScrollTextView"><attr name="clickEnable" format="boolean" /><attr name="isHorizontal" format="boolean" /><attr name="speed" format="integer" /><attr name="stTextColor" format="color" /><attr name="stTextSize" format="dimension" /><attr name="times" format="integer" /><attr name="isScrollForever" format="boolean" /></declare-styleable>
4. 完整代码
public class ScrollTextView extends SurfaceView implements SurfaceHolder.Callback {private final String TAG = "ScrollTextView";private SurfaceHolder surfaceHolder;// SurfaceHolder管理SurfaceView的绘制表面private Paint paint = null;private boolean stopScroll = false;// 停止滚动private boolean pauseScroll = false;// 暂停滚动private boolean clickEnable = false;// 是否启用点击事件public boolean isHorizontal = true;// 是否水平滚动private int speed = 4;// 滚动速度private long stayTimes = 5_000;// 横向滚动每段文字结束后的停留时间private List<String> textList = new ArrayList<>(); // 存储多个文本内容private int currentTextIndex = 0; // 当前滚动的文本索引private float textSize = 20f;// 文本字体大小private int textColor;// 文本颜色private int textBackColor = 0x00000000;// 文本背景颜色private int needScrollTimes = Integer.MAX_VALUE;// 需要滚动的次数private int viewWidth = 0, viewHeight = 0;// 视图的宽度和高度private float textWidth = 0f, textX = 0f, textY = 0f;// 文本的宽度、X坐标、Y坐标private float viewWidthPlusTextLength = 0.0f;// 视图宽度加上文本长度boolean isSetNewText = false;// 是否设置了新的文本boolean isScrollForever = true;// 是否无限滚动private Canvas canvas;// 画布,用于绘制文本private OnTextClickListener onTextClickListener;// 点击事件监听器public interface OnTextClickListener {void onTextClick(int index, String text);}public void setOnTextClickListener(OnTextClickListener listener) {this.onTextClickListener = listener;}public ScrollTextView(Context context) {super(context);}public ScrollTextView(Context context, AttributeSet attrs) {super(context, attrs);surfaceHolder = this.getHolder(); //get The surface holdersurfaceHolder.addCallback(this);paint = new Paint();TypedArray arr = getContext().obtainStyledAttributes(attrs, R.styleable.ScrollTextView);clickEnable = arr.getBoolean(R.styleable.ScrollTextView_clickEnable, clickEnable);isHorizontal = arr.getBoolean(R.styleable.ScrollTextView_isHorizontal, isHorizontal);speed = arr.getInteger(R.styleable.ScrollTextView_speed, speed);textColor = arr.getColor(R.styleable.ScrollTextView_stTextColor, Color.BLACK);textSize = arr.getDimension(R.styleable.ScrollTextView_stTextSize, textSize);needScrollTimes = arr.getInteger(R.styleable.ScrollTextView_times, Integer.MAX_VALUE);isScrollForever = arr.getBoolean(R.styleable.ScrollTextView_isScrollForever, true);paint.setColor(textColor);paint.setTextSize(textSize);paint.setFlags(Paint.ANTI_ALIAS_FLAG);paint.setAntiAlias(true);paint.setFilterBitmap(true);setZOrderOnTop(true); //控制表面视图的表面位于其窗口的顶部。getHolder().setFormat(PixelFormat.TRANSLUCENT);setFocusable(true);arr.recycle();}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);int mHeight = getFontHeight(textSize); //实际的视图高viewWidth = MeasureSpec.getSize(widthMeasureSpec);viewHeight = MeasureSpec.getSize(heightMeasureSpec);// 当布局的宽度或高度是wrap_content,应该初始化ScrollTextView的宽度/高度if (getLayoutParams().width == ViewGroup.LayoutParams.WRAP_CONTENT && getLayoutParams().height == ViewGroup.LayoutParams.WRAP_CONTENT) {setMeasuredDimension(viewWidth, mHeight);viewHeight = mHeight;} else if (getLayoutParams().width == ViewGroup.LayoutParams.WRAP_CONTENT) {setMeasuredDimension(viewWidth, viewHeight);} else if (getLayoutParams().height == ViewGroup.LayoutParams.WRAP_CONTENT) {setMeasuredDimension(viewWidth, mHeight);viewHeight = mHeight;}}/*** surfaceChanged*/@Overridepublic void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {Log.d(TAG, "arg0:" + arg0.toString() + " arg1:" + arg1 + " arg2:" + arg2 + " arg3:" + arg3);}/*** 界面出现,初始化一个新的滚动线程。** @param holder holder*/@Overridepublic void surfaceCreated(SurfaceHolder holder) {stopScroll = false;new Thread(new ScrollTextThread()).start();Log.d(TAG, "ScrollTextTextView is created");}/*** 界面退出,视图消失,自动调用** @param arg0 SurfaceHolder*/@Overridepublic void surfaceDestroyed(SurfaceHolder arg0) {synchronized (this) {stopScroll = true;}Log.d(TAG, "ScrollTextTextView is destroyed");}private int getFontHeight(float fontSize) {Paint paint = new Paint();paint.setTextSize(fontSize);Paint.FontMetrics fm = paint.getFontMetrics();return (int) Math.ceil(fm.descent - fm.ascent);}public int getBackgroundColor() {return textBackColor;}public void setTextBackColor(int color) {this.setBackgroundColor(color);this.textBackColor = color;}public int getTextBackColor() {return textBackColor;}public int getSpeed() {return speed;}public int getCurrentTextIndex() {return currentTextIndex;}public void setTimes(int times) {if (times <= 0) {throw new IllegalArgumentException("times was invalid integer, it must between > 0");} else {needScrollTimes = times;isScrollForever = false;}}/*** 纵向滚动设置每段文字的停留时间* @param stayTimes*/public void setStayTimes(long stayTimes) {this.stayTimes = stayTimes;}public long getStayTimes() {return stayTimes;}public void setTextSize(float textSizeTem) {if (textSizeTem < 20) {throw new IllegalArgumentException("textSize must > 20");} else if (textSizeTem > 900) {throw new IllegalArgumentException("textSize must < 900");} else {this.textSize = sp2px(getContext(), textSizeTem);//重新设置Sizepaint.setTextSize(textSize);//视图区域也要改变measureTextParams(true);//实际的视图高int mHeight = getFontHeight(textSizeTem);android.view.ViewGroup.LayoutParams lp = this.getLayoutParams();lp.width = viewWidth;lp.height = dip2px(this.getContext(), mHeight);this.setLayoutParams(lp);isSetNewText = true;}}private int dip2px(Context context, float dpValue) {final float scale = context.getResources().getDisplayMetrics().density;return (int) (dpValue * scale + 0.5f);}private int sp2px(Context context, float spValue) {float fontScale = context.getResources().getDisplayMetrics().scaledDensity;return (int) (spValue * fontScale + 0.5f);}public int px2sp(Context context, float pxValue) {float fontScale = context.getResources().getDisplayMetrics().scaledDensity;return (int) (pxValue / fontScale + 0.5f);}public void setHorizontal(boolean horizontal) {isHorizontal = horizontal;}// 设置多个文本内容public void setTextList(List<String> textList) {this.textList = textList;this.currentTextIndex = 0;isSetNewText = true;stopScroll = false;measureTextParams(true);}public void setTextColor(@ColorInt int color) {textColor = color;paint.setColor(textColor);}public int getTextColor() {return textColor;}/*** 设置滚动速度** @param speed SCROLL SPEED [4,14] / 0?*/public void setSpeed(int speed) {if (speed > 14 || speed < 4) {throw new IllegalArgumentException("Speed was invalid integer, it must between 4 and 14");} else {this.speed = speed;}}/*** 设置是否永远滚动文本*/public void setScrollForever(boolean scrollForever) {isScrollForever = scrollForever;}public boolean isPauseScroll() {return pauseScroll;}public void setPauseScroll(boolean pauseScroll) {this.pauseScroll = pauseScroll;}public boolean isClickEnable() {return clickEnable;}public void setClickEnable(boolean clickEnable) {this.clickEnable = clickEnable;}@Overridepublic boolean onTouchEvent(MotionEvent event) {if (!clickEnable) {return true;}switch (event.getAction()) {case MotionEvent.ACTION_DOWN:pauseScroll = !pauseScroll;if (onTextClickListener != null) {onTextClickListener.onTextClick(currentTextIndex, textList.get(currentTextIndex));}break;}return true;}private void drawVerticalScroll() {if (currentTextIndex >= textList.size()) return;// 计算基线String text = textList.get(currentTextIndex);float fontHeight = paint.getFontMetrics().bottom - paint.getFontMetrics().top;Paint.FontMetrics fontMetrics = paint.getFontMetrics();float distance = (fontMetrics.bottom - fontMetrics.top) / 2 - fontMetrics.bottom;float baseLine = (float) viewHeight / 2 + distance;// 是否需要水平滚动boolean needHorizontalScroll = paint.measureText(text) > viewWidth;// 水平滚动偏移float horizontalOffset = 0;// --- 阶段1:垂直滚动到基线位置(精准停止)---float currentVerticalPos = viewHeight + fontHeight; // 初始位置while (currentVerticalPos > baseLine) {if (stopScroll || isSetNewText) return;if (pauseScroll) {try {Thread.sleep(1000);} catch (InterruptedException ignored) {}continue;}// 计算下一步位置,避免越过基线float nextPos = currentVerticalPos - 3;if (nextPos < baseLine) nextPos = baseLine; // 确保不会越过基线drawText(0, nextPos);currentVerticalPos = nextPos;}// --- 阶段1暂停:基线位置暂停 ---if (needHorizontalScroll) {sleep(1000);} else if (!stopScroll && !isSetNewText) {sleep(stayTimes); // 原基线位置暂停}// --- 阶段2:水平滚动(垂直位置固定为基线)---if (needHorizontalScroll) {// 计算最大水平滚动偏移float maxHorizontalOffset = -(paint.measureText(text) - viewWidth);// 水平滚动while (horizontalOffset > maxHorizontalOffset) {if (stopScroll || isSetNewText) return;horizontalOffset -= speed;drawText(horizontalOffset, baseLine);}// --- 阶段2暂停:水平滚动完成后暂停 ---if (!stopScroll && !isSetNewText) {sleep(stayTimes); // 新增水平滚动完成后的暂停}}// --- 阶段3:继续垂直滚动到视图外 ---for (float i = baseLine; i > -fontHeight; i -= 3) {if (stopScroll || isSetNewText) return;if (pauseScroll) {sleep(1000);continue;}drawText(needHorizontalScroll ? horizontalOffset : 0, i);}// 切换到下一个文本currentTextIndex++;if (currentTextIndex >= textList.size()) {if (isScrollForever) currentTextIndex = 0;else stopScroll = true;}}/*** 绘制文本*/private synchronized void drawText(float x, float y) {try {canvas = surfaceHolder.lockCanvas();if (canvas == null) return;canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);canvas.drawText(textList.get(currentTextIndex), x, y, paint);} finally {if (canvas != null) {surfaceHolder.unlockCanvasAndPost(canvas);}}}@Overrideprotected void onVisibilityChanged(View changedView, int visibility) {super.onVisibilityChanged(changedView, visibility);this.setVisibility(visibility);}// 测量文本参数private void measureTextParams(boolean isInitialSetup) {if (currentTextIndex >= textList.size()) return;textWidth = paint.measureText(textList.get(currentTextIndex));viewWidthPlusTextLength = viewWidth + textWidth;if (isInitialSetup) {textX = viewWidth - viewWidth / 2; // 第一次创建 ,默认从居中的位置开始滚动Paint.FontMetrics fm = paint.getFontMetrics();float distance = (fm.bottom - fm.top) / 2 - fm.bottom;textY = viewHeight / 2 + distance;}}/*** Scroll thread*/class ScrollTextThread implements Runnable {@Overridepublic void run() {measureTextParams(true);while (!stopScroll && surfaceHolder != null && !Thread.currentThread().isInterrupted()) {if (isHorizontal) { // 水平滚动逻辑if (pauseScroll) {// 暂停sleep(1000);continue;}drawText(viewWidth - textX, textY);// 绘制文本textX += speed;// 文本滚动距离if (textX > viewWidthPlusTextLength) {// 文本从右至左滚动超出屏幕currentTextIndex++;textX = 0;--needScrollTimes;if (currentTextIndex >= textList.size()) {// 文字循环展示if (isScrollForever) {currentTextIndex = 0;} else {stopScroll = true;break;}}measureTextParams(false);}} else { // 垂直滚动逻辑drawVerticalScroll();isSetNewText = false;--needScrollTimes;}if (needScrollTimes <= 0 && isScrollForever) { // 滚动次数小于0,则滚动结束stopScroll = true;}}}}private void sleep(long millis) {try {Thread.sleep(millis);} catch (InterruptedException ignored) {}}}
5. 使用示例
// 文字列表val textList = mutableListOf("关于开展2025年市直公益性岗位征集工作的通知","国家发通知要求规范OTA升级 广汽将构建全域智行安全守护体系","2月28日,工信部联合市场监管总局发布了《关于进一步加强智能网联汽车准入、召回及软件在线升级管理的通知》(以下简称《通知》),为汽车产业健康有序发展提供了顶层指引。对此,广汽集团表示,承诺所有安全投入绝不转嫁用户成本。")// 横向mBind.scrollTextH.apply {setTextList(textList)this.isHorizontal = truespeed = 4textColor = Color.WHITEtextBackColor = R.color.teal_200isClickEnable = truesetOnTextClickListener { index, text ->ToastUtils.show("点击了 $index $text")}}// 竖向mBind.scrollTextV.apply {setTextList(textList) // 文字列表this.isHorizontal = false // 是否横向滚动speed = 4 // 滚动速度stayTimes = 3000 // 停留时间 3stextColor = Color.WHITE // 文字颜色textBackColor = R.color.teal_700 // 文字背景颜色isClickEnable = false // 是否可点击}
6. 最后
以上功能可能只满足部分场景,如需要更多功能,比如:滚动方向,多行滚动等,可自行拓展。直接看代码,可能不容易理解,但是把功能的细节点全都剖析出来,在脑子里有个完整的轮廓,再去看代码,就事半功倍了。see you ~
相关文章:
重学 Android 自定义 View 系列(十一):文字跑马灯剖析
前言 一个可以横向滚动和纵向滚动的自定义文字跑马灯View,支持水平和垂直滚动、多段文本展示、点击事件回调等功能。 该View 由 ScrollTextView,改版而来,效果如下: 1. 功能介绍 ScrollTextView 是基于 SurfaceView 的自定义视…...
Android硬件加速原理解析
Android硬件加速原理解析 一、核心思想 GPU与CPU分工 硬件加速的本质是将图形渲染任务从CPU转移到GPU,利用GPU的并行计算能力处理像素填充、矩阵变换等密集型图形操作12。CPU负责逻辑计算,GPU专注于图形处理,避免单一资源瓶颈57。 图形计算优化 GPU通过专…...
Oracle SQL优化实战要点解析(11)——索引、相关子查询及NL操作(1)
11.1. 充分利用索引有序特性,避免发生大表上的FTS,以及对中间大数据集的排序。 11.1.1. 适用场景 从一个或多个大表(例如:亿行级或TB级数据量)中过滤出全列大数据集(例如:数百万或千万行数据),对该大数据集按其中某列进行排序,最终,只取最前面的少部分数据(例如:…...
题解:AT_past202109_h 最短経路
思路 这一眼就是最短路的题目啊。 为什么不用 Dijkstra 用 死了的 SPFA。因为好写 。 这一题的数据比较小。可以暴力枚举最短路的起点,跑 SPFA,找到符合的直接输出,结束程序。随机数据下 SPFA 平均的时间复杂度为 O ( k n ) O(kn) O(kn)…...
JavaScript基础-算数运算符
在JavaScript编程中,算术运算符是构建程序逻辑的基础工具之一,它们用于执行基本的数学运算,如加法、减法、乘法和除法等。掌握这些运算符不仅有助于进行数值计算,也是编写复杂逻辑的前提条件。本文将详细介绍JavaScript中的各种算…...
Doris vs ClickHouse 企业级实时分析引擎怎么选?
Apache Doris 与 ClickHouse 同作为OLAP领域的佼佼者,在企业级实时分析引擎该如何选择呢。本文将详细介绍 Doris 的优势,并通过直观对比展示两者的关键差异,同时分享一个企业成功用 Doris 替换 ClickHouse 的实践案例,帮助您做出明…...
flask学习3-深入
flask学习-深入 flask学习-深入1. 应用程序结构和生命周期 — Flask 文档 (3.1.x)2.应用程序上下文 — Flask 文档 (3.1.x)3. 请求上下文 — Flask 文档 (3.1.x)上下文的生命周期手动推送上下文关于代理的注意事项4.使用蓝图的模块化应用程序 — Flask 文档 (3.1.x)5.扩…...
任务10:三层交换机配置
CSDN 原创主页:不羁https://blog.csdn.net/2303_76492156?typeblog三层交换机是指在OSI(开放系统互连)模型中的第三层网络层提供路由功能的交换机。它不仅具备二层交换机的交换功能,还能实现路由功能,提供更为灵活的网…...
XSD 对 XML 数据格式验证 java
xsd文件,文件名bean.xsd,放在当前java文件目录下 <?xml version"1.0" encoding"UTF-8"?> <xs:schema xmlns:xs"http://www.w3.org/2001/XMLSchema"><xs:element name"bean"><xs:comple…...
深度学习笔记——神经网络
本文为在拓尔思智能举办的训练营中学习内容的总结,部分内容摘自百度百科 个人在这里推荐一个好用的软件,Trae,主要是免费。 人工神经元是人工神经网络的基本单元。模拟生物神经元,人工神经元有1个或者多个输入(模拟多…...
大语言模型学习--向量数据库
向量数据库 向量 向量是多维数据空间中的一个坐标点。 向量类型 图像向量 文本向量 语音向量 Embedding 非结构化数据转换为向量过程 通过深度学习训练,将真实世界离散数据,投影到高维数据空间上,通过数据在空间中间的距离体现真实世界…...
蓝桥杯 封闭图形个数
蓝桥杯 封闭图形个数 题目 链接 解答 # 数字个数 n int(input()) # 数字 ls input().split() # 统计数字的圈数 o_nums {} for i, x in enumerate(ls):o_num 0for c in x:if int(c) in [0, 4, 6, 9]:o_num 1elif c 8:o_num 2o_nums[i] o_num # 字典根据圆圈数排序 …...
c++快速入门-2
算数运算符 赋值运算符“” 赋值运算符概念 将一个值赋给一个变量。 例:a3;//将3这个值赋给a 赋值是从右向左,都是赋值表达式。 错误示范:3a;//不符合从右向左 b-25;//“”左边只能有一个变量名&…...
URL中的特殊字符与web安全
在现代Web应用中,URL作为客户端与服务器之间的通信桥梁,承载着大量的重要信息。URL中的特殊字符,看似只是一些常见的符号,但在Web安全领域,它们与其他安全知识密切相关,如在Base64编码、SQL注入,…...
Python ❀ Unix时间戳转日期或日期转时间戳工具分享
设计一款Unix时间戳和日期转换工具,其代码如下: from datetime import datetimeclass Change_Date_Time(object):def __init__(self, date_strNone, date_numNone):self.date_str date_strself.date_num date_num# 转时间戳def datetime2timestamp(s…...
H.264,H.265,H.266标准技术改进
关于H.264,H.265,H.266相关资料链接: 标准及中文资料链接 视频编码中的主要技术 视频编码的目标是在保证视频质量的前提下,尽可能减少数据量。以下是视频编码中的核心技术: 块划分(Block Partitioning) 将视频帧划分…...
Crawl4AI: 赋能AI用户的开源智能网页爬虫与数据提取
Crawl4AI: 赋能AI用户的开源智能网页爬虫与数据提取 在当今人工智能时代,网络爬虫扮演着至关重要的角色。它们不仅是数据收集的强大工具,更是驱动机器学习、自然语言处理等技术发展的关键引擎。 然而,对于用户来说,在面对复杂多…...
C++ primer plus 第七节 函数探幽完结版
系列文章目录 C primer plus 第一节 步入C-CSDN博客 C primer plus 第二节 hello world刨析-CSDN博客 C primer plus 第三节 数据处理-CSDN博客 C primer plus 第四节 复合类型-CSDN博客 C primer plus 第五节 循环-CSDN博客 C primier plus 第七节 函数探幽第一部分-CSDN博客 …...
linux | Vim 命令快捷操作
注:本文为过去的 “vim 使用笔记”。 跳转命令 跳转命令 #:向前查找光标当前所在单词,并跳转到该单词的上一个出现位置。*:向后查找光标当前所在单词,并跳转到该单词的下一个出现位置。 行内跳转 0:跳转…...
RuleOS:区块链开发的“新引擎”,点燃Web3创新之火
RuleOS:区块链开发的“新引擎”,点燃Web3创新之火 在区块链技术的浪潮中,RuleOS宛如一台强劲的“新引擎”,为个人和企业开发去中心化应用(DApp)注入了前所未有的动力。它以独特的设计理念和强大的功能特性&…...
梯度本质论:从黎曼流形到神经网络的拓扑寻优
一、微分几何框架下的梯度再诠释 在标准数学分析中,梯度被定义为标量场 f : R n → R f:\mathbb{R}^n→\mathbb{R} f:Rn→R的导数张量 ∇ f ( ∂ f ∂ x 1 , . . . , ∂ f ∂ x n ) \nabla f(\frac{\partial f}{\partial x_1},...,\frac{\partial f}{\partial x_n…...
LVGL直接解码png图片的方法
通过把png文件解码为.C文件,再放到工程中的供使用,这种方式随时速度快(应为已经解码,代码中只要直接加载图片数据显示出来即可),但是不够灵活,适用于哪些简单又不经常需要更换UI的场景下使用。如…...
代码随想录算法营Day59 | 寻找存在的路径, 冗余连接,冗余连接II
寻找存在的路径 这题使用并查集即可。并查集加路径压缩。 #include <iostream> using namespace std; int find(int* father,int u){return father[u] u ? u : father[u] find(father,father[u]); }bool isSame(int* father,int u,int v){return find(father,u) fi…...
物联网智慧农业一体化解决方案-可继续扩展更多使用场景
在智慧农业中,从种子、施肥、灌溉、锄地、农具管理、日常照料到蔬菜档案管理,以及与客户、供应商、市场的对接,可以通过物联网(IoT)、大数据、人工智能(AI)、区块链和云计算等技术,构建一个从生产到销售的全流程数字化、智能化农业生态系统。以下是实现方案和技术路径的…...
第八节:基于Winform框架的串口助手小项目---完结优化《C#编程》
C# 实战串口助手完成! ----------------------------不竭余力,方能成长!-----------WHAPPY 源码:我会分享开源平台,可以艾特me!(2025/3/5)时间紧张任务中! 文章上所说的…...
Python已知后序遍历和中序遍历,求先序遍历
步骤一:树的构建 字典 def createTree(arr1,arr2,tree):if len(arr1)0 and len(arr2)0 :returnroot len(arr1)-1# print(arr1[root],root)flag arr2.index(arr1[root])# print(flag)len_right len(arr2)-flag-1len_left flagif len(arr2[:flag])>1:lchild …...
三维建模与视频融合(3D-Video Integration)技术初探。
三维建模与视频融合(3D-Video Integration)是一种将虚拟三维模型无缝嵌入实拍视频场景的技术,广泛应用于影视特效、增强现实(AR)、游戏开发、广告制作 、视频监控 等领域。 一、技术核心流程 三维建模与动画 使用工具…...
基于uniapp的蓝牙打印功能(佳博打印机已测试)
相关步骤 1.蓝牙打印与低功耗打印的区别2.蓝牙打印流程2.1 搜索蓝牙2.2 连接蓝牙 3.连接蓝牙设备4.获取服务5.写入命令源码gbk.jsglobalindex.ts 1.蓝牙打印与低功耗打印的区别 低功耗蓝牙是一种无线、低功耗个人局域网,运行在 2.4 GHz ISM 频段 1、低功耗蓝牙能够…...
基于Django的协同过滤算法养老新闻推荐系统的设计与实现
基于Django的协同过滤算法养老新闻推荐系统(可改成普通新闻推荐系统使用) 开发工具和实现技术 Pycharm,Python,Django框架,mysql8,navicat数据库管理工具,vue,spider爬虫࿰…...
PROFINET转PROFIBUS从案例剖析网关模块的协议转换功能
一、 案例背景 在当下追求高效协同的工业自动化生产体系里,设备间的无缝互联互通堪称关键要素。某企业的生产车间中,有一台性能稳定的变频器,其配备的是PROFIBUS接口。与此同时,操控整个生产线的核心大脑——西门子1500 PLC&…...
BZOJ2121 字符串游戏
想出来了一半,然后看了眼题解,果然还是和状压不熟导致的。 题目大意 给你一个字符串 L L L 和一个有 n n n 个字符串的集合 S S S,每次操作可以在 L L L 中选择一个子串,如果这个子串在集合 S S S 中,那么这个子…...
计算机组成原理:计算机系统的性能指标
文章目录 什么是计算机系统的性能指标硬件与计算机系统性能的关系软件与计算机系统性的关系计算机硬件的相关性能指标基本性能指标机器字长数据通路带宽主存容量吞吐量响应时间 与运算速度相关的性能指标CPU时钟频率和时钟周期CPICPU执行时间IPCMIPSMFLOPS 使用基准程序进行性能…...
特定领域软件架构DSSA
特定领域软件架构(Domain-Specific Software Architecture DSSA)是专用于解决某一特定类型任务(领域)的架构。它在该领域内提供了一套标准化的组合构建和软件架构,以满足独特需求和约束。DSSA通过结合特定问题领域的专…...
ubuntu22.04安装RAGFlow配合DeepSeek搭建本地知识库
一、简介 RAGFlow 是一个基于对文档的深入理解的开源 RAG(检索增强生成)引擎。当与 LLM 集成时,它能够提供真实的问答功能,并以来自各种复杂格式数据的有根据的引用为后盾。 二、安装 1.环境要求 CPU ≥ 4 核 (x86…...
Python爬虫实战:爬取财金网实时财经信息
注意:以下内容仅供技术研究,请遵守目标网站的robots.txt规定,控制请求频率避免对目标服务器造成过大压力! 一、引言 在当今数字化时代,互联网数据呈爆炸式增长,其中蕴含着巨大的商业价值、研究价值和社会价值。从金融市场动态分析到行业趋势研究,从舆情监测到学术信息收…...
【Python修仙编程】(二) Python3灵源初探(7)
字典的修炼——修仙者的法宝库 师傅玄天真人在他面前摊开一本泛黄的法典,上面写着:“字典是修仙者存储法宝的仓库,能让你快速找到需要的宝贝。” “师傅,字典是啥玩意儿?”林羽挠挠头,一脸懵逼。 “字典…...
Docker 学习(四)——Dockerfile 创建镜像
Dockerfile是一个文本格式的配置文件,其内包含了一条条的指令(Instruction),每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建。有了Dockerfile,当我们需要定制自己额外的需求时,只需在D…...
智慧校园总体方案
1. 智慧校园内涵与发展 智慧校园作为现代教育信息化的产物,其发展经历了从校园网建设到数字校园,再到智慧校园的转变。技术驱动与理念引领并重,以实现网络学习、校务治理、校园文化和校园生活的全面升级。教育部《教育信息化2.0行动计划》强…...
为什么js小数相加,会产生精度缺失的问题,怎么解决?
为什么js小数相加,会产生精度缺失的问题,怎么解决? 在 JavaScript 中,小数相加会产生精度缺失问题,主要是由 JavaScript 采用的 IEEE 754 双精度 64 位浮点数表示法所导致的,下面详细解释其中的原因&#…...
【JavaScript】DOM和BOM是什么?
作者 :Yuppie001 作者主页 : 传送 本文专栏 :JavaScript 🌟🌟🌟🌟🌟🌟🌟🌟 DOM和BOM: 一.什么是DOMDOM是如何工作 二.BOMÿ…...
虚拟系统配置案例
安全策略要求: 1、只存在一个公网IP地址,公司内网所有部门都需要借用同一个接口访问外网 2、财务部禁止访问Internet,研发部门只有部分员工可以访问Internet,行政部门全部可以访问互联网 3、为三个部门的虚拟系统分配相同的资源类…...
Easysearch 新功能: IK 字段级别词典
Easysearch 1.10 版本在 IK 词典部分增加了字段级别词典的功能。 字段级别词典的功能支持用户对不同的字段设置不同的分词词库,用户既可以完全使用自己的词库,也支持在 ik 默认的词库上增加自定义的词库内容。 在整体使用上,ik 自定义词库的…...
微信小程序接入deepseek
先上效果 话不多说,直接上代码(本人用的hbuilder Xuniapp) <template><view class"container"><!-- 聊天内容区域 --><scroll-view class"chat-list" scroll-y :scroll-top"scrollTop":…...
大白话react第十六章React 与 WebGL 结合的实战项目
大白话react第十六章React 与 WebGL 结合的实战项目 1. 项目简介 React 是一个构建用户界面的强大库,而 WebGL 则允许我们在网页上实现高性能的 3D 图形渲染。将它们结合起来,我们可以创建出炫酷的 3D 网页应用,比如 3D 产品展示、虚拟场景…...
DeepSeek-R1:引领AI领域革新,MLA技术助力模型迁移
摘要 DeepSeek的MLA技术实现了大型机器学习模型的轻松迁移,其突破性产品DeepSeek-R1凭借显著降低的训练和推理成本,吸引了业界广泛关注。MLA技术的核心在于创新性的低秩压缩键值缓存架构,使得推理成本大幅减少,仅为同等性能大型模…...
Nginx:从入门到实战使用教程
全方位解析Nginx:从入门到实战使用教程 Nginx安装、配置详细教程 文章目录 全方位解析Nginx:从入门到实战使用教程导语一、Nginx简介二、Nginx安装与配置 1. 在CentOS系统上安装Nginx:2. 在Ubuntu系统上安装Nginx:3. Nginx配置文…...
SyntaxError: Unexpected token ‘xxx‘
🤍 前端开发工程师、技术日更博主、已过CET6 🍨 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 🕠 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》、《前端求职突破计划》 🍚 蓝桥云课签约作者、…...
GaussDB安全配置指南:从认证到防御的全方面防护
一、引言 随着企业数据规模的扩大和云端化进程加速,数据库安全性成为运维的核心挑战之一。GaussDB作为一款高性能分布式数据库,提供了丰富的安全功能。本文将从 认证机制、权限控制、数据加密、审计日志 等维度,系统性地讲解如何加固 Ga…...
[项目]基于FreeRTOS的STM32四轴飞行器: 四.LED控制
基于FreeRTOS的STM32四轴飞行器: 四.LED控制 一.配置Com层二.编写驱动 一.配置Com层 先在Com_Config.h中定义灯位置的枚举类型: 之后定义Led的结构体: 定义飞行器状态: 在Com_Config.c中初始化四个灯: 在Com_Config.h外部声明…...
Python——计算机网络
一.ip 1.ip的定义 IP是“Internet Protocol”的缩写,即“互联网协议”。它是用于计算机网络通信的基础协议之一,属于TCP/IP协议族中的网络层协议。IP协议的主要功能是负责将数据包从源主机传输到目标主机,并确保数据能够在复杂的网络环境中正…...