|
167 | 167 |
|
168 | 168 | touch_interceptors_[viewId] = |
169 | 169 | fml::scoped_nsobject<FlutterTouchInterceptingView>([touch_interceptor retain]); |
170 | | - root_views_[viewId] = fml::scoped_nsobject<UIView>([touch_interceptor retain]); |
| 170 | + |
| 171 | + ChildClippingView* clipping_view = |
| 172 | + [[[ChildClippingView alloc] initWithFrame:CGRectZero] autorelease]; |
| 173 | + [clipping_view addSubview:touch_interceptor]; |
| 174 | + root_views_[viewId] = fml::scoped_nsobject<UIView>([clipping_view retain]); |
171 | 175 |
|
172 | 176 | result(nil); |
173 | 177 | } |
|
317 | 321 | return clipCount; |
318 | 322 | } |
319 | 323 |
|
320 | | -UIView* FlutterPlatformViewsController::ReconstructClipViewsChain(int number_of_clips, |
321 | | - UIView* platform_view, |
322 | | - UIView* head_clip_view) { |
323 | | - NSInteger indexInFlutterView = -1; |
324 | | - if (head_clip_view.superview) { |
325 | | - // TODO(cyanglaz): potentially cache the index of oldPlatformViewRoot to make this a O(1). |
326 | | - // https://github.com/flutter/flutter/issues/35023 |
327 | | - indexInFlutterView = [flutter_view_.get().subviews indexOfObject:head_clip_view]; |
328 | | - [head_clip_view removeFromSuperview]; |
329 | | - } |
330 | | - UIView* head = platform_view; |
331 | | - int clipIndex = 0; |
332 | | - // Re-use as much existing clip views as needed. |
333 | | - while (head != head_clip_view && clipIndex < number_of_clips) { |
334 | | - head = head.superview; |
335 | | - clipIndex++; |
336 | | - } |
337 | | - // If there were not enough existing clip views, add more. |
338 | | - while (clipIndex < number_of_clips) { |
339 | | - ChildClippingView* clippingView = |
340 | | - [[[ChildClippingView alloc] initWithFrame:flutter_view_.get().bounds] autorelease]; |
341 | | - [clippingView addSubview:head]; |
342 | | - head = clippingView; |
343 | | - clipIndex++; |
344 | | - } |
345 | | - [head removeFromSuperview]; |
346 | | - |
347 | | - if (indexInFlutterView > -1) { |
348 | | - // The chain was previously attached; attach it to the same position. |
349 | | - [flutter_view_.get() insertSubview:head atIndex:indexInFlutterView]; |
350 | | - } |
351 | | - return head; |
352 | | -} |
353 | | - |
354 | 324 | void FlutterPlatformViewsController::ApplyMutators(const MutatorsStack& mutators_stack, |
355 | 325 | UIView* embedded_view) { |
356 | 326 | FML_DCHECK(CATransform3DEqualToTransform(embedded_view.layer.transform, CATransform3DIdentity)); |
357 | | - UIView* head = embedded_view; |
358 | | - ResetAnchor(head.layer); |
| 327 | + ResetAnchor(embedded_view.layer); |
| 328 | + ChildClippingView* clipView = (ChildClippingView*)embedded_view.superview; |
359 | 329 |
|
360 | | - std::vector<std::shared_ptr<Mutator>>::const_reverse_iterator iter = mutators_stack.Bottom(); |
361 | | - while (iter != mutators_stack.Top()) { |
| 330 | + // The UIKit frame is set based on the logical resolution instead of physical. |
| 331 | + // (https://developer.apple.com/library/archive/documentation/DeviceInformation/Reference/iOSDeviceCompatibility/Displays/Displays.html). |
| 332 | + // However, flow is based on the physical resolution. For example, 1000 pixels in flow equals |
| 333 | + // 500 points in UIKit. And until this point, we did all the calculation based on the flow |
| 334 | + // resolution. So we need to scale down to match UIKit's logical resolution. |
| 335 | + CGFloat screenScale = [UIScreen mainScreen].scale; |
| 336 | + CATransform3D finalTransform = CATransform3DMakeScale(1 / screenScale, 1 / screenScale, 1); |
| 337 | + |
| 338 | + // Mask view needs to be full screen because we might draw platform view pixels outside of the |
| 339 | + // `ChildClippingView`. Since the mask view's frame will be based on the `clipView`'s coordinate |
| 340 | + // system, we need to convert the flutter_view's frame to the clipView's coordinate system. The |
| 341 | + // mask view is not displayed on the screen. |
| 342 | + CGRect maskViewFrame = [flutter_view_ convertRect:flutter_view_.get().frame toView:clipView]; |
| 343 | + FlutterClippingMaskView* maskView = |
| 344 | + [[[FlutterClippingMaskView alloc] initWithFrame:maskViewFrame] autorelease]; |
| 345 | + auto iter = mutators_stack.Begin(); |
| 346 | + while (iter != mutators_stack.End()) { |
362 | 347 | switch ((*iter)->GetType()) { |
363 | 348 | case transform: { |
364 | 349 | CATransform3D transform = GetCATransform3DFromSkMatrix((*iter)->GetMatrix()); |
365 | | - head.layer.transform = CATransform3DConcat(head.layer.transform, transform); |
| 350 | + finalTransform = CATransform3DConcat(transform, finalTransform); |
366 | 351 | break; |
367 | 352 | } |
368 | 353 | case clip_rect: |
| 354 | + [maskView clipRect:(*iter)->GetRect() matrix:finalTransform]; |
| 355 | + break; |
369 | 356 | case clip_rrect: |
370 | | - case clip_path: { |
371 | | - ChildClippingView* clipView = (ChildClippingView*)head.superview; |
372 | | - clipView.layer.transform = CATransform3DIdentity; |
373 | | - [clipView setClip:(*iter)->GetType() |
374 | | - rect:(*iter)->GetRect() |
375 | | - rrect:(*iter)->GetRRect() |
376 | | - path:(*iter)->GetPath()]; |
377 | | - ResetAnchor(clipView.layer); |
378 | | - head = clipView; |
| 357 | + [maskView clipRRect:(*iter)->GetRRect() matrix:finalTransform]; |
| 358 | + break; |
| 359 | + case clip_path: |
| 360 | + [maskView clipPath:(*iter)->GetPath() matrix:finalTransform]; |
379 | 361 | break; |
380 | | - } |
381 | 362 | case opacity: |
382 | 363 | embedded_view.alpha = (*iter)->GetAlphaFloat() * embedded_view.alpha; |
383 | 364 | break; |
384 | 365 | } |
385 | 366 | ++iter; |
386 | 367 | } |
387 | | - // Reverse scale based on screen scale. |
| 368 | + // Reverse the offset of the clipView. |
| 369 | + // The clipView's frame includes the final translate of the final transform matrix. |
| 370 | + // So we need to revese this translate so the platform view can layout at the correct offset. |
388 | 371 | // |
389 | | - // The UIKit frame is set based on the logical resolution instead of physical. |
390 | | - // (https://developer.apple.com/library/archive/documentation/DeviceInformation/Reference/iOSDeviceCompatibility/Displays/Displays.html). |
391 | | - // However, flow is based on the physical resolution. For example, 1000 pixels in flow equals |
392 | | - // 500 points in UIKit. And until this point, we did all the calculation based on the flow |
393 | | - // resolution. So we need to scale down to match UIKit's logical resolution. |
394 | | - CGFloat screenScale = [UIScreen mainScreen].scale; |
395 | | - head.layer.transform = CATransform3DConcat( |
396 | | - head.layer.transform, CATransform3DMakeScale(1 / screenScale, 1 / screenScale, 1)); |
| 372 | + // Note that we don't apply this transform matrix the clippings because clippings happen on the |
| 373 | + // mask view, whose origin is alwasy (0,0) to the flutter_view. |
| 374 | + CATransform3D reverseTranslate = |
| 375 | + CATransform3DMakeTranslation(-clipView.frame.origin.x, -clipView.frame.origin.y, 0); |
| 376 | + embedded_view.layer.transform = CATransform3DConcat(finalTransform, reverseTranslate); |
| 377 | + clipView.maskView = maskView; |
397 | 378 | } |
398 | 379 |
|
399 | 380 | void FlutterPlatformViewsController::CompositeWithParams(int view_id, |
|
406 | 387 | touchInterceptor.alpha = 1; |
407 | 388 |
|
408 | 389 | const MutatorsStack& mutatorStack = params.mutatorsStack(); |
409 | | - int currentClippingCount = CountClips(mutatorStack); |
410 | | - int previousClippingCount = clip_count_[view_id]; |
411 | | - if (currentClippingCount != previousClippingCount) { |
412 | | - clip_count_[view_id] = currentClippingCount; |
413 | | - // If we have a different clipping count in this frame, we need to reconstruct the |
414 | | - // ClippingChildView chain to prepare for `ApplyMutators`. |
415 | | - UIView* oldPlatformViewRoot = root_views_[view_id].get(); |
416 | | - UIView* newPlatformViewRoot = |
417 | | - ReconstructClipViewsChain(currentClippingCount, touchInterceptor, oldPlatformViewRoot); |
418 | | - root_views_[view_id] = fml::scoped_nsobject<UIView>([newPlatformViewRoot retain]); |
419 | | - } |
| 390 | + UIView* clippingView = root_views_[view_id].get(); |
| 391 | + // The frame of the clipping view should be the final bounding rect. |
| 392 | + // Because the translate matrix in the Mutator Stack also includes the offset, |
| 393 | + // when we apply the transforms matrix in |ApplyMutators|, we need |
| 394 | + // to remember to do a reverse translate. |
| 395 | + const SkRect& rect = params.finalBoundingRect(); |
| 396 | + CGFloat screenScale = [UIScreen mainScreen].scale; |
| 397 | + clippingView.frame = CGRectMake(rect.x() / screenScale, rect.y() / screenScale, |
| 398 | + rect.width() / screenScale, rect.height() / screenScale); |
420 | 399 | ApplyMutators(mutatorStack, touchInterceptor); |
421 | 400 | } |
422 | 401 |
|
|
0 commit comments