Android S RencentView 动画缩放研究「建议收藏」

Android S RencentView 动画缩放研究「建议收藏」关于 RecentView ,我想大家应该都十分了解。小手一滑,就可以看到最近的应用,十分方便。 我发现在 RecentView 中也可以实现动画的缩放,最近做项目涉及到动画缩放方案这一块,于是想研究

1. 相关背景知识

1.1 逻辑坐标系 setWindow

该坐标系是我们自己定义的,窗口与逻辑坐标系相关联,逻辑坐标系是一个坐标体系,窗口是在该体系的一个矩形框,窗口决定了是看一部分还是整体。将该矩形框显示在屏幕上中,也就是映射投影QT 的绘图设备上(widget),因此就有了物理坐标系和视口。

1.2 物理坐标系 setViewPort

简单理解,物理坐标系就是绘图设备上的坐标系(逻辑坐标系和物理坐标系默认重合,左上角是原点,向右是x轴正方向,向左是y轴正方向。

通过setViewPort() 定义物理坐标系的坐标原点,也定义了视口的矩形框大小。

二者关系: 窗口坐标为逻辑坐标,是基于视口坐标系的,视口坐标为物理坐标,是基于绘图设备坐标系的。


Surface

Handle onto a raw buffer that is being managed by the screen compositor.

由屏幕显示内容合成器所管理的原始缓存区的句柄。

  1. Surface 是一个句柄,通过 Surface 就可以获得原始缓冲器及其内容。
  2. a raw buffer 原始缓冲区是用于保存当前窗口的像素数据。
  3. Suffer 中有一个 Canvas 成员专门用于画图

2. 逻辑梳理

关于 RecentView ,我想大家应该都十分了解。小手一滑,就可以看到最近的应用,十分方便。 我发现在 RecentView 中也可以实现动画的缩放,最近做项目涉及到动画缩放方案这一块,于是想研究一下这块的实现原理。


Launcher 的同事告诉我他们是调用:

packages/apps/Launcher3/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java

中的 apply() 方法实现的。

真的很羡慕他们,调个方法就行了,不用管底下的逻辑,哈哈

也就是下面这块代码:

// packages/apps/Launcher3/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java

241      /** 242 * Applies the target to the previously set parameters 243 */
244      public void apply(TransformParams params) {
            // 1. 先判断 mDp 和 mThumbnailPosition 这两个矩形窗口是否为空,空的话就跳出。
245          if (mDp == null || mThumbnailPosition.isEmpty()) {
246              return;
247          }
   
            // 2. 这块的逻辑,先判断 mLayoutValid 是否为 false 
248          if (!mLayoutValid || mOrientationStateId != mOrientationState.getStateId()) {
249              mLayoutValid = true;
250              mOrientationStateId = mOrientationState.getStateId();
251              
                // 获取全屏的比例,返回的是一个浮点值
252              getFullScreenScale();
                // mOrientationState 的旋转赋给缩略图
253              mThumbnailData.rotation = mOrientationState.getDisplayRotation();
254  
255              // mIsRecentsRtl is the inverse of TaskView RTL.
256              boolean isRtlEnabled = !mIsRecentsRtl;
                // 更新缩略图的矩阵
257              mPositionHelper.updateThumbnailMatrix(
258                      mThumbnailPosition, mThumbnailData,
259                      mTaskRect.width(), mTaskRect.height(),
260                      mDp, mOrientationState.getRecentsActivityRotation(), isRtlEnabled);
                // 反转矩阵
261              mPositionHelper.getMatrix().invert(mInversePositionMatrix);
262          }
263          
   
            // 这里让 fullScreemProgress 的值 控制在(0,1)之间
264          float fullScreenProgress = Utilities.boundToRange(this.fullScreenProgress.value, 0, 1);
            
            // 3. 绘制缩略图 
265          mCurrentFullscreenParams.setProgress(
266                  fullScreenProgress, recentsViewScale.value, mTaskRect.width(), mDp,
267                  mPositionHelper);
268  
269          // Apply thumbnail matrix
270          RectF insets = mCurrentFullscreenParams.mCurrentDrawnInsets;
271          float scale = mCurrentFullscreenParams.mScale;
272          float taskWidth = mTaskRect.width();
273          float taskHeight = mTaskRect.height();
274          
            // 这里开始做缩略图的矩阵变换
275          mMatrix.set(mPositionHelper.getMatrix());
276          mMatrix.postTranslate(insets.left, insets.top);
277          mMatrix.postScale(scale, scale);
278  
279          // Apply TaskView matrix: translate, scroll
280          mMatrix.postTranslate(mTaskRect.left, mTaskRect.top);
281          mOrientationState.getOrientationHandler().set(mMatrix, MATRIX_POST_TRANSLATE,
282                  taskPrimaryTranslation.value);
283          mOrientationState.getOrientationHandler().setSecondary(mMatrix, MATRIX_POST_TRANSLATE,
284                  taskSecondaryTranslation.value);
285          mOrientationState.getOrientationHandler().set(
286                  mMatrix, MATRIX_POST_TRANSLATE, recentsViewScroll.value);
287  
288          // Apply RecentsView matrix
289          mMatrix.postScale(recentsViewScale.value, recentsViewScale.value, mPivot.x, mPivot.y);
290          mOrientationState.getOrientationHandler().setSecondary(mMatrix, MATRIX_POST_TRANSLATE,
291                  recentsViewSecondaryTranslation.value);
292          mOrientationState.getOrientationHandler().set(mMatrix, MATRIX_POST_TRANSLATE,
293                  recentsViewPrimaryTranslation.value);
294          applyWindowToHomeRotation(mMatrix);
295  
296          // Crop rect is the inverse of thumbnail matrix
            // 剪裁矩形是 缩略图 矩阵的逆
297          mTempRectF.set(-insets.left, -insets.top,
298                  taskWidth + insets.right, taskHeight + insets.bottom);
            // 4. 我们看看这个 mapRect 方法
299          mInversePositionMatrix.mapRect(mTempRectF);
            // 这里其实就是把 mTempRectF 的值设给 mTmpCropRect 
300          mTempRectF.roundOut(mTmpCropRect);
301          // 5. 我们看看这步操作
302          params.applySurfaceParams(params.createSurfaceParams(this));
303      }

通过这段代码,我们发现,很多动画操作是通过设置 mMatrix 来实现的,所以我们要看看 哪里用到了这个mMatrix

// packages/apps/Launcher3/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java 
// 在 矩阵上应用旋转,让它从 launcher 坐标空间映射到 window 坐标空间228      /** 229       * Applies the rotation on the matrix to so that it maps from launcher coordinate space to 230       * window coordinate space. 231       */
232      public void applyWindowToHomeRotation(Matrix matrix) {
233          mMatrix.postTranslate(mDp.windowX, mDp.windowY);
234          postDisplayRotation(deltaRotation(
235                  mOrientationState.getRecentsActivityRotation(),
236                  mOrientationState.getDisplayRotation()),
237                  mDp.widthPx, mDp.heightPx, matrix);
    
             // 该处的 matrix 是传入的 ,用指定的转换连接矩阵。
238          matrix.postTranslate(-mRunningTargetWindowPosition.x, -mRunningTargetWindowPosition.y);
239      }
240  
​
    
    
    // 这里的 mDp 是 DeviceProfile 对象
    // packages/apps/Launcher3/src/com/android/launcher3/DeviceProfile.java
    
214      DeviceProfile(Context context, InvariantDeviceProfile inv, Info info, WindowBounds windowBounds,
215              boolean isMultiWindowMode, boolean transposeLayoutWithOrientation,
216              boolean useTwoPanels) {
217  
218          this.inv = inv;
219          this.isLandscape = windowBounds.isLandscape();
220          this.isMultiWindowMode = isMultiWindowMode;
221          this.transposeLayoutWithOrientation = transposeLayoutWithOrientation;
222          windowX = windowBounds.bounds.left;
223          windowY = windowBounds.bounds.top;
224  
​
    
    // packages/apps/Launcher3/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java 
    
    
516      /** 517       * Posts the transformation on the matrix representing the provided display rotation 518       */
    
        // 1. displayRotation 的旋转由 deltaRotation 算出 
        // 2. mDp.widthPx , mDp.heightPx , matrix 
        // 3. 这里的 out 是传入的 matrix , 做变换。使用的都是 matrix 的方法。
519      public static void postDisplayRotation(@SurfaceRotation int displayRotation, 520              float screenWidth, float screenHeight, Matrix out) {
521          switch (displayRotation) {
522              case ROTATION_0:
523                  return;
524              case ROTATION_90:
525                  out.postRotate(270);
526                  out.postTranslate(0, screenWidth);
527                  break;
528              case ROTATION_180:
529                  out.postRotate(180);
530                  out.postTranslate(screenHeight, screenWidth);
531                  break;
532              case ROTATION_270:
533                  out.postRotate(90);
534                  out.postTranslate(screenHeight, 0);
535                  break;
536          }
537      }
538  
​
​
    // packages/apps/Launcher3/src/com/android/launcher3/states/RotationHelper.java 
191      /** 192       * @return how many factors {@param newRotation} is rotated 90 degrees clockwise. 193       * E.g. 1->Rotated by 90 degrees clockwise, 2->Rotated 180 clockwise... 194       * A value of 0 means no rotation has been applied 195       */
        // 变化量 来计算旋转
196      public static int deltaRotation(int oldRotation, int newRotation) {
197          int delta = newRotation - oldRotation;
198          if (delta < 0) delta += 4;
199          return delta;
200       }
201  
​

这里计算了变化量,给入的参数: mOrientationState.getRecentsActivityRotation()mOrientationState.getDisplayRotation()

2. 这块的逻辑,先判断 mLayoutValid 是否为 false

这里我们回到 apply()

// packages/apps/Launcher3/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java 
// 1. mOrientationState 是一个   RecentsOrientedState 类的对象public class TaskViewSimulator implements TransformParams.BuilderProxy {
 ...
 private RecentsOrientedState mOrientationState;
 ...
    
// 我们这里看看   RecentsOrientedState 是一个什么类
// packages/apps/Launcher3/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java 
// 这里注释说这个类是 保存Launcher 的方向/旋转相关信息的容器
// 这并不是一个用于在不同方向/旋转之间应用不同功能的抽象层
// 这个类有初始的默认状态,假设设备和前台应用程序没有旋转
      public class RecentsOrientedState implements
         SharedPreferences.OnSharedPreferenceChangeListener {
  ...
      
132      /** 133       * @param rotationChangeListener Callback for receiving rotation events when rotation watcher 134       *                             is enabled 135       * @see #setRotationWatcherEnabled(boolean) 136       */
137      public RecentsOrientedState(Context context, BaseActivityInterface sizeStrategy, 138              IntConsumer rotationChangeListener) {
139          mContext = context;
140          mSharedPrefs = Utilities.getPrefs(context);
141          mOrientationListener = new OrientationEventListener(context) {
142              @Override
143              public void onOrientationChanged(int degrees) {
144                  int newRotation = getRotationForUserDegreesRotated(degrees, mPreviousRotation);
145                  if (newRotation != mPreviousRotation) {
146                      mPreviousRotation = newRotation;
147                      rotationChangeListener.accept(newRotation);
148                  }
149              }
150          };
151  
152          mFlags = sizeStrategy.rotationSupportedByActivity
153                  ? FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_ACTIVITY : 0;
154  
155          mFlags |= FLAG_SWIPE_UP_NOT_RUNNING;
156          mSettingsCache = SettingsCache.INSTANCE.get(mContext);
157          initFlags();
158      }

这里看看Utilities.boundToRange

// packages/apps/Launcher3/src/com/android/launcher3/Utilities.java// 确保 值 在给定的范围内,这里其实就是设定一个范围。
// 具体的说: 如果 Value 小于 lowerBound ,返回 lowerBound 
// 如果value 大于 upperBound ,返回 upperBound ,否则返回值不变
522      public static float boundToRange(float value, float lowerBound, float upperBound) {
523          return Math.max(lowerBound, Math.min(value, upperBound));
524      }

这里我们看看 setProgress 方法:

// packages/apps/Launcher3/quickstep/src/com/android/quickstep/views/TaskView.java

public static class FullscreenDrawParams {
    ...
               /**
1504           * Sets the progress in range [0, 1]
1505           */
        
        // 这里我们主要看 setProgress 方法
1506 public void setProgress(float fullscreenProgress, float parentScale, int previewWidth,
1507 DeviceProfile dp, PreviewPositionHelper pph) {
1508 mFullscreenProgress = fullscreenProgress;
1509 RectF insets = pph.getInsetsToDrawInFullscreen();
1510  
1511 float currentInsetsLeft = insets.left * fullscreenProgress;
1512 float currentInsetsRight = insets.right * fullscreenProgress;
                  // 开始绘制
1513 mCurrentDrawnInsets.set(currentInsetsLeft, insets.top * fullscreenProgress,
1514 currentInsetsRight, insets.bottom * fullscreenProgress);
1515 float fullscreenCornerRadius = dp.isMultiWindowMode ? 0 : mWindowCornerRadius;
1516              // 当前绘制的角半径
1517 mCurrentDrawnCornerRadius =
1518 Utilities.mapRange(fullscreenProgress, mCornerRadius, fullscreenCornerRadius)
1519                              / parentScale;
1520  
1521              // We scaled the thumbnail to fit the content (excluding insets) within task view width.
1522              // Now that we are drawing left/right insets again, we need to scale down to fit them.
1523 if (previewWidth > 0) {
1524 mScale = previewWidth / (previewWidth + currentInsetsLeft + currentInsetsRight);
1525              }
1526          }
1527  
1528      }
1529  }

我们看这里 mMatrixTmp 被用来干什么

// packages/apps/Launcher3/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
205      /** 206 * Returns the current task bounds in the Launcher coordinate space. 207 */
        // 返回 Launcher 坐标空间中的 当前任务边界区域
208      public RectF getCurrentRect() {
             // 1. 先用 getCurrentCropRect() 
209          RectF result = getCurrentCropRect();
             // 2. 这里复制一个 mMatrix 
210          mMatrixTmp.set(mMatrix);
             // 3. 
211          preDisplayRotation(mOrientationState.getDisplayRotation(), mDp.widthPx, mDp.heightPx,
212                  mMatrixTmp);
213          mMatrixTmp.mapRect(result);
214          return result;
215      }
  1. getCurrentCropRect
// packages/apps/Launcher3/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
// 1. getCurrentCropRect() 
193      /** 194 * Returns the current clipped/visible window bounds in the window coordinate space 195 */
         // 返回目前剪切板或者可见窗口在(窗口坐标空间中)窗口的边界
196      public RectF getCurrentCropRect() {
197          // Crop rect is the inverse of thumbnail matrix
             // 
198          RectF insets = mCurrentFullscreenParams.mCurrentDrawnInsets;
199          mTempRectF.set(-insets.left, -insets.top,
200                  mTaskRect.width() + insets.right, mTaskRect.height() + insets.bottom);
    
             // 将此矩阵应用于矩形,并将转换后的矩形写入其中
             // 完成矩形4个角的转换,然后将其设置为这些点的边界
201          mInversePositionMatrix.mapRect(mTempRectF);
202          return mTempRectF;
203      }

这里我们单独看看 RectF 这个类

// frameworks/base/graphics/java/android/graphics/RectF.java 
// RectF 保存了 矩形 的四个浮点坐标,矩形由四条边的坐标(上、下、左、右)表示。
// 这些字段可以直接访问。
// 4. mapRect
// frameworks/base/graphics/java/android/graphics/Matrix.java 

711      /** 712 * Apply this matrix to the src rectangle, and write the transformed rectangle into dst. This is 713 * accomplished by transforming the 4 corners of src, and then setting dst to the bounds of 714 * those points. 715 * 716 * @param dst Where the transformed rectangle is written. 717 * @param src The original rectangle to be transformed. 718 * @return the result of calling rectStaysRect() 719 */
    
        // 将此矩阵用于 src 矩阵,并将转换后的矩形写入 dst 
        // 这是通过转换src的4个角来完成的,然后设置dst到这些点的边界来完成的
        // 转换后的矩形被写入 dst 
        // src 是要转换的原始矩形
        // 返回的是 rectStayRect() 的回调结果
720      public boolean mapRect(RectF dst, RectF src) {
721          if (dst == null || src == null) {
722              throw new NullPointerException();
723          }
724          return nMapRect(native_instance, dst, src);
725      }

// frameworks/base/graphics/java/android/graphics/RectF.java 


453      /** 454 * Set the dst integer Rect by rounding "out" this rectangle, choosing the 455 * floor of top and left, and the ceiling of right and bottom. 456 */
457      public void roundOut(@NonNull Rect dst) {
458          dst.set((int) Math.floor(left), (int) Math.floor(top),
459                  (int) Math.ceil(right), (int) Math.ceil(bottom));
460      }
//packages/apps/Launcher3/quickstep/src/com/android/quickstep/util/TransformParams.java 

// 5. params.applySurfaceParams(params.createSurfaceParams(this));
208      public void applySurfaceParams(SurfaceParams... params) {
209          if (mSyncTransactionApplier != null) {
210              mSyncTransactionApplier.scheduleApply(params);
211          } else {
212              TransactionCompat t = new TransactionCompat();
213              for (SurfaceParams param : params) {
214                  SyncRtSurfaceTransactionApplierCompat.applyParams(t, param);
215              }
216              t.apply();
217          }
218      }

// 这里我们看看 createSurfaceParams 

140      public SurfaceParams[] createSurfaceParams(BuilderProxy proxy) {
             // 保存由不同属性过滤的 RemoteAnimationTargets的集合
141          RemoteAnimationTargets targets = mTargetSet;
             // 根据unfilteredApps 的 length 来 new SurfaceParams
142          SurfaceParams[] surfaceParams = new SurfaceParams[targets.unfilteredApps.length];
             // 这里的mRecentsSurface 是 SurfaceControl 的对象
143          mRecentsSurface = getRecentsSurface(targets);
144  
145          for (int i = 0; i < targets.unfilteredApps.length; i++) {
146              RemoteAnimationTargetCompat app = targets.unfilteredApps[i];
147              SurfaceParams.Builder builder = new SurfaceParams.Builder(app.leash);
148  
149              if (app.mode == targets.targetMode) {
150                  if (app.activityType == RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME) {
151                      mHomeBuilderProxy.onBuildTargetParams(builder, app, this);
152                  } else {
153                      // Fade out Assistant overlay.
154                      if (app.activityType == RemoteAnimationTargetCompat.ACTIVITY_TYPE_ASSISTANT
155                              && app.isNotInRecents) {
156                          float progress = Utilities.boundToRange(getProgress(), 0, 1);
157                          builder.withAlpha(1 - Interpolators.DEACCEL_2_5.getInterpolation(progress));
158                      } else {
159                          builder.withAlpha(getTargetAlpha());
160                      }
161  
162                      proxy.onBuildTargetParams(builder, app, this);
163                  }
164              } else {
165                  mBaseBuilderProxy.onBuildTargetParams(builder, app, this);
166              }
167              surfaceParams[i] = builder.build();
168          }
169          return surfaceParams;
170      }

这里我们会发现 createSurfaceParams是一个 SurfaceParams类型的对象 通过搜索,SurfaceParams 在 SyncRtSurfaceTransactionApplierCompat.java 中

//frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java 

 public static class SurfaceParams {
			...
321          private SurfaceParams(SurfaceControl surface, int flags, float alpha, Matrix matrix,
322                  Rect windowCrop, int layer, SurfaceControl relativeTo, int relativeLayer,
323                  float cornerRadius, int backgroundBlurRadius, boolean visible, float shadowRadius) {
324              this.flags = flags;
325              this.surface = surface;
326              this.alpha = alpha;
327              this.matrix = matrix;
328              this.windowCrop = windowCrop;
329              this.layer = layer;
330              this.relativeTo = relativeTo;
331              this.relativeLayer = relativeLayer;
332              this.cornerRadius = cornerRadius;
333              this.backgroundBlurRadius = backgroundBlurRadius;
334              this.visible = visible;
335              this.shadowRadius = shadowRadius;
336          }
337  

这里有必要来看看 SurfaceControl

// frameworks/base/core/java/android/view/SurfaceControl.java // 1 .on-screen Surface 的句柄 由系统合成器管理 // 2. SurfaceControl 是缓冲源的组合,以及如何显示缓冲区的元数据 // 3. 通过从 SurfaceControl 构建 一个 Surface ,你可以提交缓冲区合成 // 4. 使用 SurfaceControl.Transaction ,你可以操作缓冲区在屏幕上显示各种属性 // 5. SurafaceControl 被安排成类似层次的场景图,因此,任何 SurfaceControl 都有可能有父级 // 6. 像变换、裁剪和z -排序这样的几何属性将被继承就好像子进程是父进程缓冲流中的内容。 
  1. applySurfaceParams
// packages/apps/Launcher3/quickstep/src/com/android/quickstep/util/SurfaceTransactionApplier.java 
mSyncTransactionApplier.scheduleApply(params);

// 辅助类,与RenderThread 同步 surface 事务 , 跟 android.view.SyncRtSurfaceTransactionApplier 类似
// 使用一些 Launcher 的方法

66      /** 67 * Schedules applying surface parameters on the next frame. * 在下一帧使用 surface 的参数 68 * 69 * @param params The surface parameters to apply. DO NOT MODIFY the list after passing into 70 * this method to avoid synchronization issues. * 传递到该方法后不要修改列表参数,防止同步问题的发生 71 */
72      public void scheduleApply(final SurfaceParams... params) {
73          View view = mTargetViewRootImpl.getView();
74          if (view == null) {
75              return;
76          }
77  
78          mLastSequenceNumber++;
79          final int toApplySeqNo = mLastSequenceNumber;
80          setCanRelease(false);
81          mTargetViewRootImpl.registerRtFrameCallback(frame -> {
82              if (mBarrierSurfaceControl == null || !mBarrierSurfaceControl.isValid()) {
83                  Message.obtain(mApplyHandler, MSG_UPDATE_SEQUENCE_NUMBER, toApplySeqNo, 0)
84                          .sendToTarget();
85                  return;
86              }
87              Transaction t = new Transaction();
88              for (int i = params.length - 1; i >= 0; i--) {
89                  SurfaceParams surfaceParams = params[i];
90                  if (surfaceParams.surface.isValid()) {
91                        surfaceParams.applyTo(t);
92                  }
93              }
94              mTargetViewRootImpl.mergeWithNextTransaction(t, frame);
95              Message.obtain(mApplyHandler, MSG_UPDATE_SEQUENCE_NUMBER, toApplySeqNo, 0)
96                      .sendToTarget();
97          });
98  
99          // Make sure a frame gets scheduled.
100          view.invalidate();
101      }

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
转载请注明出处: https://daima100.com/12870.html

(0)

相关推荐

  • 第17问:如何评估 alter table 的进度?

    第17问:如何评估 alter table 的进度?问题 我们执行 alter table 语句后,经常面临“跑又跑不完,杀又不敢杀”的窘境。 如果能评估 alter table 的进度就幸福多了。 实验 MySQL官方已经给出了文档:https:/…

    2023-03-26
    144
  • 深入学习Python中的os模块

    深入学习Python中的os模块Python 是现今世界上最流行的编程语言之一,其强大的功能和易学易用的特性深受开发者的追捧。在 Python 中,os 模块是一个不可或缺的工具,在文件操作、目录处理、进程调用等方面都有着广泛应用。本文将深入介绍 os 模块的特点和使用,帮助读者更好地理解 Python 在系统操作方面的应用。

    2024-07-05
    51
  • SQL99相较于SQL92在多表查询时的新语法「建议收藏」

    SQL99相较于SQL92在多表查询时的新语法「建议收藏」1.自然连接 NATURAL JOIN SQL99中新增的自然连接相当于SQL92中的等值连接。它可以自动的查询两个表中所有的相同字段,然后进行等值连接。 在SQL92中: SELECT 表1.字段1

    2023-05-28
    149
  • Python实现最长公共子序列算法

    Python实现最长公共子序列算法最长公共子序列是字符串处理中的基本问题之一,可以用于计算两个字符串之间的相似度或复制、粘贴代码时检测差异。而Python是一种广泛使用的高级编程语言,拥有丰富的数据结构和库函数支持。在本篇文章中,我们将展示如何使用Python实现最长公共子序列算法。

    2024-09-08
    24
  • Python输入函数

    Python输入函数Python是一种高级编程语言,提供了大量的内置函数,以帮助我们更轻松地完成日常的编程任务。其中一个非常有用的内置函数是in函数。

    2024-06-24
    46
  • 用Python的os.path.basename函数获取文件名

    用Python的os.path.basename函数获取文件名 在Python中,我们可以使用os.path.basename函数获取文件路径中的文件名部分,该函数用于获取文件的基本名称(字符串中最后一个反斜杠以后的部分),并将其作为字符串返回。如果路径以反斜杠结尾,则返回前一个部分。该函数可以应用于多种操作系统,如Windows,Linux,Unix等。使用该函数时,需要导入os模块。

    2023-12-10
    113
  • Jupyter Notebook运行代码

    Jupyter Notebook运行代码Jupyter Notebook是一个开源软件应用程序,用于创建和共享文学化的代码,支持多种编程语言,如Python,R和Julia。它的网页界面使用户能够编写和运行代码,创建注释和图形,并将所有这些组合在一个易于共享的文档中。

    2024-09-22
    13
  • dnf红眼86刷图加点(dnf95瞎子刷图加点最新)

    dnf红眼86刷图加点(dnf95瞎子刷图加点最新)

    2023-09-09
    128

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注