Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I stop the clicking on a ViewCell from changing the background color for a brief time?

I have this XAML code:

<TableView x:Name="tableView" Intent="Settings" HasUnevenRows="True">
   <TableSection>
      <TableSection.Title>
         Card Selection
      </TableSection.Title>
      <ViewCell Height="50">
         <Grid>
            <Grid x:Name="deselectGridLink" VerticalOptions="CenterAndExpand" Padding="20, 0">
               <Label TextColor="Blue" Style="{DynamicResource ListItemTextStyle}" x:Name="deselectLink" HorizontalOptions="StartAndExpand" VerticalOptions="Center" Text="Deselect All" />
            </Grid>
            <Grid x:Name="deselectGridLabel" VerticalOptions="CenterAndExpand" Padding="20, 0">
               <Label TextColor="Silver" Style="{DynamicResource ListItemTextStyle}" x:Name="deselectLabel" HorizontalOptions="StartAndExpand" VerticalOptions="Center" Text="Deselect All" />
            </Grid>
         </Grid>
       </ViewCell>
       <ViewCell Height="50">
          <Grid x:Name="selectGridLink" VerticalOptions="CenterAndExpand" Padding="20, 0">
             <Label TextColor="Blue" Style="{DynamicResource ListItemTextStyle}" x:Name="selectLink" HorizontalOptions="StartAndExpand" VerticalOptions="Center" Text="Select All" />
           </Grid>
        </ViewCell>
   </TableSection>
</TableView>

When other parts of my code call: SetPageDetails() then the label in the grid is changed to a link or the link is changed to a label. So for this when it is a label I would like to have no background flash event and no action called.

I attach a tap gesture recognizer like this. Note it's all on one line but covers two lines so it's more visible here in the SO question:

deselectGridLink.GestureRecognizers
      .Add(NewTapGestureForUpdateCategories(false));

    private TapGestureRecognizer NewTapGestureForUpdateCategories(bool val)
    {
        return new TapGestureRecognizer()
        {
            Command = new Command(() =>
            {
                App.DB.UpdateAllCategoryGroups(val);
                App.DB.UpdateAllCategories(val);
                GetPageData();
                RemoveTableViewClickSection();
                tableView.Root.Add(CreateTableSection());
            })
        };
    }

When the user clicks the row when deselectGridLink grid is visible then:

  • The deselectGridLink visibility is set to false
  • The deselectGridLabel visibility is set to true

    private void SetPageDetails()
    {
        Title = App.cardCountForSelectedCategories + (App.cardCountForSelectedCategories == 1 ? " Card Selected" : " Cards Selected");
        if (App.cardCountForSelectedCategories == 0)
        {
            deselectGridLink.IsVisible = false;
            deselectGridLabel.IsVisible = true;
        }
        else
        {
            deselectGridLink.IsVisible = true;
            deselectGridLabel.IsVisible = false;
        }
    }
    

The effect of this is that the grid link text will change to silver and the link becomes a label.

However even though it's a gray color label when the label is clicked there is still a brief background row color change from white to a dark color when the label is clicked. I assume it's just the way a view cell works.

Is there a way to suppress this from happening?

like image 429
Alan2 Avatar asked Sep 16 '17 07:09

Alan2


2 Answers

EDIT 1 - Updated answer as per updates to question. i.e. add support for switching between highlight enabled/disabled mode.

EDIT 2 - Restructure answer and add more details.

Option-1: Enable/disable view-cell through IsEnabled

The simplest option would be to use the IsEnabled property, which in turn enables/disables the background flash behavior. The only downside to this approach is that it will also disable the taps on child controls, i.e. tap events/gesture recognizer(s) will not be triggered if parent view-cell's IsEnabled is false.

For example:

XAML

<!-- Add name attribute to view-cell -->
<ViewCell x:Name="deselectCell" ..>
    <Grid>
        <Grid x:Name="deselectGridLink" ..
    ....
</ViewCell>

Code-behind

private void SetPageDetails()
{
    if (App.cardCountForSelectedCategories == 0)
    {
        deselectCell.IsEnabled = false; //disable background flash
        ...
    }
    else
    {
        deselectCell.IsEnabled = true;
        ...
    }
}

Recommendation 1 - Use data-binding and triggers

Instead of controlling visibility for each label in code-behind, you can use triggers and data-binding as follows (view-model will have a IsDeselectEnabled property):

