我正在努力解决 Xamarin Forms Android 的焦点问题。
我们通常在带有外部键盘的手持设备上运行此应用程序。
我的应用程序顶部有一个导航栏,入口通常位于底部。
在导航栏中有“退出”图像按钮返回上一页和一些其他按钮执行特殊功能。
每个页面的页脚都有条目,但很少有页面没有条目。
当带有条目的页面正确聚焦并且当我按下回车键时,与条目的“ReturnCommand”关联的 EnterCommand 被正确触发,当没有条目时,第一个元素被聚焦并且回车键触发“点击/按下“退出”图像按钮的“返回返回上一页的结果。
如何防止图像按钮聚焦或如何聚焦页面上的其他控件?
这是每个页面的基本样式:
<Grid HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand"
BackgroundColor="{StaticResource NavigationBarColor}"
Padding="0,5">
<Grid.Resources>
<Style TargetType="ImageButton">
<Style.Setters>
<Setter Property="Padding"
Value="0" />
<Setter Property="BackgroundColor"
Value="Transparent" />
<Setter Property="VerticalOptions"
Value="CenterAndExpand" />
</Style.Setters>
</Style>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="33" />
<ColumnDefinition />
<ColumnDefinition Width="33" />
<ColumnDefinition Width="33" />
</Grid.ColumnDefinitions>
<ImageButton Command="{TemplateBinding EscapeCommand}"
Source="{helpers:ImageResource Previous}"
IsVisible="{Binding IsEnabled, Source={RelativeSource Mode=Self}}" />
<ImageButton Command="{TemplateBinding OpenMenuCommand}"
Source="{helpers:ImageResource Menu}"
IsVisible="{Binding IsEnabled, Source={RelativeSource Mode=Self}}" />
<Label Text="{TemplateBinding ViewModel.Title}"
Grid.Column="1"
FontSize="Medium"
Padding="0,2"
FontAttributes="Bold"
MaxLines="2"
LineBreakMode="TailTruncation"
VerticalOptions="FillAndExpand"
VerticalTextAlignment="Center" />
<ImageButton Command="{TemplateBinding ApriOpzioniCommand}"
Source="{helpers:ImageResource More}"
IsVisible="{Binding IsEnabled, Source={RelativeSource Mode=Self}}"
Grid.Column="2" />
<ImageButton Command="{TemplateBinding ViewModel.OkCommand}"
Source="{helpers:ImageResource Next}"
IsVisible="{Binding IsEnabled, Source={RelativeSource Mode=Self}}"
IsEnabled="False"
Grid.Column="3" />
</Grid>
<!-- End NavigationBar -->
<ScrollView x:Name="scrollViewContent"
Style="{StaticResource ScrollViewBaseStyle}"
Grid.Row="1">
<StackLayout VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand"
Spacing="0">
<!-- Begin Header -->
<Frame Style="{StaticResource FrameBaseStyle}"
CornerRadius="6">
<ContentView Content="{TemplateBinding Header}"
Style="{StaticResource ContentViewStyle}"
TabIndex="2"
IsTabStop="False" />
</Frame>
<!-- End Header -->
<!-- Begin Content -->
<ContentPresenter Style="{StaticResource ContentPresenterBaseStyle}"
TabIndex="1" />
<!-- End Content -->
</StackLayout>
</ScrollView>
<!-- Begin Footer -->
<ContentView Content="{TemplateBinding Footer}"
Grid.Row="2"
Margin="5"
TabIndex="0"
Style="{StaticResource ContentViewStyle}" />
各页面的视图模型库:
public abstract class ContentPageBase<TViewModel> : ReactiveContentPage<TViewModel>, IAlertPage, ITemplatedPage, IDisposable
where TViewModel : NavigationViewModelBase
{
private bool _initialized;
private readonly Subject<int> _pagesCount;
private readonly Subject<bool> _isOpzioniDisponibili;
protected ContentPageBase(IKeyboardService keyboardListener)
{
KeyboardListener = keyboardListener ?? throw new ArgumentNullException(nameof(keyboardListener));
_pagesCount = new Subject<int>();
_isOpzioniDisponibili = new Subject<bool>();
ApriOpzioniCommand = ReactiveCommand.Create(
MostraNascondiOpzioni,
_isOpzioniDisponibili);
ChiudiOpzioniCommand = ReactiveCommand.Create(
NascondiOpzioni,
_isOpzioniDisponibili);
EscapeCommand = ReactiveCommand.CreateFromTask(
OnEscapePressedAsync,
this.WhenAnyValue(v => v.IsOpzioniVisible)
.CombineLatest(_pagesCount, (v1, v2) => v1 || v2 > 1));
OpenMenuCommand = ReactiveCommand.Create(
OnMenuPressed,
_pagesCount.Select(c => c == 1 && !IsMenuShown));
InitLayout();
this.WhenActivated(disposables =>
{
RegisterInteractions(disposables);
InitBindings(disposables);
WhenActivated(disposables);
WhenViewFocused();
_initialized = true;
});
}
public ReactiveCommand<Unit, Unit> OpenMenuCommand { get; }
public ReactiveCommand<Unit, Unit> ApriOpzioniCommand { get; }
public ReactiveCommand<Unit, Unit> ChiudiOpzioniCommand { get; }
public ReactiveCommand<Unit, Unit> EscapeCommand { get; }
public bool IsMenuShown =>
(Application.Current.MainPage as MasterDetailPage)?.IsPresented == true;
protected IKeyboardService KeyboardListener { get; }
#region Bindable properties
public static readonly BindableProperty HeaderBackgroundColorProperty = BindableProperty.Create(
propertyName: nameof(HeaderBackgroundColor),
returnType: typeof(Color),
declaringType: typeof(Color),
defaultValue: Color.FromHex("#363636"),
defaultBindingMode: BindingMode.OneTime);
public static readonly BindableProperty HeaderPaddingProperty = BindableProperty.Create(
propertyName: nameof(HeaderPadding),
returnType: typeof(Thickness),
declaringType: typeof(ContentPageBase<>),
defaultValue: new Thickness(10),
defaultBindingMode: BindingMode.OneTime);
public static readonly BindableProperty HeaderMarginProperty = BindableProperty.Create(
propertyName: nameof(HeaderMargin),
returnType: typeof(Thickness),
declaringType: typeof(ContentPageBase<>),
defaultValue: new Thickness(10),
defaultBindingMode: BindingMode.OneTime);
public static readonly BindableProperty HeaderProperty = BindableProperty.Create(
propertyName: nameof(Header),
returnType: typeof(View),
declaringType: typeof(ContentPageBase<>),
defaultValue: null,
defaultBindingMode: BindingMode.OneTime);
public static readonly BindableProperty FooterProperty = BindableProperty.Create(
propertyName: nameof(Footer),
returnType: typeof(View),
declaringType: typeof(ContentPageBase<>),
defaultValue: null,
defaultBindingMode: BindingMode.OneTime);
public static readonly BindableProperty OpzioniProperty = BindableProperty.Create(
propertyName: nameof(Opzioni),
returnType: typeof(View),
declaringType: typeof(ContentPageBase<>),
defaultValue: null,
defaultBindingMode: BindingMode.OneTime);
public static readonly BindableProperty IsOpzioniVisibleProperty = BindableProperty.Create(
propertyName: nameof(IsOpzioniVisible),
returnType: typeof(bool),
declaringType: typeof(ContentPageBase<>),
defaultValue: false,
defaultBindingMode: BindingMode.OneWay);
public static readonly BindableProperty ViewCellTemplateProperty = BindableProperty.Create(
propertyName: nameof(ViewCellTemplate),
returnType: typeof(DataTemplate),
declaringType: typeof(ContentPageBase<>),
defaultValue: null,
defaultBindingMode: BindingMode.OneTime);
public static readonly BindableProperty RiepilogoViewProperty = BindableProperty.Create(
propertyName: nameof(RiepilogoView),
returnType: typeof(IViewFor),
declaringType: typeof(ContentPageBase<>),
defaultValue: null,
defaultBindingMode: BindingMode.OneTime);
public Color HeaderBackgroundColor
{
get => (Color)GetValue(HeaderBackgroundColorProperty);
set => SetValue(HeaderBackgroundColorProperty, value);
}
public Thickness HeaderMargin
{
get => (Thickness)GetValue(HeaderMarginProperty);
set => SetValue(HeaderMarginProperty, value);
}
public Thickness HeaderPadding
{
get => (Thickness)GetValue(HeaderPaddingProperty);
set => SetValue(HeaderPaddingProperty, value);
}
public View Header
{
get => (View)GetValue(HeaderProperty);
set => SetValue(HeaderProperty, value);
}
public View Footer
{
get => (View)GetValue(FooterProperty);
set => SetValue(FooterProperty, value);
}
public View Opzioni
{
get => (View)GetValue(OpzioniProperty);
set => SetValue(OpzioniProperty, value);
}
public bool IsOpzioniVisible
{
get => (bool)GetValue(IsOpzioniVisibleProperty);
set => SetValue(IsOpzioniVisibleProperty, value);
}
public DataTemplate ViewCellTemplate
{
get => (DataTemplate)GetValue(ViewCellTemplateProperty);
set => SetValue(ViewCellTemplateProperty, value);
}
public IViewFor RiepilogoView
{
get => (IViewFor)GetValue(RiepilogoViewProperty);
set => SetValue(RiepilogoViewProperty, value);
}
#endregion Bindable properties
private void InitBindings(CompositeDisposable disposables)
{
KeyboardListener.KeysPressed
.Where(k => k.Key == KeyCode.F10)
.Select(_ => Unit.Default)
.InvokeCommand(ApriOpzioniCommand)
.DisposeWith(disposables);
KeyboardListener.KeysPressed
.Where(k => k.Key == KeyCode.Escape)
.Select(_ => Unit.Default)
.InvokeCommand(EscapeCommand)
.DisposeWith(disposables);
this.OneWayBind(ViewModel, vm => vm.Title, v => v.Title)
.DisposeWith(disposables);
//esegue il comando alla prima attivazione o sempre a seconda della proprietà nel ViewModel
if (ViewModel.OnActivateCommand != null && (!_initialized || !ViewModel.ActivateOnce))
{
Observable.Return(Unit.Default)
.InvokeCommand(this, v => v.ViewModel.OnActivateCommand)
.DisposeWith(disposables);
}
}
private void InitLayout()
{
NavigationPage.SetHasNavigationBar(this, false);
ControlTemplate = (ControlTemplate)Application.Current.Resources["MainPageTemplate"];
}
protected abstract void WhenActivated(CompositeDisposable disposables);
protected abstract void WhenViewFocused();
protected override async void OnAppearing()
{
try
{
_pagesCount.OnNext(Application.Current.MainPage is MasterDetailPage ? Navigation.NavigationStack.Count : 0);
_pagesCount.OnCompleted();
_isOpzioniDisponibili.OnNext(Opzioni != null);
_isOpzioniDisponibili.OnCompleted();
if (IsOpzioniVisible)
{
NascondiOpzioni();
}
if (Opzioni is IBindable view)
{
view.InitBindings();
}
if (RiepilogoView is IBindable viewRiepilogo)
{
viewRiepilogo.InitBindings();
}
var scroller = (ScrollView)GetTemplateChild("scrollViewContent");
await scroller.ScrollToAsync(0, scroller.Height, false).ConfigureAwait(true);
}
catch (Exception ex)
{
await DisplayExceptionAsync(ex).ConfigureAwait(true);
}
}
protected override void OnDisappearing()
{
if (Opzioni is IBindable view)
{
view.DisposeBindings();
}
if (RiepilogoView is IBindable viewRiepilogo)
{
viewRiepilogo.DisposeBindings();
}
base.OnDisappearing();
}
private async Task OnEscapePressedAsync()
{
// Se il menù è aperto
if (IsOpzioniVisible)
{
NascondiOpzioni();
}
// Se non sono la pagina root dello stack di navigazione
else if (Navigation != null && Navigation.NavigationStack.Count > 1)
{
await ViewModel.NavigationService.NavigateBackAsync().ConfigureAwait(true);
}
}
private void OnMenuPressed() =>
((MasterDetailPage)Application.Current.MainPage).IsPresented = true;
private void MostraNascondiOpzioni()
{
if (Opzioni != null)
{
if (IsOpzioniVisible)
{
NascondiOpzioni();
}
else
{
MostraOpzioni();
}
}
}
public void MostraOpzioni() => IsOpzioniVisible = true;
public void NascondiOpzioni()
{
IsOpzioniVisible = false;
WhenViewFocused();
}
}
这是一个没有条目的页面,它受到问题的影响:
<?xml version="1.0" encoding="utf-8" ?>
<views:ContentPageBase xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:vm="clr-namespace:MadLab.Spectrum.ViewModels.Inventario;assembly=MadLab.Spectrum.ViewModels"
xmlns:views="clr-namespace:MadLab.Spectrum.Views"
xmlns:vCommon="clr-namespace:MadLab.Spectrum.Views.Common"
xmlns:vDatiLotto="clr-namespace:MadLab.Spectrum.Views.Common.DatiAggiuntiviLotto"
x:Class="MadLab.Spectrum.Views.Inventario.RiepilogoConfermaPage"
x:TypeArguments="vm:RiepilogoConfermaViewModel">
<ContentPage.Content>
<Label x:Name="lblAvvisoQuantita"
VerticalOptions="Center"
HorizontalOptions="Center"
FontAttributes="Bold"
FontSize="Medium"
HorizontalTextAlignment="Center" />
</ContentPage.Content>
<views:ContentPageBase.Footer>
<StackLayout Orientation="Horizontal"
VerticalOptions="FillAndExpand"
HorizontalOptions="CenterAndExpand"
Margin="10">
<Image Source="{helper:ImageResource EnterKeyGreen32}">
<Image.GestureRecognizers>
<TapGestureRecognizer x:Name="gestRecognizer" />
</Image.GestureRecognizers>
</Image>
<Label Text="{Binding Text, Source={x:Reference view}}"
FontSize="Large"
FontAttributes="Bold"
TextColor="{StaticResource GreenColor}"
HorizontalOptions="CenterAndExpand"
VerticalOptions="CenterAndExpand"
VerticalTextAlignment="Center"
Padding="0,0,0,5" />
</StackLayout>
</views:ContentPageBase.Footer>
</views:ContentPageBase>
我已经尝试过 IsTabStop = false、TabIndex 和我在这里找到的所有提示,但没有任何效果,它保持焦点并按回车键给出相同的结果。 当我按下回车键时,什么都不应该做(似乎焦点在 Android 上是强制性的)。