简体   繁体   English

滚动视图控件在 .Net MAUI (Android) 中无法正常工作

[英]Scrollview control not working properly in .Net MAUI (Android)

I have created custom tab control using ScrollView control and Bindable StackLayout control.我使用 ScrollView 控件和 Bindable StackLayout 控件创建了自定义选项卡控件。

I have first created this solution in Xamarin.Forms (VS for Mac 2019) and it works fine in both platforms, but the same solution when developed in .Net MAUI (VS for Mac 2022 Prev) it's not working properly in Android.我首先在 Xamarin.Forms(VS for Mac 2019)中创建了这个解决方案,它在两个平台上都可以正常工作,但是在 .Net MAUI(VS for Mac 2022 Prev)中开发的相同解决方案在 Android 中无法正常工作。

Update 30 Jun 2022 2022 年 6 月 30 日更新

There is an issue with BindableLayout (StackLayout) properties in MAUI currently so when we are changing values it does not get reflected, and because of this, I think I'm facing this issue.目前 MAUI 中的 BindableLayout (StackLayout) 属性存在问题,因此当我们更改值时,它不会得到反映,因此,我认为我正面临这个问题。 Here is the reference这是参考

Here is what I have done so far:这是我到目前为止所做的:

MainPage.xaml主页.xaml

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:vm="clr-namespace:poc_maui.ViewModels"
             x:Class="poc_maui.Views.HomePage"
             xmlns:tabs="clr-namespace:poc_maui.Views.SubViews"
             Title="HomePage">

    <ContentPage.BindingContext>
        <vm:MainPageViewModel />
    </ContentPage.BindingContext>

    <Grid RowDefinitions="50, *" RowSpacing="0">
        <ScrollView Grid.Row="0" Orientation="Horizontal" VerticalOptions="Start" HorizontalScrollBarVisibility="Never"
                    Scrolled="ScrollView_Scrolled">
            <StackLayout x:Name="TabsView"
                        Orientation="Horizontal"
                        BindableLayout.ItemsSource="{Binding Tabs}" Spacing="0">
                <BindableLayout.ItemTemplate>
                    <DataTemplate>
                        <Grid RowDefinitions="*, 4" RowSpacing="0">
                            <Label Grid.Row="0"
                                   Text="{Binding TabTitle}"
                                   TextColor="White"
                                   BackgroundColor="navy"
                                   Padding="20,0"
                                   VerticalTextAlignment="Center"
                                   HorizontalTextAlignment="Center"
                                   FontSize="12"
                                   HeightRequest="40"/>
                            <BoxView Grid.Row="1"
                                     Color="Yellow"
                                     IsVisible="{Binding IsSelected}"/>

                            <Grid.GestureRecognizers>
                                <TapGestureRecognizer Command="{Binding Path=BindingContext.TabChangedCommand,
                                                                        Source={x:Reference TabsView}}"
                                                      CommandParameter="{Binding .}"/>
                            </Grid.GestureRecognizers>
                        </Grid>

                    </DataTemplate>
                </BindableLayout.ItemTemplate>
            </StackLayout>

        </ScrollView>

        <tabs:ParentRecordTabView Grid.Row="1" IsVisible="{Binding IsParentRecordTabVisible}"
                                          VerticalOptions="FillAndExpand"/>
        <tabs:AdditionalInfoTabView Grid.Row="1" IsVisible="{Binding IsAdditionalInfoTabVisible}"
                                            VerticalOptions="FillAndExpand" />
    </Grid>

</ContentPage>

MainPageViewModel MainPageViewModel

using System;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Windows.Input;
using poc_maui.Models;

namespace poc_maui.ViewModels
{
    public class MainPageViewModel : BaseViewModel
    {
        #region Constructor
        public MainPageViewModel()
        {
            GetTabs();
        }

        #endregion

        #region Private Properties

        private bool _isParentRecordTabVisible = true;
        private bool _isAdditionalInfoTabVisible;

        private ObservableCollection<TabViewModel> _tabs { get; set; }
        
        #endregion

        #region Public Properties

        public bool IsParentRecordTabVisible
        {
            get => _isParentRecordTabVisible;
            set { _isParentRecordTabVisible = value; OnPropertyChanged(nameof(IsParentRecordTabVisible)); }
        }

