I try to migrate the DLToolkit.Forms.Controls.FlowListView Library to .net 7 Maui(TestProject, to keep using it in our app. Everything works fine but an AbsoluteLayout in Datatemplate dont show list content. I had this xaml:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="FlowlistTest.Maui.MainPage"
xmlns:local="clr-namespace:FlowlistTest.Maui"
xmlns:flv="clrnamespace:DLToolkit.Maui.Controls.FlowListView;assembly=DLToolkit.Maui.Control s.FlowListView"
>
<StackLayout Padding="10">
<flv:FlowListView FlowColumnCount="3"
SeparatorVisibility="Default"
HasUnevenRows="True"
Background="Black"
FlowUseAbsoluteLayoutInternally="True"
FlowItemTappedCommand="{Binding ItemTappedCommand}"
FlowItemsSource="{Binding Items}">
<flv:FlowListView.FlowColumnTemplate>
<DataTemplate>
<Frame BackgroundColor="Purple"
Margin="5">
<Label HorizontalOptions="Fill"
VerticalOptions="Fill"
TextColor="White"
HorizontalTextAlignment="Center"
VerticalTextAlignment="Center"
Text="{Binding }"/>
</Frame>
</DataTemplate>
</flv:FlowListView.FlowColumnTemplate>
</flv:FlowListView>
</StackLayout>
</ContentPage>
And this c# code:
void AddViewToLayoutAutoHeightDisabled(View view, int containerCount, int
colNumber)
{
double desiredColumnWidth = 1d / _desiredColumnCount;
Rect bounds = Rect.Zero;
if (_flowColumnExpand != FlowColumnExpand.None && _desiredColumnCount >
containerCount)
{
int diff = _desiredColumnCount - containerCount;
bool isLastColumn = colNumber == containerCount - 1;
switch (_flowColumnExpand)
{
case FlowColumnExpand.First:
if (colNumber == 0)
{
bounds = new Rect(0d, 0d, desiredColumnWidth +
(desiredColumnWidth * diff), 1d);
}
else if (isLastColumn)
{
bounds = new Rect(1d, 0d, desiredColumnWidth, 1d);
}
else
{
bounds = new Rect(desiredColumnWidth * (colNumber + diff) / (1d
- desiredColumnWidth), 0d, desiredColumnWidth, 1d);
}
break;
case FlowColumnExpand.Last:
if (colNumber == 0)
{
bounds = new Rect(0d, 0d,
desiredColumnWidth +
(desiredColumnWidth * diff), 1d);
}
else if (isLastColumn)
{
bounds = new Rect(1d, 0d,
desiredColumnWidth + (desiredColumnWidth * diff), 1d);
}
else
{
bounds = new Rect(desiredColumnWidth * colNumber / (1d - desiredColumnWidth), 0d, desiredColumnWidth, 1d);
}
break;
case FlowColumnExpand.Proportional:
double propColumnsWidth = 1d / containerCount;
if (colNumber == 0)
{
bounds = new Rect(0d, 0d, propColumnsWidth, 1d);
}
else if (isLastColumn)
{
bounds = new Rect(1d, 0d, propColumnsWidth, 1d);
}
else
{
bounds = new Rect(propColumnsWidth * colNumber / (1d - propColumnsWidth), 0d, propColumnsWidth, 1d);
}
break;
case FlowColumnExpand.ProportionalFirst:
int propFMod = _desiredColumnCount % containerCount;
double propFSize = desiredColumnWidth * Math.Floor((double)_desiredColumnCount / containerCount);
double propFSizeFirst = propFSize + desiredColumnWidth * propFMod;
if (colNumber == 0)
{
bounds = new Rect(0d, 0d, propFSizeFirst, 1d);
}
else if (isLastColumn)
{
bounds = new Rect(1d, 0d, propFSize, 1d);
}
else
{
bounds = new Rect(((propFSize * colNumber) + (propFSizeFirst - propFSize)) / (1d - propFSize), 0d, propFSize, 1d);
}
break;
case FlowColumnExpand.ProportionalLast:
int propLMod = _desiredColumnCount % containerCount;
double propLSize = desiredColumnWidth * Math.Floor((double)_desiredColumnCount / containerCount);
double propLSizeLast = propLSize + desiredColumnWidth * propLMod;
if (colNumber == 0)
{
bounds = new Rect(0d, 0d, propLSize, 1d);
}
else if (isLastColumn)
{
bounds = new Rect(1d, 0d, propLSizeLast, 1d);
}
else
{
bounds = new Rect((propLSize * colNumber) / (1d - propLSize), 0d, propLSize, 1d);
}
break;
}
}
else
{
if (Math.Abs(1d - desiredColumnWidth) < Epsilon.DoubleValue)
{
bounds = new Rect(1d, 0d, desiredColumnWidth, 1d);
}
else
{
bounds = new Rect(desiredColumnWidth * colNumber / (1d - desiredColumnWidth), 0d, desiredColumnWidth, 1d);
}
}
_rootLayout.SetLayoutBounds(view, bounds);
_rootLayout.SetLayoutFlags(view, AbsoluteLayoutFlags.All);
_rootLayout.Children.Add(view);
}
In the old forms library the part:
_rootLayout.SetLayoutBounds(view, bounds);
_rootLayout.SetLayoutFlags(view, AbsoluteLayoutFlags.All);
_rootLayout.Children.Add(view);
was:
_rootLayout.Children.Add(view, bounds, AbsoluteLayoutFlags.All);
but at Maui the Add Function has no overloads for do it this way, so i use the SetLayoutBounds and Set LayoutFlags functions. The problem is whit this way the List content don#t get shown and i see only an black background, what i do miss here ?
Here is the constructor from FlowListViewInternalCell, this class inherits from ViewCell:
public FlowListViewInternalCell(WeakReference<FlowListView> flowListViewRef)
{
_flowListViewRef = flowListViewRef;
flowListViewRef.TryGetTarget(out FlowListView flowListView);
_useGridAsMainRoot = !flowListView.FlowUseAbsoluteLayoutInternally;
if (!_useGridAsMainRoot)
{
_rootLayout = new AbsoluteLayout()
{
Padding = 0d,
BackgroundColor = flowListView.FlowRowBackgroundColor,
};
View = _rootLayout;
}
else
{
_rootLayoutAuto = new Grid()
{
RowSpacing = 0d,
ColumnSpacing = 0d,
Padding = 0d,
BackgroundColor = flowListView.FlowRowBackgroundColor,
};
View = _rootLayoutAuto;
}
_flowColumnTemplate = flowListView.FlowColumnTemplate;
_desiredColumnCount = flowListView.FlowDesiredColumnCount;
_flowColumnExpand = flowListView.FlowColumnExpand;
View.GestureRecognizers.Clear();
View.GestureRecognizers.Add(new TapGestureRecognizer());
}
Related
I want to move the zoomed image with gyroscope. As you can see in below code I can do this thing by hand and I got a good result. I have to write something inside "Gyroscope_ReadingChanged" to convert gyroscope data to the Content.TranslationX and Content.TranslationY. Any help would be appreciated.
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:video="clr-namespace:UbSports.Controls"
xmlns:local="clr-namespace:UbSports.Controls"
x:Class="UbSports.Pages.TestPage">
<local:PinchToZoomContainer VerticalOptions="FillAndExpand">
<local:PinchToZoomContainer.Content>
<Image x:Name="image" Source="waterfront.jpg" />
</local:PinchToZoomContainer.Content>
</local:PinchToZoomContainer>
</ContentPage>
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class TestPage: ContentPage
{
public TestPage()
{
InitializeComponent();
}
}
public class PinchToZoomContainer : ContentView
{
public PinchToZoomContainer()
{
Gyroscope.ReadingChanged += Gyroscope_ReadingChanged;
try
{
Gyroscope.Start(SensorSpeed.UI);
}
catch (FeatureNotSupportedException fnsEx)
{
}
catch (Exception ex)
{
}
var panGesture = new PanGestureRecognizer();
panGesture.PanUpdated += OnPanUpdated;
GestureRecognizers.Add(panGesture);
var pinchGesture = new PinchGestureRecognizer();
pinchGesture.PinchUpdated += OnPinchUpdated;
GestureRecognizers.Add(pinchGesture);
}
void Gyroscope_ReadingChanged(object sender, GyroscopeChangedEventArgs e)
{
var data = e.Reading;
if (data.AngularVelocity.X == 0 && data.AngularVelocity.Y == 0) return;
// do something
}
void OnPanUpdated(object sender, PanUpdatedEventArgs e)
{
switch (e.StatusType)
{
case GestureStatus.Started:
startX = e.TotalX;
startY = e.TotalY;
Content.AnchorX = 0;
Content.AnchorY = 0;
break;
case GestureStatus.Running:
var maxTranslationX = Content.Scale * Content.Width - Content.Width;
Content.TranslationX = Math.Min(0, Math.Max(-maxTranslationX, xOffset + e.TotalX - startX));
var maxTranslationY = Content.Scale * Content.Height - Content.Height;
Content.TranslationY = Math.Min(0, Math.Max(-maxTranslationY, yOffset + e.TotalY - startY));
break;
case GestureStatus.Completed:
xOffset = Content.TranslationX;
yOffset = Content.TranslationY;
break;
}
}
void OnPinchUpdated(object sender, PinchGestureUpdatedEventArgs e)
{
if (e.Status == GestureStatus.Started)
{
startScale = Content.Scale;
Content.AnchorX = 0;
Content.AnchorY = 0;
}
if (e.Status == GestureStatus.Running)
{
currentScale += (e.Scale - 1) * startScale;
currentScale = Math.Max(1, currentScale);
double renderedX = Content.X + xOffset;
double deltaX = renderedX / Width;
double deltaWidth = Width / (Content.Width * startScale);
double originX = (e.ScaleOrigin.X - deltaX) * deltaWidth;
double renderedY = Content.Y + yOffset;
double deltaY = renderedY / Height;
double deltaHeight = Height / (Content.Height * startScale);
double originY = (e.ScaleOrigin.Y - deltaY) * deltaHeight;
double targetX = xOffset - (originX * Content.Width) * (currentScale - startScale);
double targetY = yOffset - (originY * Content.Height) * (currentScale - startScale);
Content.TranslationX = targetX.Clamp(-Content.Width * (currentScale - 1), 0);
Content.TranslationY = targetY.Clamp(-Content.Height * (currentScale - 1), 0);
Content.Scale = currentScale;
}
if (e.Status == GestureStatus.Completed)
{
xOffset = Content.TranslationX;
yOffset = Content.TranslationY;
}
}
}
I'm making an image viewing feature in my app that allows the user to zoom and pan.
I've used the following documentation to achieve this through SkiaSharp.
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/graphics/skiasharp/transforms/touch
What I am trying to achieve now is to restrict the user from zooming out or panning past the edges of the bitmap that is loaded into the SKCanvasView.
I have restricted the zoom but the problem I have is that I cannot figure out how to restrict the panning, I cannot find ANY example of how to do this online either. Is there a a SkiaSharp guru out there who has achieved this?
Here is code...
Relevant XAML:
<Grid BackgroundColor="#141d3d">
<Grid.Effects>
<tt:TouchEffect Capture="True" TouchAction="OnTouchEffectAction" />
</Grid.Effects>
<skia:SKCanvasView x:Name="canvasView" PaintSurface="OnCanvasViewPaintSurface" />
<Button Text="CLOSE"
TextColor="White"
VerticalOptions="Start"
HorizontalOptions="End"
BackgroundColor="Transparent"
Command="{Binding CmdCloseFullImg}" />
</Grid>
XAML.cs
public partial class AePage : ContentPage
{
private AeViewModel aeViewModel = new AeViewModel();
private TouchManipulationBitmap bitmap = new TouchManipulationBitmap();
private List<long> touchIds = new List<long>();
public static float CanvasWidth { get; set; }
public static float CanvasHeight { get; set; }
public AePage()
{
InitializeComponent();
BindingContext = aeViewModel;
}
private void OnCanvasViewPaintSurface(object sender, SkiaSharp.Views.Forms.SKPaintSurfaceEventArgs e)
{
SKImageInfo info = e.Info;
SKCanvas canvas = e.Surface.Canvas;
canvas.Clear();
// Display the bitmap
bitmap.Paint(info, canvas);
}
private void OnTouchEffectAction(object sender, TouchActionEventArgs e)
{
CanvasWidth = canvasView.CanvasSize.Width;
CanvasHeight = canvasView.CanvasSize.Height;
// Convert Xamarin.Forms point to pixels
TouchTrackingPoint pt = e.Location;
SKPoint point = new SKPoint((float)(canvasView.CanvasSize.Width * pt.X / canvasView.Width),
(float)(canvasView.CanvasSize.Height * pt.Y / canvasView.Height));
switch (e.Type)
{
case TouchActionType.Pressed:
touchIds.Add(e.Id);
bitmap.ProcessTouchEvent(e.Id, e.Type, point);
break;
case TouchActionType.Moved:
if (touchIds.Contains(e.Id))
{
bitmap.ProcessTouchEvent(e.Id, e.Type, point);
canvasView.InvalidateSurface();
}
break;
case TouchActionType.Released:
case TouchActionType.Cancelled:
if (touchIds.Contains(e.Id))
{
bitmap.ProcessTouchEvent(e.Id, e.Type, point);
touchIds.Remove(e.Id);
canvasView.InvalidateSurface();
}
break;
}
}
}
TouchManipulationBitmap Class:
public class TouchManipulationBitmap
{
public SKBitmap bitmap;
public TouchManipulationManager TouchManager { set; get; }
public static SKMatrix Matrix { set; get; }
private Dictionary<long, TouchManipulationInfo> touchDictionary = new Dictionary<long, TouchManipulationInfo>();
public TouchManipulationBitmap()
{
this.bitmap = ReturnSKBitmap();
Matrix = SKMatrix.MakeIdentity();
TouchManager = new TouchManipulationManager();
}
public SKBitmap ReturnSKBitmap()
{
string resourceId = "MetroAlarmHandlerMobile.Media.David DP.jpg";
Assembly assembly = GetType().GetTypeInfo().Assembly;
using (System.IO.Stream stream = assembly.GetManifestResourceStream(resourceId))
{
return SKBitmap.Decode(stream);
}
}
public void Paint(SKImageInfo info, SKCanvas canvas)
{
canvas.Save();
SKMatrix matrix = Matrix;
canvas.Concat(ref matrix);
float scale = Math.Min((float)info.Width / bitmap.Width, (float)info.Height / bitmap.Height);
float x = (info.Width - scale * bitmap.Width) / 2;
float y = (info.Height - scale * bitmap.Height) / 2;
SKRect destRect = new SKRect(x, y, x + scale * bitmap.Width, y + scale * bitmap.Height);
canvas.DrawBitmap(bitmap, destRect);
canvas.Restore();
Console.WriteLine($"SCALE: {Matrix.ScaleX}");
Console.WriteLine($"TRANSLATION: X = {Matrix.TransX} Y = {Matrix.TransY}");
}
public void ProcessTouchEvent(long id, TouchActionType type, SKPoint location)
{
switch (type)
{
case TouchActionType.Pressed:
touchDictionary.Add(id, new TouchManipulationInfo
{
PreviousPoint = location,
NewPoint = location
});
break;
case TouchActionType.Moved:
TouchManipulationInfo info = touchDictionary[id];
info.NewPoint = location;
Manipulate();
info.PreviousPoint = info.NewPoint;
break;
case TouchActionType.Released:
touchDictionary[id].NewPoint = location;
Manipulate();
touchDictionary.Remove(id);
break;
case TouchActionType.Cancelled:
touchDictionary.Remove(id);
break;
}
}
private void Manipulate()
{
TouchManipulationInfo[] infos = new TouchManipulationInfo[touchDictionary.Count];
touchDictionary.Values.CopyTo(infos, 0);
SKMatrix touchMatrix = SKMatrix.MakeIdentity();
if (infos.Length == 1)
{
SKPoint prevPoint = infos[0].PreviousPoint;
SKPoint newPoint = infos[0].NewPoint;
SKPoint pivotPoint = Matrix.MapPoint(bitmap.Width / 2, bitmap.Height / 2);
touchMatrix = TouchManager.OneFingerManipulate(prevPoint, newPoint, pivotPoint);
}
else if (infos.Length >= 2)
{
int pivotIndex = infos[0].NewPoint == infos[0].PreviousPoint ? 0 : 1;
SKPoint pivotPoint = infos[pivotIndex].NewPoint;
SKPoint newPoint = infos[1 - pivotIndex].NewPoint;
SKPoint prevPoint = infos[1 - pivotIndex].PreviousPoint;
touchMatrix = TouchManager.TwoFingerManipulate(prevPoint, newPoint, pivotPoint);
}
SKMatrix matrix = Matrix;
SKMatrix.PostConcat(ref matrix, touchMatrix);
Matrix = matrix;
}
}
TouchManipulationManager Class:
public class TouchManipulationManager
{
private float Magnitude(SKPoint point)
{
return (float)Math.Sqrt(Math.Pow(point.X, 2) + Math.Pow(point.Y, 2));
}
public SKMatrix OneFingerManipulate(SKPoint prevPoint, SKPoint newPoint, SKPoint pivotPoint)
{
SKMatrix touchMatrix = SKMatrix.MakeIdentity();
SKPoint delta = newPoint - prevPoint;
// Multiply the rotation matrix by a translation matrix
SKMatrix.PostConcat(ref touchMatrix, SKMatrix.MakeTranslation(delta.X, delta.Y));
return touchMatrix;
}
public SKMatrix TwoFingerManipulate(SKPoint prevPoint, SKPoint newPoint, SKPoint pivotPoint)
{
SKMatrix touchMatrix = SKMatrix.MakeIdentity();
SKPoint oldVector = prevPoint - pivotPoint;
SKPoint newVector = newPoint - pivotPoint;
float scale = Magnitude(newVector) / Magnitude(oldVector);
if (TouchManipulationBitmap.Matrix.ScaleX <= 1 && scale <= 1) return touchMatrix;
if (!float.IsNaN(scale) && !float.IsInfinity(scale))
{
SKMatrix.PostConcat(ref touchMatrix, SKMatrix.MakeScale(scale, scale, pivotPoint.X, pivotPoint.Y));
}
return touchMatrix;
}
}
TouchManipulationInfo Class:
public class TouchManipulationInfo
{
public SKPoint PreviousPoint { set; get; }
public SKPoint NewPoint { set; get; }
}
For anyone still searching for the answer, this is what I came up with:
For OneFingerManipulate, the idea is to find the relative top left and bottom right points of the original bitmap according to the current matrix (which may be scaled), and then see if the current movement request will cause the translation to exceed these bounds.
(I removed pivotPoint as it has no use in OneFingerManipulate)
Bitmap is also static in TouchManipulationBitmap as it is more convenient.
public SKMatrix OneFingerManipulate(SKPoint prevPoint, SKPoint newPoint)
{
// if movement will take us out of bounds of image, stop the movement in that direction
SKPoint topLeft = TouchManipulationBitmap.Matrix.MapPoint(new SKPoint(0, 0));
SKPoint bottomRight = TouchManipulationBitmap.Matrix.MapPoint(new SKPoint(TouchManipulationBitmap.Bitmap.Width, TouchManipulationBitmap.Bitmap.Height));
SKMatrix touchMatrix = SKMatrix.CreateIdentity();
SKPoint delta = newPoint - prevPoint;
if ((delta.X > 0 && topLeft.X >= 0) // moving left
||
(delta.X < 0 && bottomRight.X <= TouchManipulationBitmap.Bitmap.Width)) // moving right
{
delta.X = 0;
}
if ((delta.Y > 0 && topLeft.Y >= 0) // moving up
||
(delta.Y < 0 && bottomRight.Y <= TouchManipulationBitmap.Bitmap.Height)) // moving down
{
delta.Y = 0;
}
// Multiply the rotation matrix by a translation matrix
touchMatrix = touchMatrix.PostConcat(SKMatrix.CreateTranslation(delta.X, delta.Y));
return touchMatrix;
}
TwoFingerManipulate is a bit trickier, since the movement is on vectors. So I used a translation after the scaling
public SKMatrix TwoFingerManipulate(SKPoint prevPoint, SKPoint newPoint, SKPoint pivotPoint)
{
SKMatrix touchMatrix = SKMatrix.CreateIdentity();
SKPoint oldVector = prevPoint - pivotPoint;
SKPoint newVector = newPoint - pivotPoint;
float scale = Magnitude(newVector) / Magnitude(oldVector);
// if the scale crossed MIN_SCALE and the manipulation is below median (keep on scaling out) - do nothing
if (TouchManipulationBitmap.Matrix.ScaleX <= TouchManipulationBitmap.MIN_SCALE && scale <= TouchManipulationBitmap.SCALE_MEDIAN) return touchMatrix;
// if the scale crossed MAX_SCALE and the manipulation is above median (keep on scaling in) - do nothing
if (TouchManipulationBitmap.Matrix.ScaleX >= TouchManipulationBitmap.MAX_SCALE && scale >= TouchManipulationBitmap.SCALE_MEDIAN) return touchMatrix;
if (!float.IsNaN(scale) && !float.IsInfinity(scale))
{
touchMatrix = touchMatrix.PostConcat(SKMatrix.CreateScale(scale, scale, pivotPoint.X, pivotPoint.Y));
}
SKPoint topLeft = TouchManipulationBitmap.Matrix.MapPoint(new SKPoint(0, 0));
SKPoint bottomRight = TouchManipulationBitmap.Matrix.MapPoint(new SKPoint(TouchManipulationBitmap.Bitmap.Width, TouchManipulationBitmap.Bitmap.Height));
// top left
if (topLeft.X > 0 && topLeft.Y > 0)
touchMatrix = touchMatrix.PostConcat(SKMatrix.CreateTranslation(-topLeft.X, -topLeft.Y));
// left
else if (topLeft.X > 0)
touchMatrix = touchMatrix.PostConcat(SKMatrix.CreateTranslation(-topLeft.X, 0));
//top
else if (topLeft.Y > 0)
touchMatrix = touchMatrix.PostConcat(SKMatrix.CreateTranslation(0, -topLeft.Y));
// bottom right
if (bottomRight.X < TouchManipulationBitmap.Bitmap.Width && bottomRight.Y < TouchManipulationBitmap.Bitmap.Height)
touchMatrix = touchMatrix.PostConcat(SKMatrix.CreateTranslation(TouchManipulationBitmap.Bitmap.Width - bottomRight.X, TouchManipulationBitmap.Bitmap.Height - bottomRight.Y));
// right
else if (bottomRight.X < TouchManipulationBitmap.Bitmap.Width)
touchMatrix = touchMatrix.PostConcat(SKMatrix.CreateTranslation(TouchManipulationBitmap.Bitmap.Width - bottomRight.X, 0));
// bottom
else if (bottomRight.Y < TouchManipulationBitmap.Bitmap.Height)
touchMatrix = touchMatrix.PostConcat(SKMatrix.CreateTranslation(0, TouchManipulationBitmap.Bitmap.Height - bottomRight.Y));
return touchMatrix;
}
I have a problem. I created an image within a AbsoluteLayout with a panGestureRecognizer, but when I use that to move the image in the app, it jumps back and forth. Very annoying. Here is the code I am using
private void AddImageToPreview(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
{
var image = (Image)e.NewItems[e.NewItems.Count - 1];
PanGestureRecognizer panGesture = new PanGestureRecognizer();
panGesture.PanUpdated += PanUpdated;
image.GestureRecognizers.Add(panGesture);
MyLayout.Children.Add(image);
}
}
void PanUpdated(object sender, PanUpdatedEventArgs args)
{
Image image = (Image)sender;
if (args.StatusType.Equals(GestureStatus.Running))
{
x = args.TotalX;
y = args.TotalY;
image.TranslateTo(x, y, 1);
}
}
Can someone help me improve this, so this drag/drop system works smooth and fast!
I have a control purely made in Xamarin.Forms that you can use for the above mentioned usage:
using System;
using Xamarin.Forms;
using FFImageLoading.Forms;
public class ZoomImage : CachedImage
{
private const double MIN_SCALE = 1;
private const double MAX_SCALE = 4;
private const double OVERSHOOT = 0.15;
private double StartScale, LastScale;
private double StartX, StartY;
public ZoomImage()
{
var pinch = new PinchGestureRecognizer();
pinch.PinchUpdated += OnPinchUpdated;
GestureRecognizers.Add(pinch);
var pan = new PanGestureRecognizer();
pan.PanUpdated += OnPanUpdated;
GestureRecognizers.Add(pan);
var tap = new TapGestureRecognizer { NumberOfTapsRequired = 2 };
tap.Tapped += OnTapped;
GestureRecognizers.Add(tap);
Scale = MIN_SCALE;
TranslationX = TranslationY = 0;
AnchorX = AnchorY = 0;
}
protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
{
Scale = MIN_SCALE;
TranslationX = TranslationY = 0;
AnchorX = AnchorY = 0;
return base.OnMeasure(widthConstraint, heightConstraint);
}
private void OnTapped(object sender, EventArgs e)
{
if (Scale > MIN_SCALE)
{
this.ScaleTo(MIN_SCALE, 250, Easing.CubicInOut);
this.TranslateTo(0, 0, 250, Easing.CubicInOut);
}
else
{
AnchorX = AnchorY = 0.5; //TODO tapped position
this.ScaleTo(MAX_SCALE, 250, Easing.CubicInOut);
}
}
private void OnPanUpdated(object sender, PanUpdatedEventArgs e)
{
switch (e.StatusType)
{
case GestureStatus.Started:
StartX = (1 - AnchorX) * Width;
StartY = (1 - AnchorY) * Height;
break;
case GestureStatus.Running:
AnchorX = Clamp(1 - (StartX + e.TotalX) / Width, 0, 1);
AnchorY = Clamp(1 - (StartY + e.TotalY) / Height, 0, 1);
break;
}
}
private void OnPinchUpdated(object sender, PinchGestureUpdatedEventArgs e)
{
switch (e.Status)
{
case GestureStatus.Started:
LastScale = e.Scale;
StartScale = Scale;
AnchorX = e.ScaleOrigin.X;
AnchorY = e.ScaleOrigin.Y;
break;
case GestureStatus.Running:
if (e.Scale < 0 || Math.Abs(LastScale - e.Scale) > (LastScale * 1.3) - LastScale)
{ return; }
LastScale = e.Scale;
var current = Scale + (e.Scale - 1) * StartScale;
Scale = Clamp(current, MIN_SCALE * (1 - OVERSHOOT), MAX_SCALE * (1 + OVERSHOOT));
break;
case GestureStatus.Completed:
if (Scale > MAX_SCALE)
this.ScaleTo(MAX_SCALE, 250, Easing.SpringOut);
else if (Scale < MIN_SCALE)
this.ScaleTo(MIN_SCALE, 250, Easing.SpringOut);
break;
}
}
private T Clamp<T>(T value, T minimum, T maximum) where T: IComparable
{
if (value.CompareTo(minimum) < 0)
return minimum;
else if (value.CompareTo(maximum) > 0)
return maximum;
else
return value;
}
}
What this does:
Pinch zoom, Pan and Swipe movements together with double tap centre zoom and un-zoom
Note: I have used FFimageLoading's CachedImage because I needed to cache the data in case you do not intend this replace CachedImage with Xamarin.Forms.Image
I want to zoom and pan an image in my app.But it is not working perfectly.
I tried this link: https://forums.xamarin.com/discussion/74168/full-screen-image-viewer-with-pinch-to-zoom-pan-to-move-tap-to-show-captions-for-xamarin-forms
Please help me..
Here is my XAML Code
<ContentPage.Content>
<StackLayout HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand" BackgroundColor="Yellow">
<Image Source="download" x:Name="img">
<Image.GestureRecognizers>
<PanGestureRecognizer PanUpdated="OnPanUpdated"/>
<PinchGestureRecognizer PinchUpdated="OnPinchUpdated"/>
<TapGestureRecognizer Tapped="OnTapped"/>
</Image.GestureRecognizers>
</Image>
</StackLayout>
</ContentPage.Content>
My mainpage.cs
private const double MIN_SCALE = 1;
private const double MAX_SCALE = 4;
private const double OVERSHOOT = 0.15;
private double StartScale;
private double LastX, LastY;
private double StartX, StartY;
public MainPage()
{
InitializeComponent();
Scale = MIN_SCALE;
TranslationX = TranslationY = 0;
AnchorX = AnchorY = 0;
}
protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
{
Scale = MIN_SCALE;
TranslationX = TranslationY = 0;
AnchorX = AnchorY = 0;
return base.OnMeasure(widthConstraint, heightConstraint);
}
private void OnTapped(object sender, EventArgs e)
{
if (Scale > MIN_SCALE)
{
this.ScaleTo(MIN_SCALE, 250, Easing.CubicInOut);
this.TranslateTo(0, 0, 250, Easing.CubicInOut);
}
else
{
AnchorX = AnchorY = 0.5; //TODO tapped position
this.ScaleTo(MAX_SCALE, 250, Easing.CubicInOut);
}
}
private void OnPanUpdated(object sender, PanUpdatedEventArgs e)
{
if (Scale > MIN_SCALE)
switch (e.StatusType)
{
case GestureStatus.Started:
StartX = (1 - AnchorX) * Width;
StartY = (1 - AnchorY) * Height;
break;
case GestureStatus.Running:
TranslationX = Clamp(LastX + e.TotalX * Scale, -Width / 2, Width / 2);
TranslationY = Clamp(LastY + e.TotalY * Scale, -Height / 2, Height / 2);
break;
}
//switch (e.StatusType)
//{
// case GestureStatus.Started:
// StartX = (1 - AnchorX) * Width;
// StartY = (1 - AnchorY) * Height;
// break;
// case GestureStatus.Running:
// AnchorX = Clamp(1 - (StartX + e.TotalX) / Width, 0, 1);
// AnchorY = Clamp(1 - (StartY + e.TotalY) / Height, 0, 1);
// break;
//}
}
private void OnPinchUpdated(object sender, PinchGestureUpdatedEventArgs e)
{
switch (e.Status)
{
case GestureStatus.Started:
StartScale = Scale;
AnchorX = e.ScaleOrigin.X;
AnchorY = e.ScaleOrigin.Y;
break;
case GestureStatus.Running:
double current = Scale + (e.Scale - 1) * StartScale;
Scale = Clamp(current, MIN_SCALE * (1 - OVERSHOOT), MAX_SCALE * (1 + OVERSHOOT));
break;
case GestureStatus.Completed:
if (Scale > MAX_SCALE)
this.ScaleTo(MAX_SCALE, 250, Easing.SpringOut);
else if (Scale < MIN_SCALE)
this.ScaleTo(MIN_SCALE, 250, Easing.SpringOut);
break;
}
}
protected override void OnSizeAllocated(double width, double height)
{
base.OnSizeAllocated(width, height); //must be called
img.TranslateTo(0, 0, 0, Easing.Linear);
}
private T Clamp<T>(T value, T minimum, T maximum) where T : IComparable
{
if (value.CompareTo(minimum) < 0)
return minimum;
else if (value.CompareTo(maximum) > 0)
return maximum;
else
return value;
}
}
When I zoom the image ,the image position look like this
Image gestures is not working perfectly.When double tap the image ,zoom in and zoom out work perfectly.
I just want to know the best way to draw a line in windows form without using GDI+,but use control from toolbox.
You can use Visual Basic Power Pack Line Shape class - http://msdn.microsoft.com/en-us/library/microsoft.visualbasic.powerpacks.lineshape.aspx
Another solution using Label control
Add a Label control to your form.
Set Label Text to empty.
Set BorderStyle to Fixed3D.
Set AutoSize to false.
Set Height to 2 (most easily accomplished by typing 2 into the Size/Height field under Label Properties).
Source : Draw horizontal divider in winforms
Might not be the best choice but should help
Label lbl = new Label();
lbl.Text = "_________________________________";
this.Controls.Add(lbl);
From: http://www.csharp-examples.net/separator-line/
// separator bevel line
Label labelSeperator = new Label();
labelSeperator.AutoSize = false;
labelSeperator.Height = 2;
labelSeperator.BorderStyle = BorderStyle.Fixed3D;
this.Controls.Add(labelSeperator);
You can use my control:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
public class AmenLine : Control
{
bool SzChInternaly;
public enum EDIR : int
{
Horizontal = 0,
Vertical = 1,
Slash = 2,
BKSlash = 3
}
public enum eStyle : int
{
rect = 0,
circ = 1
}
private eStyle _DrawStyle;
public eStyle DrawStyle {
get { return _DrawStyle; }
set {
_DrawStyle = value;
BuildMe(_Direc);
}
}
private EDIR _Direc = EDIR.Horizontal;
public EDIR Direction {
get { return _Direc; }
set {
if ((value == EDIR.Horizontal && _Direc == EDIR.Vertical) | (value == EDIR.Vertical && _Direc == EDIR.Horizontal)) {
SzChInternaly = true;
this.Size = new Size(this.Height, this.Width);
}
_Direc = value;
BuildMe(value);
}
}
private void BuildMe(EDIR BodyDir)
{
try {
SzChInternaly = true;
Drawing2D.GraphicsPath gr = new Drawing2D.GraphicsPath();
switch (BodyDir) {
case EDIR.Horizontal:
this.Size = new Size(this.Width, _BorderWidth + 10);
gr.AddRectangle(new Rectangle(0, (-_BorderWidth + this.Height) / 2, this.Width, _BorderWidth));
break;
case EDIR.Vertical:
this.Size = new Size(_BorderWidth + 10, this.Height);
gr.AddRectangle(new Rectangle((-_BorderWidth + this.Width) / 2, 0, _BorderWidth, this.Height));
break;
case EDIR.Slash:
try {
// CType(_BorderWidth * Height / Width, Integer)
for (i = 0; i <= Convert.ToInt32(Math.Pow(Math.Pow(Width, 2) + Math.Pow(Height, 2), 0.5)); i += _BorderWidth) {
switch (_DrawStyle) {
case eStyle.circ:
gr.AddPie(i, Convert.ToInt32(Height * i / Width), _BorderWidth, _BorderWidth, 0, 360);
break;
case eStyle.rect:
gr.AddRectangle(new Rectangle(i, Convert.ToInt32(Height * i / Width), _BorderWidth, _BorderWidth));
break;
}
}
} catch {
}
break;
case EDIR.BKSlash:
try {
// CType(_BorderWidth * Height / Width, Integer)
for (i = 0; i <= Convert.ToInt32(Math.Pow(Math.Pow(this.Height, 2) + Math.Pow(this.Width, 2), 0.5)); i += _BorderWidth) {
switch (_DrawStyle) {
case eStyle.circ:
gr.AddPie(Width - 1 - i, Convert.ToInt32(Height * i / Width), _BorderWidth, _BorderWidth, 0, 360);
break;
case eStyle.rect:
gr.AddRectangle(new Rectangle(Width - 1 - i, Convert.ToInt32(Height * i / Width), _BorderWidth, _BorderWidth));
break;
}
}
} catch {
}
break;
}
this.Region = new Region(gr);
SzChInternaly = false;
} catch {
}
}
private int _BorderWidth = 1;
public int BorderWidth {
get { return _BorderWidth; }
set {
_BorderWidth = value;
BuildMe(_Direc);
}
}
public AmenLine()
{
this.BackColor = Color.Black;
BuildMe(_Direc);
}
protected override void OnSizeChanged(System.EventArgs e)
{
base.OnSizeChanged(e);
if (SzChInternaly == false)
BuildMe(_Direc);
}
}
You can draw line runtime by below code:
Pen pen = new Pen(Color.FromArgb(255, 0, 0, 0));
e.Graphics.DrawLine(pen, 20, 10, 300, 100);