Search code examples
xamlxamarinmvvmxamarin.formsfreshmvvm

Xamarin MVVM Opacity Converter?


So I got five images, when you click one of them I want that one to get full opacity while the other only gets half, to show it is the selected one.

I managed to do this using this method, however this won't work as I'm not allowed to reference the view in MVVM.

I figured I would have to use converters for the opacity, and send the image as a command parameter? I've used converters Before but never made my own so I'm unsure what to do, first time trying to use Mvvm.

   public void OnStatusTapped(object sender, EventArgs args)
        {
            statusUnResolved.Opacity = 0.5;
            statusInProgress.Opacity = 0.5;
            statusDone.Opacity = 0.5;

            var image = (Image)sender;
            image.Opacity = 1;

            String[] buttons = new String[StatusValues.Count];
            for (int n = 0; n < StatusValues.Count; ++n)
            {
                buttons[n] = StatusValues[n].Name;
            }
            if (image.Source is FileImageSource)
            {
                FileImageSource fileImageSource = (FileImageSource)image.Source;
                string fileName = fileImageSource.File;
                foreach (var item in StatusValues)
                {
                    if (item.Name == fileName)
                    {
                        Issue.StatusEx = item.Value;
                        StatusChecker();
                        return;
                    }
                }
            }
        }



 private readonly ICommand onStatusTappedCommand = null;
public ICommand OnStatusTappedCommand
{
    get { return onStatusTappedCommand ?? new Command(OnStatusTapped); }
}


            <StackLayout Grid.Row="3" Grid.Column="1" Orientation="Horizontal" Spacing="0"  >
                <Image x:Name="statusUnResolved" Source="statusUnresolved.png" HorizontalOptions="Center" VerticalOptions="Center" HeightRequest="40" Opacity="0.6">
                    <Image.GestureRecognizers>
                        <TapGestureRecognizer Tapped="OnStatusTapped" NumberOfTapsRequired="1"/>
                    </Image.GestureRecognizers>
                </Image>
            </StackLayout>
            <StackLayout Grid.Row="3" Grid.Column="2" Orientation="Horizontal" Spacing="4">
                <Image x:Name="statusInProgress" Source="statusInProgress.png" HorizontalOptions="Center" VerticalOptions="Center" HeightRequest="40" Opacity="0.6" >
                    <Image.GestureRecognizers>
                        <TapGestureRecognizer Tapped="OnStatusTapped" NumberOfTapsRequired="1"/>
                    </Image.GestureRecognizers>
                </Image>
            </StackLayout>
            <StackLayout Grid.Row="3" Grid.Column="3" Orientation="Horizontal" Spacing="4" >
                <Image x:Name="statusDone" Source="statusDone.png" HorizontalOptions="Center" VerticalOptions="Center" HeightRequest="40" Opacity="1">
                    <Image.GestureRecognizers>
                        <TapGestureRecognizer Tapped="OnStatusTapped" NumberOfTapsRequired="1"/>
                    </Image.GestureRecognizers>
                </Image>
            </StackLayout>
        </Grid>

Solution

  • Assuming you will always have these 5 specific images on the screen, you can create five double properties in your ViewModel (one for each image), ie:

    public double StatusUnresolvedOpacity
    {
        get => _statusUnresolvedOpacity;
        set
        {
            if (_statusUnresolvedOpacity != value)
            {
                _statusUnresolvedOpacity = value;
                OnPropertyChanged(nameof(StatusUnresolvedOpacity));
            }
        }
    }
    

    And also have another method that can reset the opacities of each to 0.5, ie

    public void ResetOpacities()
    {
        StatusUnresolvedOpacity = 0.5;
        StatusInProgressOpacity = 0.5;
        ...
    }
    

    And then give each image a tap gesture recognizer, that will call ResetOpacities(), and then directly set the View Model property of the button that was clicked to 1.0. ie:

    private void OnStatusUnresolvedTapped(object sender, EventArgs e)
    {
        myViewModel.ResetOpacities();
        myViewModel.StatusUnresolvedOpacity = 1.0;
    }
    

    If you really want to use a Value Converter though, I would instead suggest creating five bool properties instead of double properties, ie:

    public bool IsStatusUnresolvedActive { get ... set ... }
    

    And now instead of resetting/setting the opacities, you can simply set the active button's property to true, and the inactive ones' to false. Then in your value converter:

    public class ActiveOpacityConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return (bool)value ? 1.0 : 0.5;
        }
    
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
    

    And to use in your xaml:

    <ContentPage x:converters="clr-namespace:MyProject.WhateverFolderValueConverersAreIn;assembly:MyProject" />
        <ContentPage.Resources>
            <ResourceDictionary>
                <converters:ActiveOpacityConverter x:Key="activeOpacityConverter" />
            </ResourceDictionary>
        </ContentPage.Resources>
        <Image Opacity={Binding IsStatusUnresolvedActive, 
                                Converter={converters:activeOpacityConverter}}
    </ContentPage>