        public bool IsAdditionalInfoTabVisible
        {
            get => _isAdditionalInfoTabVisible;
            set { _isAdditionalInfoTabVisible = value; OnPropertyChanged(nameof(IsAdditionalInfoTabVisible)); }
        }

        public ObservableCollection<TabViewModel> Tabs
        {
            get => _tabs;
            set { _tabs = value; OnPropertyChanged(nameof(Tabs)); }
        }

        #endregion

        #region Commands

        public ICommand TabChangedCommand { get { return new Command<TabViewModel>(ChangeTabClick); } }

        #endregion

        #region Private Methods

        private void GetTabs()
        {
            Tabs = new ObservableCollection<TabViewModel>();
            Tabs.Add(new TabViewModel { TabId = 1, IsSelected = true, TabTitle = "Parent record" });
            Tabs.Add(new TabViewModel { TabId = 2, TabTitle = "Additional Info" });
            Tabs.Add(new TabViewModel { TabId = 3, TabTitle = "Contacts" });
            Tabs.Add(new TabViewModel { TabId = 4, TabTitle = "Previous inspections" });
            Tabs.Add(new TabViewModel { TabId = 5, TabTitle = "Attachments" });

            SelectedTab = Tabs.FirstOrDefault();
        }

        private void ChangeTabClick(TabViewModel tab)
        {
            try
            {
                var tabs = new ObservableCollection<TabViewModel>(Tabs);

                foreach (var item in tabs)
                {
                    if (item.TabId == tab.TabId)
                    {
                        item.IsSelected = true;
                    }
                    else
                    {
                        item.IsSelected = false;
                    }
                }

                Tabs.Clear();
                Tabs = new ObservableCollection<TabViewModel>(tabs);

                switch (tab.TabId)
                {
                    case 1:
                        IsParentRecordTabVisible = true;
                        IsAdditionalInfoTabVisible = false;
                        break;
                    case 2:
                        IsParentRecordTabVisible = false;
                        IsAdditionalInfoTabVisible = true;
                        break;
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.Message);
            }
        }

        #endregion
    }
}

#ParentTabView.xaml #ParentTabView.xaml

<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="poc_maui.Views.SubViews.ParentTabView">
    <StackLayout HorizontalOptions="FillAndExpand" VerticalOptions="CenterAndExpand" >
        <Label 
            Text="Welcome to Parent tab!"
            VerticalOptions="Center" 
            HorizontalOptions="Center" />
    </StackLayout>
</ContentView>


#AdditionalInfoTabView.xaml #AdditionalInfoTabView.xaml

<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="poc_maui.Views.SubViews.AdditionalInfoTabView">
    <StackLayout HorizontalOptions="FillAndExpand" VerticalOptions="CenterAndExpand" >
        <Label 
            Text="Welcome to Additiona info tab!"
            VerticalOptions="Center" 
            HorizontalOptions="Center" />
    </StackLayout>
</ContentView>

So what happens here in Android is when I'm clicking AdditionalInfo Tab then it will show a blank white screen and if you press the hardware back button and open the app again it will show AdditionalTab as selected and its views content as well.因此,在 Android 中发生的情况是,当我单击 AdditionalInfo 选项卡时,它将显示一个空白的白色屏幕,如果您按下硬件后退按钮并再次打开应用程序,它将显示 AdditionalTab 已选中,并且它的视图内容也将显示。

If I remove switch() code part from the ViewModel then it will work fine but tabs will not change.如果我从 ViewModel 中删除 switch() 代码部分,那么它会正常工作,但选项卡不会改变。 Does anyone have idea about this kind of behavior of scroll view in MAUI?有人知道 MAUI 中滚动视图的这种行为吗?

The full source code is here: maui_sample完整的源代码在这里: maui_sample

Does this work-around fix it?这个变通办法能解决吗?

MainPage.xaml: MainPage.xaml:

<ScrollView x:Name "theScrollView" ... >

MainPage.xaml.cs: MainPage.xaml.cs:

public MainPage()
{
    InitializeComponent();

    MessagingCenter.Subscribe<MainPageViewModel>(this, "update", (sender) =>
    {
        // Tell theScrollView to re-layout its contents.
        (theScrollView as IView).InvalidateArrange();
    });
}    

MainPageViewModel:主页面视图模型:

private void ChangeTabClick(TabViewModel tab)
{
    ... make changes ...

    MessagingCenter.Send<MainPageViewModel>(this, "update");
}

