Перетащите неправильную форму в Xamarin.Forms

У меня есть приложение Xamarin.Forms, где мне нужно перетаскивать элементы управления неправильной формы (TwinTechForms SvgImageView), например, так:

Я хочу, чтобы он реагировал только на прикосновения к черной области, а не к прозрачным (клетчатым) областям

Я пытался использовать пакет MR.Gestures. Подключение к событию Panning позволяет перетаскивать изображение, но оно также начинает перетаскиваться, когда я касаюсь его прозрачных частей.

Моя установка выглядит так:

<mr:ContentView x:Name="mrContentView" Panning="PanningEventHandler" Panned="PannedEventHandler" Background="transparent">
  <ttf:SvgImageView x:Name="svgView" Background="transparent" SvgPath=.../>
</mr:ContentView>

и код позади

private void PanningEventHandler(object sender, PanningEventParameters arg){  
     svgView.TranslateX = arg.IsCancelled ? 0: arg.TotalDistance.X;
     svgView.TranslateY = arg.IsCancelled ? 0: arg.TotalDistance.Y; 
}

private void PannedEventHandler(object sender, PanningEventParameters arg){  
  if (!arg.IsCancelled){
     mrContentView.TranslateX = svgView.TranslateX;
     mrContentView.TranslateY = svgView.TranslateY; 
  }
  svgView.TranslateX = 0;
  svgView.TranslateY = 0;
}

В этом фрагменте кода, как я должен проверить, попадаю ли я в прозрачную точку на целевом объекте, и когда это происходит, как мне отменить жест, чтобы другой вид под этим мог реагировать на него? На изображении справа, касающемся красного внутри отверстия зеленого O, должно начаться перетаскивание красного O

Обновление: решено

Предложение принятого ответа сработало, но не было простым.

Мне пришлось раскошелиться и изменить как NGraphics ( github fork), так и TwinTechsFormsLib (TTFL, github fork)

В разветвлении NGraphics я добавил в SvgReader ctor фильтра XDocument+, чтобы один и тот же XDocument можно было передавать в разные экземпляры SvgImageView с помощью другого фильтра разбора, эффективно разделяя исходный SVG на несколько объектов SvgImageView, которые можно перемещать независимо друг от друга без слишком большого количества попадание в память Мне пришлось исправить некоторые наследования кисти, чтобы мои SVG показывали, как и ожидалось.

Разветвление TTFL предоставляет фильтр Ctor XDocument+ и добавляет платформо-зависимый GetPixelColor к средствам визуализации.

Затем на моей странице Xamarin.Forms я могу загрузить исходный файл SVG в несколько экземпляров SvgImageView:

List<SvgImageView> LoadSvgImages(string resourceName, int widthRequest = 500, int heightRequest = 500)
{
    var svgImageViews = new List<SvgImageView>();

    var assembly = this.GetType().GetTypeInfo().Assembly;
    Stream stream = assembly.GetManifestResourceStream(resourceName);
    XDocument xdoc = XDocument.Load(stream);

    // only groups that don't have other groups
    List<XElement> leafGroups = xdoc.Descendants()
        .Where(x => x.Name.LocalName == "g" && x.HasElements && !x.Elements().Any(dx => dx.Name.LocalName == "g"))
        .ToList();

    leafGroups.Insert(0, new XElement("nonGroups")); // this one will 
    foreach (XElement leafGroup in leafGroups)
    { 
        var svgImage = new SvgImageView
        {
            HeightRequest = widthRequest,
            WidthRequest = heightRequest,
            HorizontalOptions = LayoutOptions.Start,
            VerticalOptions = LayoutOptions.End,
            StyleId = leafGroup.Attribute("id")?.Value, // for debugging
        };

        // this loads the original SVG as if only there's only one leaf group
        // and its parent groups (to preserve transformations, brushes, opacity etc)
        svgImage.LoadSvgFromXDocument(xdoc, (xe) =>
        {
            bool doRender = xe == leafGroup ||
                            xe.Ancestors().Contains(leafGroup) ||
                            xe.Descendants().Contains(leafGroup); 
            return doRender;
        });

        svgImageViews.Add(svgImage);
    }

    return svgImageViews;
}

Затем я добавляю все svgImageViews в MR.Gesture. <mr:Grid x:Name="movableHost"> и подключите к нему события панорамирования и панорамирования.

SvgImageView dragSvgView = null; Point originalPosition = Point.Zero; movableView.Panning + = (sender, pcp) => {// если мы ничего не перетаскиваем - проверьте ранее загруженные изображения SVG // если они имеют непрозрачный пиксель в точке касания if (dragSvgView==null){ dragSvgView = svgImages.FirstOrDefault(si => { var c = si.GetPixelColor(pcp.Touches[0].X - si.TranslationX, pcp.Touches[0].Y - si.TranslationY); вернуть cA > 0,0001; });

    if (dragSvgView != null)
    {
      // save the original position of this item so we can put it back in case dragging was canceled
      originalPosition = new Point (dragSvgView.TranslationX, dragSvgView.TranslationY);  
    }
  }
  // if we're dragging something - move it along
  if (dragSvgView != null)
  {
    dragSvgView.TranslationX += pcp.DeltaDistance.X;
    dragSvgView.TranslationY += pcp.DeltaDistance.Y;
  }

}

1 ответ

Решение

Ни MR.Gestures, ни какая-либо нижележащая платформа не проверяют прозрачность области касания в представлении. Элементы, которые слушают жесты прикосновения, всегда прямоугольные. Таким образом, вы должны сделать тестирование удара самостоятельно.

PanningEventParameters содержать Point[] Touches с координатами всех соприкасающихся пальцев. С помощью этих координат вы можете проверить, соответствуют ли они какой-либо видимой области в SVG.

Хит-тестирование пончика из вашего образца легко, но тестирование для общей фигуры - нет (и я думаю, это то, что вы хотите). Если вам повезет, то SvgImage уже поддерживает это. Если нет, то вы можете найти принципы, как это можно сделать, в механизме рендеринга SVG, алгоритм Point-in-Polygon - определение, находится ли точка внутри сложного многоугольника или двумерное обнаружение столкновений.

К сожалению, перекрывающиеся элементы представляют собой небольшую проблему. Я пытался реализовать это с Handled флаг, когда я первоначально написал MR.Gestures, но я не мог заставить его работать на всех платформах. Поскольку я думал, что важнее быть последовательным, чем заставить его работать только на одной платформе, я игнорирую Handled на всех платформах и скорее поднять события для всех перекрывающихся элементов. (Я должен был полностью убрать флаг)

В вашем конкретном случае я бы предложил вам использовать такую ​​структуру для нескольких SVG:

<mr:ContentView x:Name="mrContentView" Panning="PanningEventHandler" Panned="PannedEventHandler" Background="transparent">
  <ttf:SvgImageView x:Name="svgView1" Background="transparent" SvgPath=.../>
  <ttf:SvgImageView x:Name="svgView2" Background="transparent" SvgPath=.../>
  <ttf:SvgImageView x:Name="svgView3" Background="transparent" SvgPath=.../>
</mr:ContentView>

в PanningEventHandler Вы можете проверить, если Touches находятся на любом SVG, и если да, который находится на вершине.

Если бы вы сделали несколько ContentViewЕсли у каждого есть только один SVG, то PanningEventHandler будет вызываться для каждого перекрывающегося прямоугольного элемента, который не является тем, что вы хотите.

Другие вопросы по тегам