大家好,我是考100分的小小码 ,祝大家学习进步,加薪顺利呀。今天说一说Android S RencentView 动画缩放研究「建议收藏」,希望您对编程的造诣更进一步.
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.
由屏幕显示内容合成器所管理的原始缓存区的句柄。
- Surface 是一个句柄,通过 Surface 就可以获得原始缓冲器及其内容。
- a raw buffer 原始缓冲区是用于保存当前窗口的像素数据。
- 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 }
- 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 -排序这样的几何属性将被继承就好像子进程是父进程缓冲流中的内容。
- 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