MAYBE:也许:

I'm not sure if MessagingCenter Subscribe is on Dispatcher (Main) thread.我不确定 MessagingCenter 订阅是否在调度程序(主)线程上。 To be reliable, do:为了可靠,请执行以下操作:

    MessagingCenter.Subscribe<MainPageViewModel>(this, "update", (sender) =>
    {
        Dispatcher.Dispatch( () =>
        {
            (theScrollView as IView).InvalidateArrange();
        });
    }

UPDATE更新

There are other Maui bugs, that have a common "theme": Maui on Android does "something" related to layout only once - at the time the page is first drawn.还有其他 Maui 错误,它们有一个共同的“主题”:Android 上的 Maui 只做了一次与布局相关的“事情”——在第一次绘制页面时。 UNFORTUNATELY, anything that is "not visible" at that time, is skipped.不幸的是,当时“不可见”的任何内容都会被跳过。 And won't work when later made visible.并且在以后变得可见时将不起作用。

Until such bugs are fixed, you'll have to do some work-around.在修复此类错误之前,您必须做一些工作。

WORK-AROUND #1:解决方法 #1:

  • Start with ALL tabs IsVisible="True" .从所有标签IsVisible="True"开始。
  • As soon as the page has been drawn the first time, in code-behind, create the desired Bindings on those IsVisible properties.第一次绘制页面后,在代码隐藏中,在这些 IsVisible 属性上创建所需的绑定。 Page drawn first time can be intercepted in a custom handler.第一次绘制的页面可以在自定义处理程序中被拦截。 But this is a temp work-around, so its easier to just run a method after a 250 ms delay.但这是一种临时解决方法,因此在 250 毫秒延迟后运行一个方法更容易。 Use a boolean "flag" to make the method only run the first time.使用布尔“标志”使该方法仅在第一次运行。
  • Might have to do InvalidateArrange as shown above, to force the Bindings to function the first time.可能必须如上所示执行InvalidateArrange ,以强制 Bindings 第一次运行。

OR WORK-AROUND #2:或解决方法 #2:

  • Each time tab changes, use shell route to go to MainPage again.每次选项卡更改时,使用 shell 路由再次转到 MainPage。 Keep same view model, so knows which tab to show first (and remembers any other state you care about).保持相同的视图模型,因此知道首先显示哪个选项卡(并记住您关心的任何其他状态)。

Both of these are ugly.这两个都很丑。


I recommend creating an issue at .Net Maui github, and providing link to your github sample.我建议在 .Net Maui github 上创建一个问题,并提供指向您的 github 示例的链接。

This is still not works for me properly but after looking at below two links I found that it it not what we are looking for.这仍然不适用于我,但在查看下面的两个链接后,我发现它不是我们正在寻找的。 The Isvisible : false first and then on switch or check box change you are trying to make it visible then it will not visible but the actual control visible. Isvisible : false 首先在开关或复选框更改上尝试使其可见,然后它将不可见,但实际控件可见。 So on look after I have see this link but again the answer is not what I was looking for.所以在我看到这个链接之后,但答案又不是我想要的。

Step to resolve.一步解决。

  1. On View use the Parent as ScrollView or control belongs to IView,IElement.在 View 上使用 Parent 作为 ScrollView 或控件属于 IView、IElement。

<ScrollView x:Name "myScrollView"> ..... <ScrollView x:Name "myScrollView"> .....

... ...

  1. Add Action on ViewModel在 ViewModel 上添加操作

public delegate void Action(T obj);公共委托无效动作(T obj);

  1. Invoke the Action Note: Make sure you call this on require not all the time.调用动作 注意:确保你调用它不需要一直。 eg On Visibility set in ViewModel call after visibility update.例如,可见性更新后在 ViewModel 调用中设置的可见性。

MeasureAction?.Invoke("reSetVisibility"); MeasureAction?.Invoke("reSetVisibility");

  1. Now on View's Code File, use Viewmodel and accept the invoke现在在 View 的代码文件中,使用 Viewmodel 并接受调用

Here Call the below line will works perfectly.这里调用下面的行将完美地工作。

(myScrollView as IView).InvalidateMeasure(); (myScrollView 作为 IView).InvalidateMeasure();

That's IT... Enjoy IsVisible now and make your layout as require.就是这样......现在享受 IsVisible 并根据需要进行布局。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM