0

我尝试在 UI 中显示一堆带有重叠按钮的图像。这些按钮应该是透明的,并且在最顶部或底部有一个图标。虽然“按钮”正在工作,但图像堆栈却没有(或者更准确地说是父网格。但是,我还不是 100% 确定)。我希望得到与此类似的结果:https://i.imgur.com/2F7n963.png(橙色 = Up.png / Down.png;透明绿色 = 上 CommandGrid / Button;透明蓝色 = 下 CommandGrid / Button;黑色和红色图像堆栈,而红色是选定的“级别”)。

到目前为止,这是我的代码:

<Grid HorizontalOptions="End" VerticalOptions="StartAndExpand" Margin="10">
    <Grid.RowDefinitions>
        <RowDefinition Height="*" />
        <RowDefinition Height="*" />
    </Grid.RowDefinitions>

    <controls:ItemsStack Grid.Row="0" Grid.RowSpan="2" ItemsSource="{Binding Levels}" Margin="0,15" Spacing="-8">
        <controls:ItemsStack.ItemTemplate>
            <DataTemplate>
                <controls:TintedImage Source="{Binding Source}" Foreground="{Binding Selected, Converter={StaticResource MapLevelColorConverter}}" />
            </DataTemplate>
        </controls:ItemsStack.ItemTemplate>
    </controls:ItemsStack>

    <controls:CommandGrid Grid.Row="0" Command="{Binding NavigateLevelCommand}" CommandParameter="{Binding LevelUpKey}" MinimumHeightRequest="32" Margin="0" Padding="0">
        <controls:TintedImage Source="Up.png" Foreground="{DynamicResource AccentColor}" VerticalOptions="Start" HorizontalOptions="Center" />
    </controls:CommandGrid>

    <controls:CommandGrid Grid.Row="1" Command="{Binding NavigateLevelCommand}" CommandParameter="{Binding LevelDownKey}" MinimumHeightRequest="32" Margin="0" Padding="0">
        <controls:TintedImage Source="Down.png" Foreground="{DynamicResource AccentColor}" VerticalOptions="End" HorizontalOptions="Center" />
    </controls:CommandGrid>
</Grid>

这是 ItemsStack(只是一个可绑定的 StackLayout):

public class ItemsStack : StackLayout
{
    public static readonly BindableProperty ItemsSourceProperty =
        BindableProperty.Create(nameof(ItemsSource), typeof(IEnumerable), typeof(ItemsStack),
            default(IEnumerable<object>), BindingMode.TwoWay, null, ItemsSourceChanged);

    public static readonly BindableProperty ItemTemplateProperty =
        BindableProperty.Create(nameof(ItemTemplate), typeof(DataTemplate), typeof(ItemsStack),
            default(DataTemplate), BindingMode.TwoWay);

    public event EventHandler<SelectedItemChangedEventArgs> SelectedItemChanged;

    public IEnumerable ItemsSource
    {
        get => (IEnumerable)GetValue(ItemsSourceProperty);
        set => SetValue(ItemsSourceProperty, value);
    }

    public DataTemplate ItemTemplate
    {
        get => (DataTemplate)GetValue(ItemTemplateProperty);
        set => SetValue(ItemTemplateProperty, value);
    }

    private static void ItemsSourceChanged(BindableObject bindable, object oldValue, object newValue)
    {
        var itemsLayout = (ItemsStack)bindable;
        itemsLayout.SetItems();
    }

    protected readonly ICommand ItemSelectedCommand;

    protected virtual void SetItems()
    {
        Children.Clear();

        if (ItemsSource == null)
        {
            ObservableSource = null;
            return;
        }

        var t = ItemsSource.GetType();
        if (t.IsConstructedGenericType && t.GetGenericTypeDefinition() == typeof(ObservableCollection<>))
        {
            var o = Activator.CreateInstance(typeof(ObservableReadOnlyCollection<>).MakeGenericType(t.GenericTypeArguments), ItemsSource);
            ObservableSource = (IObservableReadOnlyCollection<object>)o;
        }
        else
        {
            foreach (var item in ItemsSource)
                Children.Add(GetItemView(item));
        }
    }

