在Xamarin.Forms中拖动不规则形状

时间:2017-01-24 20:21:22

标签: xamarin.forms mr.gestures

我有一个Xamarin.Forms应用程序,我需要拖动不规则形状的控件(TwinTechForms SvgImageView),就像这样:

enter image description here

我希望它只响应黑色区域的触摸而不是透明(方格)区域

我尝试使用MR.Gestures包。连接到平移事件让我拖动图像,但是当我触摸它的透明部分时它也开始拖动。

我的设置如下:

<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叉我添加一个XDocument +过滤器构造函数到SvgReader所以同样的XDocument可进入不同SvgImageView实例与不同的解析滤波器,从而有效地分离了原始SVG成多个SvgImageView对象,可以独立地,没有太多地移动大部分记忆命中。我必须为我的SVG修复一些画笔继承,以便按预期显示。

TTFL fork暴露了XDocument + filter ctor,并为渲染器添加了特定于平台的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">并将Panning和Panned事件连接到它。

SvgImageView dragSvgView = null; Point originalPosition = Point.Zero; movableView.Panning + =(sender,pcp)=&gt;     {       //如果我们没有拖动任何东西 - 请检查以前加载的SVG图像       //如果他们在触摸点有一个不透明的像素       if(dragSvgView == null){         dragSvgView = svgImages.FirstOrDefault(si =&gt; {           var c = si.GetPixelColor(pcp.Touches [0] .X - si.TranslationX,pcp.Touches [0] .Y - si.TranslationY);           返回c.A&gt; 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 个答案:

答案 0 :(得分:2)

MR.Gestures和任何基础平台都不会检查视图中的触摸区域是否透明。听取触摸手势的元素总是矩形的。所以你必须亲自进行命中测试。

PanningEventParameters包含Point[] Touches,其坐标为所有触摸手指。使用这些坐标,您可以检查它们是否与SVG中的任何可见区域匹配。

对样品中的甜甜圈进行测试很容易,但测试一般形状不是(我认为这就是你想要的)。如果你很幸运,那么SvgImage已经支持它了。如果没有,那么您可以在SVG Rendering EnginePoint-In-Polygon Algorithm — Determining Whether A Point Is Inside A Complex Polygon2D collision detection中找到如何做到这一点的原则。

不幸的是重叠元素有点问题。当我最初编写MR.Gestures时,我尝试使用Handled标志来实现它,但我无法在所有平台上使用它。我认为保持一致比让它只在一个平台上工作更重要,我忽略所有平台上的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会为每个重叠的矩形元素调用,而这不是你想要的。