diff --git a/src/Controls/samples/Controls.Sample.Sandbox/MainPage.xaml b/src/Controls/samples/Controls.Sample.Sandbox/MainPage.xaml
index 7363d18deadd..f0a51b94e3d3 100644
--- a/src/Controls/samples/Controls.Sample.Sandbox/MainPage.xaml
+++ b/src/Controls/samples/Controls.Sample.Sandbox/MainPage.xaml
@@ -2,4 +2,27 @@
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Maui.Controls.Sample.MainPage"
xmlns:local="clr-namespace:Maui.Controls.Sample">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Controls/samples/Controls.Sample.Sandbox/MainPage.xaml.cs b/src/Controls/samples/Controls.Sample.Sandbox/MainPage.xaml.cs
index b7744fa262ea..00b2cef97998 100644
--- a/src/Controls/samples/Controls.Sample.Sandbox/MainPage.xaml.cs
+++ b/src/Controls/samples/Controls.Sample.Sandbox/MainPage.xaml.cs
@@ -2,8 +2,24 @@
public partial class MainPage : ContentPage
{
+ private bool _isRtl = false;
+
public MainPage()
{
InitializeComponent();
}
+
+ private void OnToggleClicked(object sender, EventArgs e)
+ {
+ _isRtl = !_isRtl;
+
+ var newFlowDirection = _isRtl ? FlowDirection.RightToLeft : FlowDirection.LeftToRight;
+
+ SearchBar1.FlowDirection = newFlowDirection;
+ SearchBar2.FlowDirection = newFlowDirection;
+ SearchBar3.FlowDirection = newFlowDirection;
+
+ ToggleButton.Text = _isRtl ? "Toggle All to LTR" : "Toggle All to RTL";
+ StatusLabel.Text = $"Current state: {(_isRtl ? "Right to Left" : "Left to Right")}";
+ }
}
\ No newline at end of file
diff --git a/src/Core/src/Handlers/SearchBar/SearchBarHandler.Android.cs b/src/Core/src/Handlers/SearchBar/SearchBarHandler.Android.cs
index 1d21a1dc52fc..4a50de0e31fa 100644
--- a/src/Core/src/Handlers/SearchBar/SearchBarHandler.Android.cs
+++ b/src/Core/src/Handlers/SearchBar/SearchBarHandler.Android.cs
@@ -132,6 +132,17 @@ public static void MapFocus(ISearchBarHandler handler, ISearchBar searchBar, obj
handler.QueryEditor?.Focus(request);
}
+ internal static void MapFlowDirection(ISearchBarHandler handler, ISearchBar searchBar)
+ {
+ // Ensure we have a platform view
+ if (handler.PlatformView == null)
+ return;
+
+ // Update flow direction for both SearchView and its internal EditText
+ // This will handle both direct assignments and inherited values from parent
+ handler.PlatformView.UpdateFlowDirection(searchBar, handler.QueryEditor);
+ }
+
void OnQueryTextSubmit(object? sender, QueryTextSubmitEventArgs e)
{
VirtualView.SearchButtonPressed();
diff --git a/src/Core/src/Handlers/SearchBar/SearchBarHandler.cs b/src/Core/src/Handlers/SearchBar/SearchBarHandler.cs
index ef6615dbbc30..45e659cfd8a5 100644
--- a/src/Core/src/Handlers/SearchBar/SearchBarHandler.cs
+++ b/src/Core/src/Handlers/SearchBar/SearchBarHandler.cs
@@ -19,6 +19,9 @@ public partial class SearchBarHandler : ISearchBarHandler
{
#if __IOS__
[nameof(ISearchBar.IsEnabled)] = MapIsEnabled,
+#endif
+#if ANDROID
+ [nameof(IView.FlowDirection)] = MapFlowDirection,
#endif
[nameof(ISearchBar.Background)] = MapBackground,
[nameof(ISearchBar.CharacterSpacing)] = MapCharacterSpacing,
diff --git a/src/Core/src/Platform/Android/SearchViewExtensions.cs b/src/Core/src/Platform/Android/SearchViewExtensions.cs
index cdfe739f80bc..a6d1d4988597 100644
--- a/src/Core/src/Platform/Android/SearchViewExtensions.cs
+++ b/src/Core/src/Platform/Android/SearchViewExtensions.cs
@@ -1,7 +1,10 @@
-using Android.Content.Res;
+using System;
+using Android.Content.Res;
using Android.Text;
+using Android.Views;
using Android.Widget;
using SearchView = AndroidX.AppCompat.Widget.SearchView;
+using ATextDirection = Android.Views.TextDirection;
namespace Microsoft.Maui.Platform
{
@@ -171,5 +174,83 @@ internal static void SetInputType(this SearchView searchView, ISearchBar searchB
editText.SetInputType(searchBar);
}
+
+ internal static void UpdateFlowDirection(this SearchView searchView, ISearchBar searchBar, EditText? editText = null)
+ {
+ // Get the internal EditText first
+ editText ??= searchView.GetFirstChildOfType();
+
+ // Resolve the effective FlowDirection for the SearchBar
+ // This handles both direct assignments and inherited values from parent elements
+ var effectiveFlowDirection = GetEffectiveFlowDirection(searchBar);
+
+ void UpdateFlowDirectionForViews(EditText et)
+ {
+ // Apply the resolved FlowDirection to both SearchView and EditText
+ // We can't rely on inheritance for SearchBar because we're applying FlowDirection
+ // to the inner EditText, so we need to handle MatchParent scenarios manually
+ switch (effectiveFlowDirection)
+ {
+ case FlowDirection.RightToLeft:
+ searchView.LayoutDirection = Android.Views.LayoutDirection.Rtl;
+ et.LayoutDirection = Android.Views.LayoutDirection.Rtl;
+#pragma warning disable CA1416 // Introduced in API 23
+ et.TextDirection = ATextDirection.FirstStrongRtl;
+#pragma warning restore CA1416
+ break;
+ case FlowDirection.LeftToRight:
+ searchView.LayoutDirection = Android.Views.LayoutDirection.Ltr;
+ et.LayoutDirection = Android.Views.LayoutDirection.Ltr;
+#pragma warning disable CA1416 // Introduced in API 23
+ et.TextDirection = ATextDirection.FirstStrongLtr;
+#pragma warning restore CA1416
+ break;
+ default: // MatchParent or unspecified - use system default (LTR)
+ searchView.LayoutDirection = Android.Views.LayoutDirection.Ltr;
+ et.LayoutDirection = Android.Views.LayoutDirection.Ltr;
+#pragma warning disable CA1416 // Introduced in API 23
+ et.TextDirection = ATextDirection.FirstStrongLtr;
+#pragma warning restore CA1416
+ break;
+ }
+ }
+
+ if (editText != null)
+ {
+ UpdateFlowDirectionForViews(editText);
+ }
+ else
+ {
+ // If EditText isn't available yet, post a delayed update
+ // This can happen during initialization when the SearchView hierarchy isn't fully built
+ searchView.Post(() =>
+ {
+ var delayedEditText = searchView.GetFirstChildOfType();
+ if (delayedEditText != null)
+ {
+ UpdateFlowDirectionForViews(delayedEditText);
+ }
+ });
+ }
+ }
+
+ private static FlowDirection GetEffectiveFlowDirection(ISearchBar searchBar)
+ {
+ // If SearchBar has an explicit FlowDirection, use it
+ if (searchBar.FlowDirection != FlowDirection.MatchParent)
+ return searchBar.FlowDirection;
+
+ // For MatchParent, traverse up the parent chain to find the effective FlowDirection
+ var currentView = searchBar.Parent;
+ while (currentView != null)
+ {
+ if (currentView is IView view && view.FlowDirection != FlowDirection.MatchParent)
+ return view.FlowDirection;
+ currentView = currentView.Parent;
+ }
+
+ // Default to LeftToRight if no explicit FlowDirection is found in the hierarchy
+ return FlowDirection.LeftToRight;
+ }
}
}
\ No newline at end of file
diff --git a/src/Core/tests/DeviceTests/Handlers/SearchBar/SearchBarHandlerTests.Android.cs b/src/Core/tests/DeviceTests/Handlers/SearchBar/SearchBarHandlerTests.Android.cs
index 4f9f9af09ef6..5571d0e5753a 100644
--- a/src/Core/tests/DeviceTests/Handlers/SearchBar/SearchBarHandlerTests.Android.cs
+++ b/src/Core/tests/DeviceTests/Handlers/SearchBar/SearchBarHandlerTests.Android.cs
@@ -358,5 +358,149 @@ bool GetNativeIsSpellCheckEnabled(SearchBarHandler searchBarHandler)
return !inputTypes.HasFlag(InputTypes.TextFlagNoSuggestions);
}
+
+ Android.Views.LayoutDirection GetNativeFlowDirection(SearchBarHandler searchBarHandler)
+ {
+ var searchView = GetNativeSearchBar(searchBarHandler);
+ return searchView.LayoutDirection;
+ }
+
+ Android.Views.LayoutDirection GetNativeEditTextFlowDirection(SearchBarHandler searchBarHandler)
+ {
+ var searchView = GetNativeSearchBar(searchBarHandler);
+ var editText = searchView.GetChildrenOfType().FirstOrDefault();
+ return editText?.LayoutDirection ?? Android.Views.LayoutDirection.Inherit;
+ }
+
+ Android.Views.TextDirection GetNativeEditTextTextDirection(SearchBarHandler searchBarHandler)
+ {
+ var searchView = GetNativeSearchBar(searchBarHandler);
+ var editText = searchView.GetChildrenOfType().FirstOrDefault();
+#pragma warning disable CA1416 // Introduced in API 23
+ return editText?.TextDirection ?? Android.Views.TextDirection.Inherit;
+#pragma warning restore CA1416
+ }
+
+ [Fact(DisplayName = "FlowDirection Initializes Correctly")]
+ public async Task FlowDirectionInitializesCorrectly()
+ {
+ var searchBarStub = new SearchBarStub()
+ {
+ Text = "Test",
+ FlowDirection = FlowDirection.RightToLeft
+ };
+
+ var values = await GetValueAsync(searchBarStub, (handler) =>
+ {
+ return new
+ {
+ ViewValue = searchBarStub.FlowDirection,
+ PlatformViewValue = GetNativeFlowDirection(handler),
+ EditTextFlowDirection = GetNativeEditTextFlowDirection(handler)
+ };
+ });
+
+ Assert.Equal(FlowDirection.RightToLeft, values.ViewValue);
+ Assert.Equal(Android.Views.LayoutDirection.Rtl, values.PlatformViewValue);
+ // EditText should have the resolved FlowDirection applied directly (not inherit)
+ Assert.Equal(Android.Views.LayoutDirection.Rtl, values.EditTextFlowDirection);
+ }
+
+ [Fact(DisplayName = "FlowDirection Updates Correctly")]
+ public async Task FlowDirectionUpdatesCorrectly()
+ {
+ var searchBarStub = new SearchBarStub()
+ {
+ Text = "Test",
+ FlowDirection = FlowDirection.LeftToRight
+ };
+
+ await ValidatePropertyUpdatesValue(
+ searchBarStub,
+ nameof(IView.FlowDirection),
+ GetNativeFlowDirection,
+ FlowDirection.RightToLeft,
+ Android.Views.LayoutDirection.Rtl);
+
+ // Also verify EditText flow direction updates
+ var handler = CreateHandler(searchBarStub);
+ await InvokeOnMainThreadAsync(() =>
+ {
+ searchBarStub.FlowDirection = FlowDirection.RightToLeft;
+ var editTextDirection = GetNativeEditTextFlowDirection(handler);
+ // EditText should have the resolved FlowDirection applied directly (not inherit)
+ Assert.Equal(Android.Views.LayoutDirection.Rtl, editTextDirection);
+ });
+ }
+
+ [Fact(DisplayName = "FlowDirection Inherits From Parent Correctly")]
+ public async Task FlowDirectionInheritsFromParentCorrectly()
+ {
+ await InvokeOnMainThreadAsync(() =>
+ {
+ // Create a SearchBar with default FlowDirection (MatchParent)
+ var searchBarStub = new SearchBarStub()
+ {
+ Text = "Test",
+ FlowDirection = FlowDirection.MatchParent
+ };
+
+ // Create a parent layout with RTL FlowDirection
+ var layoutStub = new LayoutStub()
+ {
+ FlowDirection = FlowDirection.RightToLeft
+ };
+ layoutStub.Add(searchBarStub);
+
+ // Create handlers and set up the hierarchy
+ var layoutHandler = CreateHandler(layoutStub);
+ var searchBarHandler = CreateHandler(searchBarStub);
+
+ // Simulate the parent-child relationship in the platform view hierarchy
+ if (layoutHandler.PlatformView is Android.Views.ViewGroup parentView)
+ {
+ parentView.LayoutDirection = Android.Views.LayoutDirection.Rtl;
+ parentView.AddView(searchBarHandler.PlatformView);
+ }
+
+ // Verify that SearchBar resolves and applies the RTL direction from parent
+ var searchView = GetNativeSearchBar(searchBarHandler);
+ var editText = searchView.GetChildrenOfType().FirstOrDefault();
+
+ // Both SearchView and EditText should have RTL resolved from parent chain
+ Assert.Equal(Android.Views.LayoutDirection.Rtl, searchView.LayoutDirection);
+ Assert.Equal(Android.Views.LayoutDirection.Rtl, editText?.LayoutDirection);
+ });
+ }
+
+ [Fact(DisplayName = "FlowDirection Text Direction Set Correctly")]
+ public async Task FlowDirectionTextDirectionSetCorrectly()
+ {
+ var searchBarStub = new SearchBarStub()
+ {
+ Text = "Test",
+ FlowDirection = FlowDirection.RightToLeft
+ };
+
+ var values = await GetValueAsync(searchBarStub, (handler) =>
+ {
+ return new
+ {
+ ViewValue = searchBarStub.FlowDirection,
+ PlatformViewValue = GetNativeFlowDirection(handler),
+ EditTextFlowDirection = GetNativeEditTextFlowDirection(handler),
+ EditTextTextDirection = GetNativeEditTextTextDirection(handler)
+ };
+ });
+
+ Assert.Equal(FlowDirection.RightToLeft, values.ViewValue);
+ Assert.Equal(Android.Views.LayoutDirection.Rtl, values.PlatformViewValue);
+ Assert.Equal(Android.Views.LayoutDirection.Rtl, values.EditTextFlowDirection);
+#pragma warning disable CA1416 // Introduced in API 23
+ Assert.Equal(Android.Views.TextDirection.FirstStrongRtl, values.EditTextTextDirection);
+#pragma warning restore CA1416
+ }
+
+
}
}
\ No newline at end of file