    protected virtual View GetItemView(object item)
    {
        DataTemplate template;
        if (ItemTemplate is DataTemplateSelector selector)
            template = selector.SelectTemplate(item, this);
        else
            template = ItemTemplate;

        var content = template.CreateContent();

        var view = content as View;
        if (view == null)
            return null;

        view.BindingContext = item;

        var gesture = new TapGestureRecognizer
            {
                Command = ItemSelectedCommand,
                CommandParameter = item
            };

        return view;
    }

    IObservableReadOnlyCollection<object> _observableSource;
    protected IObservableReadOnlyCollection<object> ObservableSource
    {
        get => _observableSource;
        set
        {
            if (_observableSource != null)
            {
                _observableSource.CollectionChanged -= CollectionChanged;
            }
            _observableSource = value;

            if (_observableSource != null)
            {
                _observableSource.CollectionChanged += CollectionChanged;
            }
        }
    }

    private void CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        switch (e.Action)
        {
            case NotifyCollectionChangedAction.Add:
                {
                    var index = e.NewStartingIndex;
                    foreach (var item in e.NewItems)
                        Children.Insert(index++, GetItemView(item));
                }
                break;
            case NotifyCollectionChangedAction.Move:
                {
                    var item = ObservableSource[e.OldStartingIndex];
                    Children.RemoveAt(e.OldStartingIndex);
                    Children.Insert(e.NewStartingIndex, GetItemView(item));
                }
                break;
            case NotifyCollectionChangedAction.Remove:
                {
                    Children.RemoveAt(e.OldStartingIndex);
                }
                break;
            case NotifyCollectionChangedAction.Replace:
                {
                    Children.RemoveAt(e.OldStartingIndex);
                    Children.Insert(e.NewStartingIndex, GetItemView(ObservableSource[e.NewStartingIndex]));
                }
                break;
            case NotifyCollectionChangedAction.Reset:
                Children.Clear();
                foreach (var item in ItemsSource)
                    Children.Add(GetItemView(item));
                break;
        }
    }
}

这是 CommandGrid(基本上是一个具有自定义内容和透明度的“按钮”):

public class CommandGrid : Grid
{
    public static readonly BindableProperty CommandProperty =
        BindableProperty.Create(nameof(Command), typeof(ICommand), typeof(CommandGrid),
            default(ICommand), propertyChanged: OnCommandPropertyChanged);

    public static readonly BindableProperty CommandParameterProperty =
        BindableProperty.Create(nameof(CommandParameter), typeof(object), typeof(CommandGrid),
            default(object), propertyChanged: OnCommandPropertyChanged);

    public ICommand Command
    {
        get => (ICommand)GetValue(CommandProperty);
        set => SetValue(CommandProperty, value);
    }

    public object CommandParameter
    {
        get => GetValue(CommandParameterProperty);
        set => SetValue(CommandParameterProperty, value);
    }

    private static void OnCommandPropertyChanged(BindableObject bindable, object oldvalue, object newvalue)
    {
        if (bindable is CommandGrid grid)
        {
            grid.GestureRecognizers.Clear();
            grid.GestureRecognizers.Add(new TapGestureRecognizer { Command = grid.Command, CommandParameter = grid.CommandParameter });
        }
    }
}

我现在整天都在尝试解决这个问题,并且不再知道如何解决它......

编辑:这是它目前的样子(远离它应该的样子):https ://i.imgur.com/KVmcjwe.png

编辑 2:至于现在,首先要找到一个可以使用的解决方案,我使用后面的代码来解决这个问题。但我显然应该对此做更多的研究,也许在休息后看看它(或者如果这里有人知道它是如何工作的或者这里有什么问题)。这就是我现在“解决”它的方式:

private async void SetLevelSize()
{
    await Task.Delay(200);

    var requiredHeight = 64d;
    if (ViewModel.ShowMapLevelIcon)
    {
        LevelStack.Margin = ViewModel.ShowMapLevelButtons ? new Thickness(0, 15) : new Thickness(0);
        requiredHeight = LevelStack.Children.Sum(c => c.Height + c.Margin.Top + c.Margin.Bottom) + LevelStack.Spacing * (LevelStack.Children.Count - 1) + LevelStack.Margin.Top + LevelStack.Margin.Bottom;
        requiredHeight = Math.Max(requiredHeight, 64);
    }

    LevelGrid.HeightRequest = requiredHeight;
}
4

0 回答 0