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

Android动画(四):PathMeasure实现路径动画

文章概览

  • 1 PathMeasure概述
  • 2 实现路径加载动画
  • 3 实现箭头加载动画
  • 4 实现操作成功动画

本系列将介绍以下内容:
在这里插入图片描述

Android动画

1 PathMeasure概述

PathMeasure是一个单独的类,其全部源码如下(请详细研读注释):

package android.graphics;public class PathMeasure {private Path mPath;public PathMeasure() {mPath = null;native_instance = native_create(0, false);}/*** @param forceClosed If true, then the path will be considered as "closed"*        even if its contour was not explicitly closed.*       如果为 "true",则路径将被视为 "封闭" 即使其轮廓没有明确封闭。*/public PathMeasure(Path path, boolean forceClosed) {// The native implementation does not copy the path, prevent it from being GC'dmPath = path;native_instance = native_create(path != null ? path.readOnlyNI() : 0,forceClosed);}public void setPath(Path path, boolean forceClosed) {mPath = path;native_setPath(native_instance,path != null ? path.readOnlyNI() : 0,forceClosed);}/*** Return the total length of the current contour, or 0 if no path is* associated with this measure object.* 返回当前轮廓的总长度,如果此测量对象没有关联路径,则返回 0。*/public float getLength() {return native_getLength(native_instance);}public boolean getPosTan(float distance, float pos[], float tan[]) {if (pos != null && pos.length < 2 ||tan != null && tan.length < 2) {throw new ArrayIndexOutOfBoundsException();}return native_getPosTan(native_instance, distance, pos, tan);}public static final int POSITION_MATRIX_FLAG = 0x01;    // must match flags in SkPathMeasure.hpublic static final int TANGENT_MATRIX_FLAG  = 0x02;    // must match flags in SkPathMeasure.hpublic boolean getMatrix(float distance, Matrix matrix, int flags) {return native_getMatrix(native_instance, distance, matrix.ni(), flags);}/*** @param dst 将截取的Path添加(不是替换)到dst中。* @param startWithMoveTo 起始点是否使用moveTo* * 注意:* 1、路径截取是以路径的左上角为起始点开始的。* 2、路径的截取方向与路径的生成方向相同。* 3、截取的Path片段是被添加到路径dst中,而不是替换dst中的内容。* 4、如果startWithMoveTo为true,则被截取出来的Path片段保持原状;如果为false,则会将截取出来的Path片段的起始点移动到dst的最后一个点,以保证dst路径的连续性。*/public boolean getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo) {float length = getLength();if (startD < 0) {startD = 0;}if (stopD > length) {stopD = length;}if (startD >= stopD) {return false;}return native_getSegment(native_instance, startD, stopD, dst.mutateNI(), startWithMoveTo);}/*** Return true if the current contour is closed()* 如果当前轮廓封闭,则返回 true()*/public boolean isClosed() {return native_isClosed(native_instance);}/*** Move to the next contour in the path. Return true if one exists, or* false if we're done with the path.* 移动到路径中的下一个轮廓。如果存在下一个轮廓,则返回 true;* 如果已经完成路径的移动,则返回 false。* * 注意:通过该方法得到的曲线的顺序与Path中添加的顺序相同。*/public boolean nextContour() {return native_nextContour(native_instance);}protected void finalize() throws Throwable {native_destroy(native_instance);native_instance = 0;  // Other finalizers can still call us.}private static native long native_create(long native_path, boolean forceClosed);private static native void native_setPath(long native_instance, long native_path, boolean forceClosed);private static native float native_getLength(long native_instance);private static native boolean native_getPosTan(long native_instance, float distance, float pos[], float tan[]);private static native boolean native_getMatrix(long native_instance, float distance, long native_matrix, int flags);private static native boolean native_getSegment(long native_instance, float startD, float stopD, long native_path, boolean startWithMoveTo);private static native boolean native_isClosed(long native_instance);private static native boolean native_nextContour(long native_instance);private static native void native_destroy(long native_instance);private long native_instance;
}

PathMeasure的初始化方法是

Path mCirclePath = new Path();PathMeasure mPathMeasure = new PathMeasure();
mPathMeasure.setPath(mCirclePath, true);

Path mCirclePath = new Path();
PathMeasure mPathMeasure = new PathMeasure(mCirclePath, false);

getLength()、getSegment()都只会针对其中第一条线段进行计算。它们针对的是当前的曲线,而不是整个Path,所以getLength()方法获取到的是当前曲线的长度,而不是整个Path的长度。

2 实现路径加载动画

主要使用PathMeasure的getSegment(x)方法实现动画效果。

直接在布局文件中引用:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><com.example.myapplication.GetSegmentViewandroid:layout_width="match_parent"android:layout_height="match_parent" /></LinearLayout>

自定义的GetSegmentView:

package com.example.myapplication;import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.*;
import android.util.AttributeSet;
import android.view.View;import androidx.annotation.NonNull;public class GetSegmentView extends View {private Paint mPaint;private Path mCirclePath, mDstPath;private PathMeasure mPathMeasure;private Float mCurAnimValue;public GetSegmentView(Context context, AttributeSet attrs) {super(context, attrs);setLayerType(LAYER_TYPE_SOFTWARE, null);mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);mPaint.setStyle(Paint.Style.STROKE);mPaint.setStrokeWidth(4);mPaint.setColor(Color.BLACK);mDstPath = new Path();mCirclePath = new Path();mCirclePath.addCircle(100, 100, 50, Path.Direction.CW);mPathMeasure = new PathMeasure(mCirclePath, true);ValueAnimator animator = ValueAnimator.ofFloat(0, 1);animator.setRepeatCount(ValueAnimator.INFINITE);animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(@NonNull ValueAnimator animation) {mCurAnimValue = (Float) animation.getAnimatedValue();invalidate();}});animator.setDuration(2000);animator.start();}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);float length = mPathMeasure.getLength();float stop = length * mCurAnimValue;float start = (float) (stop - ((0.5 - Math.abs(mCurAnimValue - 0.5)) * length));// 清空之前生成的路径mDstPath.reset();canvas.drawColor(Color.WHITE);mPathMeasure.getSegment(0, stop, mDstPath, true);
//        mPathMeasure.getSegment(start, stop, mDstPath, true);canvas.drawPath(mDstPath, mPaint);}}

效果图:
在这里插入图片描述
上述动画效果的起始位置是从0开始的,将onDraw(x)中的代码切换,改变动画的起始位置:

//        mPathMeasure.getSegment(0, stop, mDstPath, true);mPathMeasure.getSegment(start, stop, mDstPath, true);

效果图:
在这里插入图片描述

3 实现箭头加载动画

利用PathMeasure的getPosTan(x)方法实现箭头加载动画。

箭头资源图片:
在这里插入图片描述
布局文件引用:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><com.example.myapplication.GetPosTanViewandroid:layout_width="match_parent"android:layout_height="match_parent" /></LinearLayout>

自定义的GetPosTanView:

package com.example.myapplication;import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.util.AttributeSet;
import android.view.View;import androidx.annotation.NonNull;public class GetPosTanView extends View {private Paint mPaint;private Path mCirclePath, mDstPath;private PathMeasure mPathMeasure;private Float mCurAnimValue;private Bitmap mArrawBmp;private float[] pos = new float[2];private float[] tan = new float[2];public GetPosTanView(Context context, AttributeSet attrs) {super(context, attrs);setLayerType(LAYER_TYPE_SOFTWARE, null);// 缩小箭头图片BitmapFactory.Options options = new BitmapFactory.Options();options.inSampleSize = 6;mArrawBmp = BitmapFactory.decodeResource(getResources(), R.drawable.arraw, options);mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);mPaint.setStyle(Paint.Style.STROKE);mPaint.setStrokeWidth(4);mPaint.setColor(Color.BLACK);mDstPath = new Path();mCirclePath = new Path();mCirclePath.addCircle(200, 200, 50, Path.Direction.CW);mPathMeasure = new PathMeasure(mCirclePath, true);ValueAnimator animator = ValueAnimator.ofFloat(0, 1);animator.setRepeatCount(ValueAnimator.INFINITE);animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(@NonNull ValueAnimator animation) {mCurAnimValue = (Float) animation.getAnimatedValue();invalidate();}});animator.setDuration(2000);animator.start();}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);canvas.drawColor(Color.WHITE);float length = mPathMeasure.getLength();float stop = length * mCurAnimValue;mDstPath.reset();mPathMeasure.getSegment(0, stop, mDstPath, true);canvas.drawPath(mDstPath, mPaint);// 箭头旋转、位移实现方式一,通过getPosTan(x)实现mPathMeasure.getPosTan(stop, pos, tan);float degrees = (float) (Math.atan2(tan[1], tan[0]) * 180.0 / Math.PI);Matrix matrix = new Matrix();matrix.postRotate(degrees, mArrawBmp.getWidth() / 2, mArrawBmp.getHeight() / 2);matrix.postTranslate(pos[0] - mArrawBmp.getWidth() / 2, pos[1] - mArrawBmp.getHeight() / 2);// 箭头旋转、位移实现方式二,通过getMatrix(x)实现/*Matrix matrix = new Matrix();mPathMeasure.getMatrix(stop,matrix,PathMeasure.POSITION_MATRIX_FLAG | PathMeasure.TANGENT_MATRIX_FLAG);matrix.preTranslate(-mArrawBmp.getWidth() / 2, -mArrawBmp.getHeight() / 2);*/canvas.drawBitmap(mArrawBmp, matrix, mPaint);}}

效果图:
在这里插入图片描述

4 实现操作成功动画

需要用到PathMeasure的nextContour()方法。

布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><com.example.myapplication.OperationViewandroid:layout_width="match_parent"android:layout_height="match_parent" /></LinearLayout>

自定义OperationView:

package com.example.myapplication;import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.util.AttributeSet;
import android.view.View;import androidx.annotation.NonNull;public class OperationView extends View {private Paint mPaint;private Path mCirclePath, mDstPath;private PathMeasure mPathMeasure;private Float mCurAnimValue;private int mCentX = 200;private int mCentY = 200;private int mRadius = 50;boolean mNext = false;public OperationView(Context context, AttributeSet attrs) {super(context, attrs);setLayerType(LAYER_TYPE_SOFTWARE, null);mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);mPaint.setStyle(Paint.Style.STROKE);mPaint.setStrokeWidth(4);mPaint.setColor(Color.BLACK);mDstPath = new Path();mCirclePath = new Path();mCirclePath.addCircle(mCentX, mCentY, mRadius, Path.Direction.CW);mCirclePath.moveTo(mCentX - mRadius / 2, mCentY);mCirclePath.lineTo(mCentX, mCentY + mRadius / 2);mCirclePath.lineTo(mCentX + mRadius / 2, mCentY - mRadius / 3);mPathMeasure = new PathMeasure(mCirclePath, false);// 0~1之间画第一条路径,1~2之间画第二条路径ValueAnimator animator = ValueAnimator.ofFloat(0, 2);animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(@NonNull ValueAnimator animation) {mCurAnimValue = (Float) animation.getAnimatedValue();invalidate();}});animator.setDuration(4000);animator.start();}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);canvas.drawColor(Color.WHITE);if (mCurAnimValue < 1) {float stop = mPathMeasure.getLength() * mCurAnimValue;mPathMeasure.getSegment(0, stop, mDstPath, true);} else {if (!mNext) {mNext = true;mPathMeasure.getSegment(0, mPathMeasure.getLength(), mDstPath, true);mPathMeasure.nextContour();}float stop = mPathMeasure.getLength() * (mCurAnimValue - 1);mPathMeasure.getSegment(0, stop, mDstPath, true);}canvas.drawPath(mDstPath, mPaint);}}

效果图:
在这里插入图片描述

参考文献:
[1] UML中的类图及类图之间的关系
[2] 启舰.Android自定义控件开发入门与实战[M].北京:电子工业出版社,2018

微信公众号:TechU
在这里插入图片描述

相关文章:

Android动画(四):PathMeasure实现路径动画

文章概览 1 PathMeasure概述2 实现路径加载动画3 实现箭头加载动画4 实现操作成功动画 本系列将介绍以下内容&#xff1a; Android动画 1 PathMeasure概述 PathMeasure是一个单独的类&#xff0c;其全部源码如下&#xff08;请详细研读注释&#xff09;&#xff1a; package…...

HTTP 连接详解

概述 世界上几乎所有的 HTTP 通信都是由 TCP/IP 承载的&#xff0c;客户端可以打开一条TCP/IP连接&#xff0c;连接到任何地方的服务器。一旦连接建立&#xff0c;客户端和服务器之间交换的报文就永远不会丢失、受损或失序 TCP&#xff08;Transmission Control Protocol&…...

练习题(2024/5/12)

1二分查找 给定一个 n 个元素有序的&#xff08;升序&#xff09;整型数组 nums 和一个目标值 target &#xff0c;写一个函数搜索 nums 中的 target&#xff0c;如果目标值存在返回下标&#xff0c;否则返回 -1。 示例 1: 输入: nums [-1,0,3,5,9,12], target 9 输出: 4…...

Day50代码随想录动态规划part12:309.最佳买卖股票时机含冷冻期、714.买卖股票的最佳时机含手续费

Day50 动态规划part12 股票问题 309.最佳买卖股票时机含冷冻期 leetcode题目链接&#xff1a;309. 买卖股票的最佳时机含冷冻期 - 力扣&#xff08;LeetCode&#xff09; 题意&#xff1a;给定一个整数数组&#xff0c;其中第 i 个元素代表了第 i 天的股票价格 。设计一个算…...

【软考】scrum的步骤

目录 1. 明确产品愿景和需求2. 制定计划和任务列表3. 进行迭代开发&#xff08;Sprint&#xff09;4. Sprint评审会议5. Sprint回顾会议6. 重复迭代 1. 明确产品愿景和需求 1.这个过程通常由项目所有者和利益相关者参与&#xff0c;目的是确保整个团队对项目的目标和方向有清晰…...

【C语言】编译与链接

✨✨欢迎大家来到Celia的博客✨✨ &#x1f389;&#x1f389;创作不易&#xff0c;请点赞关注&#xff0c;多多支持哦&#x1f389;&#x1f389; 所属专栏&#xff1a;C语言 个人主页&#xff1a;Celias blog~ 目录 引言 一、翻译环境 1.1 编译 1.1.1 预处理 1.1.2 编译 …...

Consul 注册的服务地址变成了 127.0.1.1

问题 我们的服务一直用 Consul 作为注册中心&#xff0c;在 AWS 和 阿里云上使用的时候&#xff0c;没出现过问题。最近把一些服务迁到腾讯云的时候&#xff0c;遇到一个问题&#xff1a;注册的服务地址都是 127.0.1.1。 127.0.1.1 这个地址我们平时遇到的比较少&#xff0c;…...

数字水印 | 离散小波变换 DWT 的 Python 代码实现

&#x1f34d;原文&#xff1a; 【图像处理】图像离散小波变换及 Python 代码实现 &#x1f34d;写在前面&#xff1a; 本文在原文的基础上补全了代码。 1 环境准备 ① 安装 p y w t \mathsf{pywt} pywt 包&#xff1a; pip install PyWavelets说明&#xff1a; p y w t \…...

[框架] Unity 公共执行器

本篇我们通过使用单例模式来创建一个公共执行器&#xff0c;使得原本应该在Update()、FixedUpdate()中的指令都可以统一放在一个对象中执行&#xff0c;且可进行添加和移除操作。 1. 创建单例模式改造器&#xff1a;SingletonMono 我们先创建一个单例模式改造器&#xff0c;使…...

二进制转为HEX数组小工具

在使用RA8889时&#xff0c;JPG的解码只能从FLASH的DMA通道获取&#xff0c;那么如果要从远端、或者SD卡等处读取JPG图片出来显示怎么办&#xff1f; RA8889支持JPG图片硬解码&#xff0c;但数据流是从FLASH进行DMA读取的&#xff0c;然后再进行解码。因此这种情况下&#xff…...

数据结构-二叉树-红黑树

一、红黑树的概念 红黑树是一种二叉搜索树&#xff0c;但在每个节点上增加一个存储位表示节点的颜色&#xff0c;可以是Red或者BLACK&#xff0c;通过对任何一条从根到叶子的路径上各个节点着色方式的限制&#xff0c;红黑树确保没有一条路径会比其他路径长出两倍&#xff0c;…...

C++11 新特性 decltype 说明符

一、typeof与typeid 1.1、typeof 在C11标准之前&#xff0c;GCC已经提供了一个类似功能的运算符 typeof对类型进行推导&#xff0c;但是这毕竟是编译器的实现&#xff0c;不是标准。 int a 0; typeof(a) b 5;1.2、typeid C标准提供了 typeid 运算符&#xff0c;获取的类型…...

java线程局部变量使用方式

线程局部变量是Java中用于存储线程本地信息的变量。这种变量仅在线程的生命周期内存在&#xff0c;并且每个线程都有自己的一份拷贝。换句话说&#xff0c;线程局部变量是线程私有的&#xff0c;其他线程无法访问。 使用场景主要包括&#xff1a; 1. 存储线程状态信息&#xff…...

【隧道篇 / WAN优化】(7.4) ❀ 01. 启动WAN优化 ❀ FortiGate 防火墙

【简介】几乎所有的人都知道&#xff0c;防火墙自带的硬盘是用来保存日志&#xff0c;以方便在出现问题时能找到原因。但是很少的人知道&#xff0c;防火墙自带的硬盘其实还有另一个功能&#xff0c;那就是用于WAN优化。 防火墙自带的硬盘 在FortiGate防火墙A、B、C、D系列&…...

2024数维杯数学建模B题生物质和煤共热解问题的研究原创论文分享

大家好&#xff0c;从昨天肝到现在&#xff0c;终于完成了2024数维杯数学建模挑战赛B题的完整论文啦。 实在精力有限&#xff0c;具体的讲解大家可以去讲解视频&#xff1a; 2024数维杯数学建模B题煤共热解每一问高质量完整代码讲解&#xff01;_哔哩哔哩_bilibili 2024数维杯…...

中国电子学会(CEIT)2022年12月真题C语言软件编程等级考试三级(含详细解析答案)

中国电子学会(CEIT)考评中心历届真题(含解析答案) C语言软件编程等级考试一级 2022年12月 编程题五道 总分:100分一、鸡兔同笼(20分) 一个笼子里面关了鸡和兔子(鸡有2只脚,兔子有4只脚,没有例外)。已经知道了笼子里面脚的总数a,问笼子里面至少有多少只动物,至…...

今日早报 每日精选15条新闻简报 每天一分钟 知晓天下事 5月12日,星期日

每天一分钟&#xff0c;知晓天下事&#xff01; 2024年5月12日 星期日 农历四月初五 1、 全国多地已推“一次挂号管三天”&#xff0c;部分医院专家门诊适用。 2、 在梅大高速塌方事故中拦车、救援&#xff0c;黄曼秋等5人拟确认为见义勇为。 3、 深圳新能源车指标申请条件调…...

微服务思想以及实现

文章目录 前言一、什么时候需要拆分微服务1. 创业型项目2. 大型项目 二、怎么拆1. 拆分目标2. 拆分方式 三、微服务之间远程调用1. 实现方式2. 手动发送Http请求&#xff08;RestTemplate&#xff09;3. 服务注册中心3.1 原理3.2 Nacos注册中心3.3 服务注册3.4 服务发现(Discov…...

C语法:格式符号%f和%lf引发的错误

今天编程时有如下代码&#xff1a; #include"stdio.h"int main(void) {double profit;double bonus;printf("请输入本月利润\n");scanf("%f",&profit);//错误&#xff1a;此行profit是double类型&#xff0c;格式符为%f,当输入8时&#xff0…...

Java基础入门day48

day48 JDBC调用关系 tomcat 简介 tomcat是Apache下的一个核心项目&#xff0c;免费开源&#xff0c;支持servlet和jsp。 tomcat技术先进&#xff0c;性能稳定&#xff0c;目前比较流行的web应用服务器 安装 官网&#xff1a; Apache Tomcat - Welcome! 下载 tomcat8.5 解压&a…...

stm32G473的flash模式是单bank还是双bank?

今天突然有人stm32G473的flash模式是单bank还是双bank&#xff1f;由于时间太久&#xff0c;我真忘记了。搜搜发现&#xff0c;还真有人和我一样。见下面的链接&#xff1a;https://shequ.stmicroelectronics.cn/forum.php?modviewthread&tid644563 根据STM32G4系列参考手…...

Swift 协议扩展精进之路:解决 CoreData 托管实体子类的类型不匹配问题(下)

概述 在 Swift 开发语言中&#xff0c;各位秃头小码农们可以充分利用语法本身所带来的便利去劈荆斩棘。我们还可以恣意利用泛型、协议关联类型和协议扩展来进一步简化和优化我们复杂的代码需求。 不过&#xff0c;在涉及到多个子类派生于基类进行多态模拟的场景下&#xff0c;…...

Cloudflare 从 Nginx 到 Pingora:性能、效率与安全的全面升级

在互联网的快速发展中&#xff0c;高性能、高效率和高安全性的网络服务成为了各大互联网基础设施提供商的核心追求。Cloudflare 作为全球领先的互联网安全和基础设施公司&#xff0c;近期做出了一个重大技术决策&#xff1a;弃用长期使用的 Nginx&#xff0c;转而采用其内部开发…...

解决本地部署 SmolVLM2 大语言模型运行 flash-attn 报错

出现的问题 安装 flash-attn 会一直卡在 build 那一步或者运行报错 解决办法 是因为你安装的 flash-attn 版本没有对应上&#xff0c;所以报错&#xff0c;到 https://github.com/Dao-AILab/flash-attention/releases 下载对应版本&#xff0c;cu、torch、cp 的版本一定要对…...

【HTML-16】深入理解HTML中的块元素与行内元素

HTML元素根据其显示特性可以分为两大类&#xff1a;块元素(Block-level Elements)和行内元素(Inline Elements)。理解这两者的区别对于构建良好的网页布局至关重要。本文将全面解析这两种元素的特性、区别以及实际应用场景。 1. 块元素(Block-level Elements) 1.1 基本特性 …...

Caliper 配置文件解析:fisco-bcos.json

config.yaml 文件 config.yaml 是 Caliper 的主配置文件,通常包含以下内容: test:name: fisco-bcos-test # 测试名称description: Performance test of FISCO-BCOS # 测试描述workers:type: local # 工作进程类型number: 5 # 工作进程数量monitor:type: - docker- pro…...

从面试角度回答Android中ContentProvider启动原理

Android中ContentProvider原理的面试角度解析&#xff0c;分为​​已启动​​和​​未启动​​两种场景&#xff1a; 一、ContentProvider已启动的情况 1. ​​核心流程​​ ​​触发条件​​&#xff1a;当其他组件&#xff08;如Activity、Service&#xff09;通过ContentR…...

uniapp 实现腾讯云IM群文件上传下载功能

UniApp 集成腾讯云IM实现群文件上传下载功能全攻略 一、功能背景与技术选型 在团队协作场景中&#xff0c;群文件共享是核心需求之一。本文将介绍如何基于腾讯云IMCOS&#xff0c;在uniapp中实现&#xff1a; 群内文件上传/下载文件元数据管理下载进度追踪跨平台文件预览 二…...

【堆垛策略】设计方法

堆垛策略的设计是积木堆叠系统的核心&#xff0c;直接影响堆叠的稳定性、效率和容错能力。以下是分层次的堆垛策略设计方法&#xff0c;涵盖基础规则、优化算法和容错机制&#xff1a; 1. 基础堆垛规则 (1) 物理稳定性优先 重心原则&#xff1a; 大尺寸/重量积木在下&#xf…...

Python爬虫实战:研究Restkit库相关技术

1. 引言 1.1 研究背景与意义 在当今信息爆炸的时代,互联网上存在着海量的有价值数据。如何高效地采集这些数据并将其应用于实际业务中,成为了许多企业和开发者关注的焦点。网络爬虫技术作为一种自动化的数据采集工具,可以帮助我们从网页中提取所需的信息。而 RESTful API …...