<ViewCell IsEnabled="{Binding IsDeselectEnabled}" Height="50">
    <Label Margin="20,0,20,0" Style="{DynamicResource ListItemTextStyle}" HorizontalOptions="StartAndExpand" VerticalOptions="Center" Text="Deselect All">
        <Label.Triggers>
            <DataTrigger TargetType="Label" Binding="{Binding IsDeselectEnabled}" Value="true">
                <Setter Property="TextColor" Value="Blue" />
            </DataTrigger>
            <DataTrigger TargetType="Label" Binding="{Binding IsDeselectEnabled}" Value="false">
                <Setter Property="TextColor" Value="Silver" />
            </DataTrigger>
        </Label.Triggers>
    </Label>
</ViewCell>

Recommendation 2 - Use triggers with view as source

<ViewCell x:Name="deselectCell" Height="50">
    <Label Margin="20,0,20,0" Style="{DynamicResource ListItemTextStyle}" HorizontalOptions="StartAndExpand" VerticalOptions="Center" Text="Deselect All">
        <Label.Triggers>
            <DataTrigger TargetType="Label" Binding="{Binding IsEnabled, Source={x:Reference deselectCell}}" Value="true">
                <Setter Property="TextColor" Value="Blue" />
            </DataTrigger>
            <DataTrigger TargetType="Label" Binding="{Binding IsEnabled, Source={x:Reference deselectCell}}" Value="false">
                <Setter Property="TextColor" Value="Silver" />
            </DataTrigger>
        </Label.Triggers>
    </Label>
</ViewCell>

Option-2: Enable/disable highlight, but allow taps

To allow taps while toggling ViewCell's background-highlight behavior, we will need to implement platform-renderer(s).

In case of iOS, we can use SelectionStyle to toggle this behavior, while in case of android, we can use Clickable property.

Shared control:

public class CustomViewCell : ViewCell
{
    public static readonly BindableProperty AllowHighlightProperty =
        BindableProperty.Create(
            "AllowHighlight", typeof(bool), typeof(CustomViewCell),
            defaultValue: true);

    public bool AllowHighlight
    {
        get { return (bool)GetValue(AllowHighlightProperty); }
        set { SetValue(AllowHighlightProperty, value); }
    }
}

iOS renderer:

[assembly: ExportRenderer(typeof(CustomViewCell), typeof(CustomViewCellRenderer))]
namespace SampleApp.iOS
{
    public class CustomViewCellRenderer : ViewCellRenderer
    {
        UITableViewCell _nativeCell;

        //get access to the associated forms-element and subscribe to property-changed
        public override UITableViewCell GetCell(Cell item, UITableViewCell reusableCell, UITableView tv)
        {
            _nativeCell = base.GetCell(item, reusableCell, tv);

            var formsCell = item as CustomViewCell;

            if (formsCell != null)
            {
                formsCell.PropertyChanged -= OnPropertyChanged;
                formsCell.PropertyChanged += OnPropertyChanged;
            }

            //and, update the style 
            SetStyle(formsCell);

            return _nativeCell;
        }

        void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            var formsCell = sender as CustomViewCell;
            if (formsCell == null)
                return;
            //TODO: Trying to find a nicer and more robust way to dispose and unsubscribe :(
            if (_nativeCell == null)
                formsCell.PropertyChanged -= OnPropertyChanged;

            if (e.PropertyName == CustomViewCell.AllowHighlightProperty.PropertyName)
            {
                SetStyle(formsCell);
            }
        }

        private void SetStyle(CustomViewCell formsCell)
        {
            //added this code as sometimes on tap, the separator disappears, if style is updated before tap animation finishes 
            //https://stackoverflow.com/questions/25613117/how-do-you-prevent-uitableviewcellselectionstylenone-from-removing-cell-separato
            Device.StartTimer(TimeSpan.FromMilliseconds(50), () => {
                Device.BeginInvokeOnMainThread(() =>
                {
                    if (formsCell.AllowHighlight)
                        _nativeCell.SelectionStyle = UITableViewCellSelectionStyle.Default;
                    else
                        _nativeCell.SelectionStyle = UITableViewCellSelectionStyle.None;
                });
                return false;
            });
        }
    }
}

Android renderer:

[assembly: ExportRenderer(typeof(CustomViewCell), typeof(CustomViewCellRenderer))]
namespace SampleApp.Droid
{
    public class CustomViewCellRenderer : ViewCellRenderer
    {
        Android.Views.View _nativeCell;

        protected override Android.Views.View GetCellCore(Cell item, Android.Views.View convertView, Android.Views.ViewGroup parent, Android.Content.Context context)
        {
            _nativeCell = base.GetCellCore(item, convertView, parent, context);

            SetStyle();

            return _nativeCell;
        }

        // this one is simpler as the base class has a nice override-able method for our purpose - so we don't need to subscribe 
        protected override void OnCellPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            base.OnCellPropertyChanged(sender, e);

