1+ using Microsoft . Maui ;
2+ using Microsoft . Maui . Controls ;
3+ using Microsoft . Maui . Graphics ;
4+ using Microsoft . Maui . Layouts ;
5+
6+ namespace Maui . Controls . Sample . Issues
7+ {
8+ [ Issue ( IssueTracker . Github , 20772 , "Flickering occurs while updating the width of ContentView through PanGestureRecognizer" , PlatformAffected . Android ) ]
9+ public partial class Issue20772 : ContentPage
10+ {
11+ public Issue20772 ( )
12+ {
13+ Content = new CustomView20772 ( ) ;
14+ }
15+ }
16+
17+ public abstract class ControlLayout20772 : Layout
18+ {
19+ internal abstract Size LayoutArrangeChildren ( Rect bounds ) ;
20+
21+ internal abstract Size LayoutMeasure ( double widthConstraint , double heightConstraint ) ;
22+ }
23+
24+ internal class ControlLayoutManager20772 : LayoutManager
25+ {
26+ ControlLayout20772 layout ;
27+ internal ControlLayoutManager20772 ( ControlLayout20772 layout ) : base ( layout )
28+ {
29+ this . layout = layout ;
30+ }
31+
32+ public override Size ArrangeChildren ( Rect bounds ) => this . layout . LayoutArrangeChildren ( bounds ) ;
33+
34+ public override Size Measure ( double widthConstraint , double heightConstraint ) => this . layout . LayoutMeasure ( widthConstraint , heightConstraint ) ;
35+ }
36+
37+ public class CustomView20772 : ControlLayout20772
38+ {
39+ CustomChild20772 _customChild ;
40+ CustomContent20772 _customContent ;
41+ Label _statusLabel ;
42+
43+ public CustomView20772 ( )
44+ {
45+ _customChild = new CustomChild20772 ( ) ;
46+ _customContent = new CustomContent20772 ( ) ;
47+ _statusLabel = new Label
48+ {
49+ AutomationId = "StatusLabel" ,
50+ Text = "Waiting for pan gesture..." ,
51+ HeightRequest = 50
52+ } ;
53+
54+ // Give status label access to CustomChild for width tracking
55+ _customContent . ChildView = _customChild ;
56+ _customContent . StatusLabel = _statusLabel ;
57+
58+ this . Children . Add ( _customChild ) ;
59+ this . Children . Add ( _customContent ) ;
60+ this . Children . Add ( _statusLabel ) ;
61+ }
62+
63+ protected override ILayoutManager CreateLayoutManager ( )
64+ {
65+ return new ControlLayoutManager20772 ( this ) ;
66+ }
67+
68+ internal override Size LayoutArrangeChildren ( Rect bounds )
69+ {
70+ ( _customChild as IView ) . Arrange ( new Rect ( 0 , 0 , _customChild . WidthRequest , _customChild . HeightRequest ) ) ;
71+ ( _customContent as IView ) . Arrange ( new Rect ( _customChild . WidthRequest , 0 , _customContent . WidthRequest , _customContent . HeightRequest ) ) ;
72+ ( _statusLabel as IView ) . Arrange ( new Rect ( 0 , 120 , bounds . Width , 50 ) ) ;
73+ return bounds . Size ;
74+ }
75+
76+ internal override Size LayoutMeasure ( double widthConstraint , double heightConstraint )
77+ {
78+ ( _customChild as IView ) . Measure ( _customChild . WidthRequest , _customChild . HeightRequest ) ;
79+ ( _customContent as IView ) . Measure ( _customContent . WidthRequest , _customContent . HeightRequest ) ;
80+ ( _statusLabel as IView ) . Measure ( widthConstraint , 50 ) ;
81+ return new Size ( widthConstraint , heightConstraint ) ;
82+ }
83+ }
84+
85+ public class CustomChild20772 : ContentView
86+ {
87+ Label _label ;
88+
89+ public CustomChild20772 ( )
90+ {
91+ AutomationId = "CustomChild" ;
92+ BackgroundColor = Colors . Pink ;
93+ HeightRequest = 100 ;
94+ WidthRequest = 180 ;
95+ _label = new Label ( ) { Text = "Custom Child" , HeightRequest = 100 , WidthRequest = 180 } ;
96+ Content = _label ;
97+ }
98+ }
99+
100+ public class CustomContent20772 : ContentView
101+ {
102+ Label _label ;
103+ double _startingChildWidth ;
104+ double _totalPanDistance ;
105+
106+ #nullable enable
107+ public Label ? StatusLabel { get ; set ; }
108+ public ContentView ? ChildView { get ; set ; }
109+ #nullable restore
110+
111+ public CustomContent20772 ( )
112+ {
113+ AutomationId = "CustomContent" ;
114+ BackgroundColor = Colors . Yellow ;
115+ HeightRequest = 100 ;
116+ WidthRequest = 180 ;
117+ _label = new Label ( ) { Text = "Drag here" , HeightRequest = 100 , WidthRequest = 180 } ;
118+ Content = _label ;
119+
120+ PanGestureRecognizer gestureRecognizer = new PanGestureRecognizer ( ) ;
121+ gestureRecognizer . PanUpdated += OnGestureRecognizerPanUpdated ;
122+ GestureRecognizers . Add ( gestureRecognizer ) ;
123+ }
124+
125+ void OnGestureRecognizerPanUpdated ( object sender , PanUpdatedEventArgs e )
126+ {
127+ switch ( e . StatusType )
128+ {
129+ case GestureStatus . Started :
130+ _startingChildWidth = ChildView ? . WidthRequest ?? 180 ;
131+ _totalPanDistance = 0 ;
132+ break ;
133+ case GestureStatus . Running :
134+ _totalPanDistance = e . TotalX ;
135+ OnResizing ( e ) ;
136+ break ;
137+ case GestureStatus . Completed :
138+ case GestureStatus . Canceled :
139+ // Check if final width matches expected width based on pan distance
140+ // With the bug: width changes erratically because coordinates jump
141+ // With fix: width change = pan distance (approximately)
142+ if ( StatusLabel is not null && ChildView is not null )
143+ {
144+ var actualWidthChange = ChildView . WidthRequest - _startingChildWidth ;
145+ var expectedWidthChange = _totalPanDistance ;
146+ var difference = Math . Abs ( actualWidthChange - expectedWidthChange ) ;
147+
148+ // Allow some tolerance for timing/rounding
149+ // With the bug, the difference would be huge due to coordinate jumps
150+ StatusLabel . Text = $ "WidthChange:{ actualWidthChange : F0} ,Expected:{ expectedWidthChange : F0} ";
151+ }
152+ break ;
153+ }
154+ }
155+
156+ void OnResizing ( PanUpdatedEventArgs e )
157+ {
158+ // The key issue: when we update WidthRequest, the view moves
159+ // With relative coordinates (bug): next TotalX is wrong because view position changed
160+ // With raw coordinates (fix): TotalX stays consistent
161+ if ( ChildView is not null )
162+ {
163+ // Set width directly based on starting width + total pan distance
164+ // This is the expected behavior with raw coordinates
165+ ChildView . WidthRequest = _startingChildWidth + e . TotalX ;
166+ ChildView . InvalidateMeasure ( ) ;
167+ }
168+ }
169+ }
170+ }
0 commit comments