Forcing a RelativeLayout in a DataTemplate to update after constraints change

JasonWilliamsJasonWilliams USMember
edited December 2016 in Xamarin.Forms

We are prototyping an idea, so this code is far from production-ready. That said, I'll paste the relevant parts below.
We are using an IValueConverter to convert an integer value into a relative constraint to position a BoxView along the X-axis of an image. When the X value in the ViewModel gets updated, so does our binding (we can tell because our value converter is called) and a new constraint is created. However, the RelativeLayout does not update its layout when a constraint is changed.

Add to this, the relative layout is inside a DataTemplate and you get all kinds of fun. :)

Our question: how do you force a RelativeLayout (that is inside a DataTemplate) to redraw itself so the new constraint is used?

Here's part of our ViewModel:

private int _PosX;
        public int PosX
        {
            get { return _PosX;}
            set
            {
                if (_PosX == value)
                    return;

                _PosX = value;
                OnPropertyChanged(nameof(PosX));
            }
        }

The ContentPage:

<Slider Value="{Binding PosX, Mode=TwoWay}"
            Minimum="0" Maximum="640" x:Name="xAdjustmentSlider" />

    <cv:CarouselView x:Name="ImageCarousel" Grid.Row="2"
                     ItemsSource="{Binding ShotList}">
      <cv:CarouselView.ItemTemplate>
        <DataTemplate>
          <Grid x:Name="TemplateGrid">
            <Grid.RowDefinitions>
              <RowDefinition Height="*" />
              <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>

            <RelativeLayout>
              <Image Grid.Row="0"
                     x:Name="targetImage"
                     Source="{Binding TheImage}"
                     Aspect="AspectFit">
                <Image.GestureRecognizers>
                  <TapGestureRecognizer Command="{Binding Path=BindingContext.PreviewImageCommand, Source={x:Reference ImageViewPage}}"
                                        CommandParameter="{Binding ImageNumber}" />
                </Image.GestureRecognizers>
              </Image>
              <BoxView Color="Red"
                       RelativeLayout.XConstraint="{Binding Source={x:Reference xAdjustmentSlider}, Path=BindingContext.PosX, Converter={StaticResource xConv}, ConverterParameter={x:Reference targetImage}}"
                       WidthRequest="4" HeightRequest="4" />
            </RelativeLayout>
          </Grid>
        </DataTemplate>
      </cv:CarouselView.ItemTemplate>
    </cv:CarouselView>

Here's the int -> relative layout constraint converter:

public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value == null || !(value is System.Int32) || parameter == null || !(parameter is Xamarin.Forms.View))
                return null;

            int val = (int)value;
            View relativeView = (View)parameter;

            var result = Constraint.RelativeToView(relativeView, (Parent, sibling) =>
            {
                return sibling.X + val;
            });

            return result;
        }

Posts

  • No ideas?

  • MarkFeldmanMarkFeldman USMember ✭✭

    I've seen other people complain about this, if you look at the RelativeLayout source code it would appear that the constraints have no internal propertychanged callback, so changing them obviously won't force a layout or anything. Also they only seem to be used when a child is added, and once in place you're not even allowed to remove them from the RelativeLayout's child list which rules out using a a behavior with an attached property to re-add them in response to your XPos changing.

    Personally I'd probably go about this a different way e.g. with an AbsoluteLayout, margins or by binding to TranslationX instead of RelativeLayout.XConstraint.

Sign In or Register to comment.