            if(e.PropertyName == CustomViewCell.AllowHighlightProperty.PropertyName)
            {
                SetStyle();
            }
        }

        private void SetStyle()
        {
            var formsCell = Cell as CustomViewCell;
            if (formsCell == null)
                return;

            _nativeCell.Clickable = !formsCell.AllowHighlight;
        }
    }
}

Sample usage 1 - Through data-binding

<local:CustomViewCell  AllowHighlight="{Binding IsHighlightEnabled}" ..>
    <Grid>
        <Grid x:Name="deselectGridLink" ..
    ...
</local:CustomViewCell>

Sample usage 2 - Through code-behind

XAML

<!-- Add name attribute to view-cell -->
<local:CustomViewCell x:Name="deselectCell" ..>
    <Grid>
        <Grid x:Name="deselectGridLink" ..
    ...
</local:CustomViewCell>

Code-behind

private void SetPageDetails()
{
    if (App.cardCountForSelectedCategories == 0)
    {
        deselectCell.AllowHighlight= false; //disable background flash
        ...
    }
    else
    {
        deselectCell.AllowHighlight= true;
        ...
    }
}

Option-3: Disable highlight, selection for all items

This particularly applies to ListView. The updated question now specifies that the cells are part of TableView, so this option is no longer valid in current question context.

You will need to implement platform renderers to disable highlight colors, and add ItemTapped handler to ListView to disable selection by setting SelectedItem as null always. References used:

  1. Disable highlight item
  2. Disable selection

Code

To get started, create a custom view-cell:

public class NoSelectViewCell : ViewCell { }

Implement iOS renderer as:

[assembly: ExportRenderer(typeof(NoSelectViewCell), typeof(NoSelectViewCellRenderer))]
namespace SampleApp.iOS
{
    public class NoSelectViewCellRenderer : ViewCellRenderer
    {
        public override UITableViewCell GetCell(Cell item, UITableViewCell reusableCell, UITableView tv)
        {
            var nativeCell = base.GetCell(item, reusableCell, tv);
            nativeCell.SelectionStyle = UITableViewCellSelectionStyle.None;
            return nativeCell;
        }
    }
}

Implement android renderer as:

[assembly: ExportRenderer(typeof(NoSelectViewCell), typeof(NoSelectViewCellRenderer))]
namespace SampleApp.Droid
{
    public class NoSelectViewCellRenderer : ViewCellRenderer
    {
        protected override Android.Views.View GetCellCore(Cell item, Android.Views.View convertView, Android.Views.ViewGroup parent, Android.Content.Context context)
        {
            var cell = base.GetCellCore(item, convertView, parent, context);

            cell.Focusable = false;
            cell.FocusableInTouchMode = false;

            var listView = parent as Android.Widget.ListView;
            if (listView != null)
            {
                listView.SetSelector(Android.Resource.Color.Transparent);
                listView.CacheColorHint = Xamarin.Forms.Color.Transparent.ToAndroid();
            }
            return cell;
        }
    }
}

Sample Usage:

XAML

<ListView ItemTapped="Handle_ItemTapped">
    <ListView.ItemTemplate>
        <DataTemplate>
            <local:NoSelectViewCell Height="50">
               <Grid>
                  <Grid x:Name="deselectGridLink" VerticalOptions="CenterAndExpand" Padding="20, 0">
                     <Label TextColor="Blue" Style="{DynamicResource ListItemTextStyle}" x:Name="deselectLink" HorizontalOptions="StartAndExpand" VerticalOptions="Center" Text="Deselect All" />
                  </Grid>
                  <Grid x:Name="deselectGridLabel" VerticalOptions="CenterAndExpand" Padding="20, 0">
                     <Label TextColor="Silver" Style="{DynamicResource ListItemTextStyle}" x:Name="deselectLabel" HorizontalOptions="StartAndExpand" VerticalOptions="Center" Text="Deselect All" />
                  </Grid>
               </Grid>
            </local:NoSelectViewCell>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

Code-behind

void Handle_ItemTapped(object sender, Xamarin.Forms.ItemTappedEventArgs e)
{
    // don't do anything if we just de-selected the row
    if (e.Item == null) return;
    // do something with e.SelectedItem
    ((ListView)sender).SelectedItem = null; // de-select the row
}
like image 178
Sharada Gururaj Avatar answered Nov 09 '22 07:11

Sharada Gururaj


What G.Sharada proposes is very nicely working for iOS, but on Android I still had blinks on click. Adding this line to the styles file solved the problem.

<item name="android:colorActivatedHighlight">@android:color/transparent</item>
like image 25
Elisabeth Avatar answered Nov 09 '22 08:11

Elisabeth