--- /dev/null
+using System;
+using System.Collections.Generic;
+using Xamarin.Forms.CustomAttributes;
+using Xamarin.Forms.Internals;
+
+#if UITEST
+using NUnit.Framework;
+using System.Threading;
+#endif
+
+namespace Xamarin.Forms.Controls.Issues
+{
+ [Preserve(AllMembers = true)]
+ [Issue(IssueTracker.Github, 4187, "Picker list shows up, when focus is set on other controls", PlatformAffected.Android)]
+ public class Issue4187 : TestCarouselPage
+ {
+ protected override void Init()
+ {
+ var items = new List<Issue4187Model>
+ {
+ new Issue4187Model
+ {
+ Label = "Label 1",
+ PickerSource = new List<string> {"Flower", "Harvest", "Propagation", "Vegetation"},
+ Text = "Text 1",
+ Date = DateTime.Now
+ },
+ new Issue4187Model
+ {
+ Label = "Label 2",
+ PickerSource = new List<string> {"1", "2", "3", "4"},
+ Text = "Text 2",
+ Date = DateTime.Now.AddDays(1)
+ }
+ };
+ var listView = new ListView
+ {
+ VerticalOptions = LayoutOptions.FillAndExpand,
+ HasUnevenRows = true,
+ ItemsSource = items,
+ ItemTemplate = new DataTemplate(() => GetViewCell())
+ };
+
+ var tableView = new TableView
+ {
+ BackgroundColor = Color.Wheat,
+ HasUnevenRows = true,
+ RowHeight = 100
+ };
+ tableView.Root = new TableRoot
+ {
+ new TableSection
+ {
+ GetViewCell(),
+ GetViewCell(),
+ }
+ };
+ tableView.BindingContext = new Issue4187Model
+ {
+ Label = "Label 1",
+ PickerSource = new List<string> { "Flower", "Harvest", "Propagation", "Vegetation" },
+ Text = "Text 1",
+ Date = DateTime.Now
+ };
+
+ Children.Add(new ContentPage
+ {
+ Content = new StackLayout
+ {
+ Children = {
+ GenerateNewPicker(),
+ listView
+ }
+ }
+ });
+
+ Children.Add(new ContentPage
+ {
+ BackgroundColor = Color.Red,
+ Content = new StackLayout
+ {
+ Children = { GenerateNewPicker(), tableView }
+ }
+ });
+
+ Children.Add(new ContentPage
+ {
+ BackgroundColor = Color.Blue,
+ Content = new StackLayout
+ {
+ Children = { GenerateNewPicker() }
+ }
+ });
+ }
+
+ static ViewCell GetViewCell()
+ {
+ var label = new Label { Text = "Status" };
+ label.SetBinding(Label.TextProperty, new Binding(nameof(Issue4187Model.Label)));
+ var picker = new Picker();
+ picker.SetBinding(Picker.ItemsSourceProperty, new Binding(nameof(Issue4187Model.PickerSource)));
+
+ var datePicker = new DatePicker();
+ datePicker.SetBinding(DatePicker.DateProperty, new Binding(nameof(Issue4187Model.Date)));
+
+ var entry = new Entry();
+ entry.SetBinding(Entry.TextProperty, new Binding(nameof(Issue4187Model.Text)));
+
+ return new ViewCell
+ {
+ View = new StackLayout
+ {
+ BackgroundColor = Color.Pink,
+ Children = {
+ label,
+ picker,
+ datePicker,
+ new TimePicker(),
+ entry
+ }
+ }
+ };
+ }
+
+ Picker GenerateNewPicker()
+ {
+ var picker = new Picker();
+ for (int i = 1; i < 100; i++)
+ picker.Items.Add($"item {i}");
+ return picker;
+ }
+
+ [Preserve(AllMembers = true)]
+ class Issue4187Model
+ {
+ public string Label { get; set; }
+ public List<string> PickerSource { get; set; }
+ public string Text { get; set; }
+ public DateTime Date { get; set; }
+ }
+
+#if UITEST && __ANDROID__
+ [Test]
+ public void Issue4187Test()
+ {
+ RunningApp.WaitForElement("Text 1");
+ Assert.AreEqual(7, RunningApp.Query(q => q.TextField().Class("PickerEditText")).Length, "picker count");
+ TapOnPicker(1);
+ Assert.IsTrue(DialogIsOpened(), "#1");
+ RunningApp.Tap("Text 2");
+ Assert.IsFalse(DialogIsOpened(), "#2");
+ TapOnPicker(3);
+ Assert.IsTrue(DialogIsOpened(), "#3");
+ RunningApp.Tap("Text 1");
+ Assert.IsFalse(DialogIsOpened(), "#5");
+
+ // Carousel - first page
+ RunningApp.Back();
+ RunningApp.ScrollUp();
+ TapOnPicker(0);
+ Assert.IsTrue(DialogIsOpened(), "Carousel - #1");
+
+ // Red page
+ RunningApp.SwipeRightToLeft();
+ Assert.IsFalse(DialogIsOpened(), "Carousel - #2");
+ TapOnPicker(0);
+ Assert.IsTrue(DialogIsOpened(), "Carousel - #3");
+
+ // Blue page
+ RunningApp.SwipeRightToLeft();
+ Assert.IsFalse(DialogIsOpened(), "Carousel - #4");
+ TapOnPicker(0);
+ Assert.IsTrue(DialogIsOpened(), "Carousel - #5");
+ }
+
+ void TapOnPicker(int index)
+ {
+ var picker = RunningApp.Query(q => q.TextField().Class("PickerEditText"))[index];
+ var location = picker.Rect;
+ RunningApp.TapCoordinates(location.X + 10, location.Y + location.Height / 2);
+ }
+
+ bool DialogIsOpened()
+ {
+ Thread.Sleep(1500);
+ var frameLayouts = RunningApp.Query(q => q.Class("FrameLayout"));
+ foreach (var layout in frameLayouts)
+ {
+ if (layout.Rect.X > 0 && layout.Rect.Y > 0 && layout.Description.Contains(@"id/content"))
+ {
+ // close dialog
+ RunningApp.Back();
+ Thread.Sleep(1500);
+ return true;
+ }
+ }
+ return false;
+ }
+#endif
+ }
+}
\ No newline at end of file
<Compile Include="$(MSBuildThisFileDirectory)Issue1236.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Issue1259.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Issue1267.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Issue4187.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Issue1305.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Issue1329.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Issue1384.cs" />
using Android.App;
-using Android.Content.Res;
-using Android.Text;
using Android.Util;
-using Android.Widget;
using System;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using Android.Content;
-using Object = Java.Lang.Object;
-using Xamarin.Forms.PlatformConfiguration.AndroidSpecific;
-using System.Collections.Generic;
-using Android.Views;
+using Android.Widget;
namespace Xamarin.Forms.Platform.Android.AppCompat
{
- public class PickerRenderer : ViewRenderer<Picker, EditText>
+ public class PickerRenderer : ViewRenderer<Picker, EditText>, IPickerRenderer
{
AlertDialog _dialog;
bool _disposed;
TextColorSwitcher _textColorSwitcher;
- HashSet<Keycode> availableKeys = new HashSet<Keycode>(new[] {
- Keycode.Tab, Keycode.Forward, Keycode.Back, Keycode.DpadDown, Keycode.DpadLeft, Keycode.DpadRight, Keycode.DpadUp
- });
-
public PickerRenderer(Context context) : base(context)
{
AutoPackage = false;
protected override EditText CreateNativeControl()
{
- return new EditText(Context);
+ return new PickerEditText(Context, this);
}
protected override void Dispose(bool disposing)
((INotifyCollectionChanged)e.NewElement.Items).CollectionChanged += RowsCollectionChanged;
if (Control == null)
{
- EditText textField = CreateNativeControl();
- textField.Focusable = true;
- textField.Clickable = true;
- textField.Tag = this;
- textField.InputType = InputTypes.Null;
- textField.KeyPress += TextFieldKeyPress;
- textField.SetOnClickListener(PickerListener.Instance);
+ var textField = CreateNativeControl();
var useLegacyColorManagement = e.NewElement.UseLegacyColorManagement();
_textColorSwitcher = new TextColorSwitcher(textField.TextColors, useLegacyColorManagement);
-
+
SetNativeControl(textField);
}
UpdateFont();
base.OnElementChanged(e);
}
- void TextFieldKeyPress(object sender, KeyEventArgs e)
- {
- if (availableKeys.Contains(e.KeyCode))
- {
- e.Handled = false;
- return;
- }
- e.Handled = true;
- OnClick();
- }
-
- internal override void OnNativeFocusChanged(bool hasFocus)
- {
- base.OnNativeFocusChanged(hasFocus);
- if (hasFocus)
- OnClick();
- }
-
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
base.OnFocusChangeRequested(sender, e);
if (e.Focus)
- OnClick();
+ CallOnClick();
else if (_dialog != null)
{
_dialog.Hide();
}
}
- void OnClick()
+ void IPickerRenderer.OnClick()
{
Picker model = Element;
if (_dialog == null)
builder.SetItems(items, (s, e) => ((IElementController)model).SetValueFromRenderer(Picker.SelectedIndexProperty, e.Which));
builder.SetNegativeButton(global::Android.Resource.String.Cancel, (o, args) => { });
-
+
((IElementController)Element).SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, true);
_dialog = builder.Create();
{
_textColorSwitcher?.UpdateTextColor(Control, Element.TextColor);
}
-
- class PickerListener : Object, IOnClickListener
- {
- #region Statics
-
- public static readonly PickerListener Instance = new PickerListener();
-
- #endregion
-
- public void OnClick(global::Android.Views.View v)
- {
- var renderer = v.Tag as PickerRenderer;
- renderer?.OnClick();
- }
- }
}
}
\ No newline at end of file
--- /dev/null
+namespace Xamarin.Forms.Platform.Android
+{
+ public interface IPickerRenderer
+ {
+ void OnClick();
+ }
+}
\ No newline at end of file
using System;
-using System.Collections.Generic;
using System.ComponentModel;
using Android.App;
using Android.Content;
-using Android.Content.Res;
-using Android.Text;
using Android.Util;
-using Android.Views;
using Android.Widget;
-using Xamarin.Forms.PlatformConfiguration.AndroidSpecific;
-using AView = Android.Views.View;
-using Object = Java.Lang.Object;
namespace Xamarin.Forms.Platform.Android
{
- public class DatePickerRenderer : ViewRenderer<DatePicker, EditText>
+ public class DatePickerRenderer : ViewRenderer<DatePicker, EditText>, IPickerRenderer
{
DatePickerDialog _dialog;
bool _disposed;
TextColorSwitcher _textColorSwitcher;
- HashSet<Keycode> availableKeys = new HashSet<Keycode>(new[] {
- Keycode.Tab, Keycode.Forward, Keycode.Back, Keycode.DpadDown, Keycode.DpadLeft, Keycode.DpadRight, Keycode.DpadUp
- });
-
public DatePickerRenderer(Context context) : base(context)
{
AutoPackage = false;
protected override EditText CreateNativeControl()
{
- return new EditText(Context) { Focusable = true, Clickable = true, Tag = this };
+ return new PickerEditText(Context, this);
}
protected override void OnElementChanged(ElementChangedEventArgs<DatePicker> e)
{
var textField = CreateNativeControl();
- textField.SetOnClickListener(TextFieldClickHandler.Instance);
- textField.InputType = InputTypes.Null;
- textField.KeyPress += TextFieldKeyPress;
SetNativeControl(textField);
var useLegacyColorManagement = e.NewElement.UseLegacyColorManagement();
UpdateTextColor();
}
- void TextFieldKeyPress(object sender, KeyEventArgs e)
- {
- if (availableKeys.Contains(e.KeyCode))
- {
- e.Handled = false;
- return;
- }
- e.Handled = true;
- OnTextFieldClicked();
- }
-
- internal override void OnNativeFocusChanged(bool hasFocus)
- {
- base.OnNativeFocusChanged(hasFocus);
- if (hasFocus)
- OnTextFieldClicked();
- }
-
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
base.OnFocusChangeRequested(sender, e);
if (e.Focus)
- OnTextFieldClicked();
+ CallOnClick();
else if (_dialog != null)
{
_dialog.Hide();
}
}
- void OnTextFieldClicked()
+ void IPickerRenderer.OnClick()
{
if (_dialog != null && _dialog.IsShowing)
{
{
_textColorSwitcher?.UpdateTextColor(Control, Element.TextColor);
}
-
- class TextFieldClickHandler : Object, IOnClickListener
- {
- public static readonly TextFieldClickHandler Instance = new TextFieldClickHandler();
-
- public void OnClick(AView v)
- {
- ((DatePickerRenderer)v.Tag).OnTextFieldClicked();
- }
- }
}
}
\ No newline at end of file
--- /dev/null
+using Android.Content;
+using Android.Views;
+using Android.Widget;
+using Android.Text;
+using Java.Lang;
+using System.Collections.Generic;
+using Android.Graphics;
+using Android.Runtime;
+
+namespace Xamarin.Forms.Platform.Android
+{
+ public class PickerEditText : EditText
+ {
+ readonly static HashSet<Keycode> availableKeys = new HashSet<Keycode>(new[] {
+ Keycode.Tab, Keycode.Forward, Keycode.Back, Keycode.DpadDown, Keycode.DpadLeft, Keycode.DpadRight, Keycode.DpadUp
+ });
+
+ System.WeakReference<IPickerRenderer> rendererRef;
+
+ internal bool FromFocusSearch;
+
+ public PickerEditText(Context context, IPickerRenderer pickerRenderer) : base(context)
+ {
+ Focusable = true;
+ Clickable = true;
+ InputType = InputTypes.Null;
+ KeyPress += OnKeyPress;
+ rendererRef = new System.WeakReference<IPickerRenderer>(pickerRenderer);
+ SetOnClickListener(PickerListener.Instance);
+ }
+
+ protected override void OnFocusChanged(bool gainFocus, [GeneratedEnum] FocusSearchDirection direction, Rect previouslyFocusedRect)
+ {
+ base.OnFocusChanged(gainFocus, direction, previouslyFocusedRect);
+ if (gainFocus && FromFocusSearch)
+ CallOnClick();
+ FromFocusSearch = false;
+ }
+
+ void OnKeyPress(object sender, KeyEventArgs e)
+ {
+ if (availableKeys.Contains(e.KeyCode))
+ {
+ e.Handled = false;
+ return;
+ }
+ e.Handled = true;
+ CallOnClick();
+ }
+
+ public override bool OnTouchEvent(MotionEvent e)
+ {
+ if (e.Action == MotionEventActions.Up && !IsFocused)
+ RequestFocus();
+ return base.OnTouchEvent(e); // raises the OnClick event if focus is already received
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ KeyPress -= OnKeyPress;
+ rendererRef = null;
+ }
+ base.Dispose(disposing);
+ }
+
+ class PickerListener : Object, IOnClickListener
+ {
+ public static readonly PickerListener Instance = new PickerListener();
+
+ public void OnClick(global::Android.Views.View v)
+ {
+ if (v is PickerEditText picker)
+ {
+ picker.rendererRef.TryGetTarget(out IPickerRenderer renderer);
+ renderer?.OnClick();
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
using Android.App;
-using Android.Content.Res;
using Android.Util;
using Android.Views;
using Android.Widget;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
-using ADatePicker = Android.Widget.DatePicker;
-using ATimePicker = Android.Widget.TimePicker;
-using Object = Java.Lang.Object;
using Orientation = Android.Widget.Orientation;
using Android.Content;
-using Xamarin.Forms.PlatformConfiguration.AndroidSpecific;
-using System.Collections.Generic;
-using Android.Text;
namespace Xamarin.Forms.Platform.Android
{
- public class PickerRenderer : ViewRenderer<Picker, EditText>
+ public class PickerRenderer : ViewRenderer<Picker, EditText>, IPickerRenderer
{
AlertDialog _dialog;
bool _isDisposed;
TextColorSwitcher _textColorSwitcher;
- HashSet<Keycode> availableKeys = new HashSet<Keycode>(new[] {
- Keycode.Tab, Keycode.Forward, Keycode.Back, Keycode.DpadDown, Keycode.DpadLeft, Keycode.DpadRight, Keycode.DpadUp
- });
-
public PickerRenderer(Context context) : base(context)
{
AutoPackage = false;
protected override EditText CreateNativeControl()
{
- return new EditText(Context) { Focusable = true, Clickable = true, Tag = this };
+ return new PickerEditText(Context, this);
}
protected override void OnElementChanged(ElementChangedEventArgs<Picker> e)
if (Control == null)
{
var textField = CreateNativeControl();
- textField.SetOnClickListener(PickerListener.Instance);
- textField.InputType = InputTypes.Null;
- textField.KeyPress += TextFieldKeyPress;
var useLegacyColorManagement = e.NewElement.UseLegacyColorManagement();
_textColorSwitcher = new TextColorSwitcher(textField.TextColors, useLegacyColorManagement);
SetNativeControl(textField);
}
-
+
UpdateFont();
UpdatePicker();
UpdateTextColor();
base.OnElementChanged(e);
}
- void TextFieldKeyPress(object sender, KeyEventArgs e)
- {
- if (availableKeys.Contains(e.KeyCode))
- {
- e.Handled = false;
- return;
- }
- e.Handled = true;
- OnClick();
- }
-
- internal override void OnNativeFocusChanged(bool hasFocus)
- {
- base.OnNativeFocusChanged(hasFocus);
- if (hasFocus)
- OnClick();
- }
-
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
base.OnFocusChangeRequested(sender, e);
if (e.Focus)
- OnClick();
+ CallOnClick();
else if (_dialog != null)
{
_dialog.Hide();
}
}
- void OnClick()
+ void IPickerRenderer.OnClick()
{
Picker model = Element;
{
_textColorSwitcher?.UpdateTextColor(Control, Element.TextColor);
}
-
- class PickerListener : Object, IOnClickListener
- {
- public static readonly PickerListener Instance = new PickerListener();
-
- public void OnClick(global::Android.Views.View v)
- {
- var renderer = v.Tag as PickerRenderer;
- if (renderer == null)
- return;
-
- renderer.OnClick();
- }
- }
}
}
\ No newline at end of file
using Android.App;
using Android.Content;
using Android.Util;
-using Android.Widget;
using Android.Text.Format;
using ATimePicker = Android.Widget.TimePicker;
-using Object = Java.Lang.Object;
-using AView = Android.Views.View;
using Android.OS;
-using Android.Views;
-using System.Collections.Generic;
-using Android.Text;
+using Android.Widget;
namespace Xamarin.Forms.Platform.Android
{
- public class TimePickerRenderer : ViewRenderer<TimePicker, EditText>, TimePickerDialog.IOnTimeSetListener
+ public class TimePickerRenderer : ViewRenderer<TimePicker, EditText>, TimePickerDialog.IOnTimeSetListener, IPickerRenderer
{
AlertDialog _dialog;
TextColorSwitcher _textColorSwitcher;
-
-
- HashSet<Keycode> availableKeys = new HashSet<Keycode>(new[] {
- Keycode.Tab, Keycode.Forward, Keycode.Back, Keycode.DpadDown, Keycode.DpadLeft, Keycode.DpadRight, Keycode.DpadUp
- });
bool Is24HourView
{
protected override EditText CreateNativeControl()
{
- return new EditText(Context) { Focusable = true, Clickable = true, Tag = this };
+ return new PickerEditText(Context, this);
}
protected override void OnElementChanged(ElementChangedEventArgs<TimePicker> e)
{
var textField = CreateNativeControl();
- textField.SetOnClickListener(TimePickerListener.Instance);
- textField.InputType = InputTypes.Null;
- textField.KeyPress += TextFieldKeyPress;
SetNativeControl(textField);
var useLegacyColorManagement = e.NewElement.UseLegacyColorManagement();
Control.TextAlignment = global::Android.Views.TextAlignment.ViewStart;
}
- void TextFieldKeyPress(object sender, KeyEventArgs e)
- {
- if (availableKeys.Contains(e.KeyCode))
- {
- e.Handled = false;
- return;
- }
- e.Handled = true;
- OnClick();
- }
-
- internal override void OnNativeFocusChanged(bool hasFocus)
- {
- base.OnNativeFocusChanged(hasFocus);
- if (hasFocus)
- OnClick();
- }
-
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
base.OnFocusChangeRequested(sender, e);
if (e.Focus)
- OnClick();
+ CallOnClick();
else if (_dialog != null)
{
_dialog.Hide();
return dialog;
}
- void OnClick()
+ void IPickerRenderer.OnClick()
{
if (_dialog != null && _dialog.IsShowing)
{
{
_textColorSwitcher?.UpdateTextColor(Control, Element.TextColor);
}
-
- class TimePickerListener : Object, IOnClickListener
- {
- public static readonly TimePickerListener Instance = new TimePickerListener();
-
- public void OnClick(AView v)
- {
- var renderer = v.Tag as TimePickerRenderer;
- if (renderer == null)
- return;
-
- renderer.OnClick();
- }
- }
}
}
control = (renderer as ITabStop)?.TabStop;
} while (!(control?.Focusable == true || ++attempt >= maxAttempts));
+ // when the user focuses on picker show a popup dialog
+ if (control is PickerEditText picker)
+ picker.FromFocusSearch = true;
+
return control;
}
<Compile Include="IBorderVisualElementRenderer.cs" />
<Compile Include="IDeviceInfoProvider.cs" />
<Compile Include="ITabStop.cs" />
+ <Compile Include="IPickerRenderer.cs" />
+ <Compile Include="Renderers\PickerEditText.cs" />
<Compile Include="Renderers\FormsWebViewClient.cs" />
<Compile Include="Renderers\IImageViewHandler.cs" />
<Compile Include="InnerGestureListener.cs" />