Not
Bu sayfaya erişim yetkilendirme gerektiriyor. Oturum açmayı veya dizinleri değiştirmeyi deneyebilirsiniz.
Bu sayfaya erişim yetkilendirme gerektiriyor. Dizinleri değiştirmeyi deneyebilirsiniz.
Dokunmatik sürükleme, sıkıştırma ve döndürme uygulamak için matris dönüşümlerini kullanma
Mobil cihazlardakiler gibi çok dokunmalı ortamlarda kullanıcılar genellikle ekrandaki nesneleri işlemek için parmaklarını kullanır. Tek parmakla sürükleme ve iki parmakla sıkıştırma gibi yaygın hareketler nesneleri taşıyabilir ve ölçeklendirebilir, hatta döndürebilir. Bu hareketler genellikle dönüşüm matrisleri kullanılarak uygulanır ve bu makalede bunu nasıl yapabileceğiniz gösterilir.

Burada gösterilen tüm örnekler, Efektlerden Olayları Çağırma makalesinde sunulan dokunma izleme efektini kullanırXamarin.Forms.
Sürükleme ve Çeviri
Matris dönüşümlerinin en önemli uygulamalarından biri dokunma işlemedir. Tek SKMatrix bir değer, bir dizi dokunma işlemi birleştirebilir.
Tek parmakla sürükleme için SKMatrix değer çeviri gerçekleştirir. Bu, Bit Eşlem Sürükleme sayfasında gösterilir. XAML dosyası içinde Xamarin.FormsGridbir SKCanvasView örneği oluşturur. TouchEffect Bu koleksiyonuna Effects Gridbir nesne eklendi:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:skia="clr-namespace:SkiaSharp.Views.Forms;assembly=SkiaSharp.Views.Forms"
xmlns:tt="clr-namespace:TouchTracking"
x:Class="SkiaSharpFormsDemos.Transforms.BitmapDraggingPage"
Title="Bitmap Dragging">
<Grid BackgroundColor="White">
<skia:SKCanvasView x:Name="canvasView"
PaintSurface="OnCanvasViewPaintSurface" />
<Grid.Effects>
<tt:TouchEffect Capture="True"
TouchAction="OnTouchEffectAction" />
</Grid.Effects>
</Grid>
</ContentPage>
Teoride TouchEffect , nesnesi doğrudan koleksiyonuna Effects SKCanvasVieweklenebilir, ancak bu tüm platformlarda çalışmaz. SKCanvasView bu yapılandırmadaki ile Grid aynı boyutta olduğundan, aynı Grid şekilde çalışır.
Arka planda kod dosyası oluşturucusunda bir bit eşlem kaynağına yüklenir ve işleyicide PaintSurface görüntülenir:
public partial class BitmapDraggingPage : ContentPage
{
// Bitmap and matrix for display
SKBitmap bitmap;
SKMatrix matrix = SKMatrix.MakeIdentity();
···
public BitmapDraggingPage()
{
InitializeComponent();
string resourceID = "SkiaSharpFormsDemos.Media.SeatedMonkey.jpg";
Assembly assembly = GetType().GetTypeInfo().Assembly;
using (Stream stream = assembly.GetManifestResourceStream(resourceID))
{
bitmap = SKBitmap.Decode(stream);
}
}
···
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
// Display the bitmap
canvas.SetMatrix(matrix);
canvas.DrawBitmap(bitmap, new SKPoint());
}
}
Başka kod olmadan değer SKMatrix her zaman tanımlama matrisidir ve bit eşlemin görüntülenmesi üzerinde hiçbir etkisi olmaz. XAML dosyasında ayarlanan işleyicinin OnTouchEffectAction amacı, matris değerini dokunmatik işlemeleri yansıtacak şekilde değiştirmektir.
İşleyici, OnTouchEffectAction değeri bir SkiaSharp SKPoint değerine dönüştürerek Xamarin.FormsPoint başlar. Bu, ve özelliklerine SKCanvasView (cihazdan bağımsız birimlerdir) ve Height piksel birimleri cinsinden özelliğine CanvasSize göre Width ölçeklendirmenin basit bir konusudur:
public partial class BitmapDraggingPage : ContentPage
{
···
// Touch information
long touchId = -1;
SKPoint previousPoint;
···
void OnTouchEffectAction(object sender, TouchActionEventArgs args)
{
// Convert Xamarin.Forms point to pixels
Point pt = args.Location;
SKPoint point =
new SKPoint((float)(canvasView.CanvasSize.Width * pt.X / canvasView.Width),
(float)(canvasView.CanvasSize.Height * pt.Y / canvasView.Height));
switch (args.Type)
{
case TouchActionType.Pressed:
// Find transformed bitmap rectangle
SKRect rect = new SKRect(0, 0, bitmap.Width, bitmap.Height);
rect = matrix.MapRect(rect);
// Determine if the touch was within that rectangle
if (rect.Contains(point))
{
touchId = args.Id;
previousPoint = point;
}
break;
case TouchActionType.Moved:
if (touchId == args.Id)
{
// Adjust the matrix for the new position
matrix.TransX += point.X - previousPoint.X;
matrix.TransY += point.Y - previousPoint.Y;
previousPoint = point;
canvasView.InvalidateSurface();
}
break;
case TouchActionType.Released:
case TouchActionType.Cancelled:
touchId = -1;
break;
}
}
···
}
Bir parmak ekrana ilk kez dokunduğunda, türünde TouchActionType.Pressed bir olay tetiklenir. İlk görev, parmağın bit eşlem dokunup dokunmadığını belirlemektir. Böyle bir görev genellikle isabet testi olarak adlandırılır. Bu durumda, isabet sınaması bit eşlemine karşılık gelen bir SKRect değer oluşturarak, ile MapRectmatris dönüşümünü uygulayarak ve ardından dokunma noktasının dönüştürülmüş dikdörtgenin içinde olup olmadığını belirleyerek gerçekleştirilebilir.
Bu durumda, touchId alan dokunmatik kimlik olarak ayarlanır ve parmak konumu kaydedilir.
Olay için TouchActionType.Moved değerin çeviri faktörleri SKMatrix , parmağın geçerli konumuna ve parmağın yeni konumuna göre ayarlanır. Bu yeni konum bir sonraki sefer için kaydedilir ve SKCanvasView geçersiz kılındı.
Bu programla deneme yaparken bit eşlemi yalnızca parmağınız bit eşlemin görüntülendiği bir alana dokunduğunda sürükleyebileceğinizi unutmayın. Bu kısıtlama bu program için çok önemli olmasa da, birden çok bit eşlemi düzenlerken çok önemli hale gelir.
Sıkıştırma ve Ölçeklendirme
İki parmak bit eşleme dokunduğunda ne olmasını istiyorsunuz? İki parmak paralel hareket ederse bit eşleminin parmaklarla birlikte hareket etmesini istersiniz. İki parmak bir sıkıştırma veya esnetme işlemi gerçekleştiriyorsa bit eşleminin döndürülmesini (sonraki bölümde ele alınması) veya ölçeklendirilmesini isteyebilirsiniz. Bir bit eşlemi ölçeklendirirken, iki parmağın bit eşlem ile aynı konumlarda kalması ve bit eşlemin buna göre ölçeklendirilmesi en mantıklı seçenektir.
aynı anda iki parmağınızı işlemek karmaşık görünür, ancak işleyicinin TouchAction tek seferde yalnızca bir parmakla ilgili bilgileri aldığını unutmayın. Bit eşlemi iki parmakla işleniyorsa, her olay için bir parmağın konumu değişmiştir ancak diğeri değişmemiştir. Aşağıdaki Bit Eşlem Ölçeklendirme sayfa kodunda, dönüştürme bu noktaya göre olduğundan konumu değişmeyen parmak özet noktası olarak adlandırılır.
Bu program ile önceki program arasındaki bir fark, birden çok dokunmatik kimlik kaydedilmesi gerektiğidir. Bu amaç için bir sözlük kullanılır; burada dokunmatik kimlik sözlük tuşu, sözlük değeri ise o parmağın geçerli konumudur:
public partial class BitmapScalingPage : ContentPage
{
···
// Touch information
Dictionary<long, SKPoint> touchDictionary = new Dictionary<long, SKPoint>();
···
void OnTouchEffectAction(object sender, TouchActionEventArgs args)
{
// Convert Xamarin.Forms point to pixels
Point pt = args.Location;
SKPoint point =
new SKPoint((float)(canvasView.CanvasSize.Width * pt.X / canvasView.Width),
(float)(canvasView.CanvasSize.Height * pt.Y / canvasView.Height));
switch (args.Type)
{
case TouchActionType.Pressed:
// Find transformed bitmap rectangle
SKRect rect = new SKRect(0, 0, bitmap.Width, bitmap.Height);
rect = matrix.MapRect(rect);
// Determine if the touch was within that rectangle
if (rect.Contains(point) && !touchDictionary.ContainsKey(args.Id))
{
touchDictionary.Add(args.Id, point);
}
break;
case TouchActionType.Moved:
if (touchDictionary.ContainsKey(args.Id))
{
// Single-finger drag
if (touchDictionary.Count == 1)
{
SKPoint prevPoint = touchDictionary[args.Id];
// Adjust the matrix for the new position
matrix.TransX += point.X - prevPoint.X;
matrix.TransY += point.Y - prevPoint.Y;
canvasView.InvalidateSurface();
}
// Double-finger scale and drag
else if (touchDictionary.Count >= 2)
{
// Copy two dictionary keys into array
long[] keys = new long[touchDictionary.Count];
touchDictionary.Keys.CopyTo(keys, 0);
// Find index of non-moving (pivot) finger
int pivotIndex = (keys[0] == args.Id) ? 1 : 0;
// Get the three points involved in the transform
SKPoint pivotPoint = touchDictionary[keys[pivotIndex]];
SKPoint prevPoint = touchDictionary[args.Id];
SKPoint newPoint = point;
// Calculate two vectors
SKPoint oldVector = prevPoint - pivotPoint;
SKPoint newVector = newPoint - pivotPoint;
// Scaling factors are ratios of those
float scaleX = newVector.X / oldVector.X;
float scaleY = newVector.Y / oldVector.Y;
if (!float.IsNaN(scaleX) && !float.IsInfinity(scaleX) &&
!float.IsNaN(scaleY) && !float.IsInfinity(scaleY))
{
// If something bad hasn't happened, calculate a scale and translation matrix
SKMatrix scaleMatrix =
SKMatrix.MakeScale(scaleX, scaleY, pivotPoint.X, pivotPoint.Y);
SKMatrix.PostConcat(ref matrix, scaleMatrix);
canvasView.InvalidateSurface();
}
}
// Store the new point in the dictionary
touchDictionary[args.Id] = point;
}
break;
case TouchActionType.Released:
case TouchActionType.Cancelled:
if (touchDictionary.ContainsKey(args.Id))
{
touchDictionary.Remove(args.Id);
}
break;
}
}
···
}
Eylemin Pressed işlenmesi, kimlik ve dokunma noktasının sözlüğe eklenmesi dışında önceki programla neredeyse aynıdır. Released ve Cancelled eylemleri sözlük girdisini kaldırır.
Ancak eylemin Moved işlenmesi daha karmaşıktır. Yalnızca bir parmağınızla ilgiliyse, işlem önceki programla çok aynıdır. İki veya daha fazla parmak için, programın hareket etmeyen parmağı içeren sözlükten de bilgi alması gerekir. Bunu, sözlük anahtarlarını bir diziye kopyalayıp ilk anahtarı taşınan parmağın kimliğiyle karşılaştırarak yapar. Bu, programın hareket etmeyen parmakla ilgili pivot noktasını almasını sağlar.
Ardından, program yeni parmak konumunun pivot noktasına göre iki vektör ve pivot noktasına göre eski parmak konumunu hesaplar. Bu vektörlerin oranları ölçeklendirme faktörleridir. Sıfıra bölme olasılığı olduğundan, bunlar sonsuz değerler veya NaN (sayı değil) değerleri için denetlenmelidir. Her şey yolundaysa, ölçeklendirme dönüşümü alan olarak kaydedilen değerle SKMatrix birleştirilir.
Bu sayfayla denemeler yaparken bit eşlemi bir veya iki parmakla sürükleyebileceğinizi veya iki parmağınızla ölçeklendirebileceğinizi fark edeceksiniz. Ölçeklendirme anisotropiktir, bu da ölçeklendirmenin yatay ve dikey yönlerde farklı olabileceği anlamına gelir. Bu, en boy oranını bozmasına karşın, bir yansıtma görüntüsü oluşturmak için bit eşlemi çevirmenize de olanak tanır. Bit eşlemi sıfır boyuta küçültebileceğinizi ve yok olduğunu da keşfedebilirsiniz. Üretim kodunda buna karşı koruma sağlamak istersiniz.
İki parmak döndürme
Bit Eşlem Döndürme sayfası, döndürme veya izotropik ölçeklendirme için iki parmağınızı kullanmanıza olanak tanır. Bit eşlem her zaman doğru en boy oranını korur. Hem döndürme hem de anizotropik ölçeklendirme için iki parmağın kullanılması çok iyi çalışmaz çünkü parmak hareketi her iki görev için de çok benzerdir.
Bu programdaki ilk büyük fark isabet testi mantığıdır. Önceki programlar, dokunma noktasının Contains bit eşleme karşılık gelen dönüştürülmüş dikdörtgen içinde olup olmadığını belirlemek için yöntemini SKRect kullandı. Ancak kullanıcı bit eşlemi işledikçe bit eşlem döndürülebilir ve SKRect döndürülmüş dikdörtgeni düzgün şekilde temsil edemez. Bu durumda isabet testi mantığının oldukça karmaşık analiz geometrisi uygulaması gerektiğinden korkabilirsiniz.
Bununla birlikte, bir kısayol kullanılabilir: Bir noktanın dönüştürülmüş dikdörtgenin sınırları içinde olup olmadığını belirlemek, dönüştürülen bir ters noktanın dönüştürülmemiş dikdörtgenin sınırları içinde olup olmadığını belirlemekle aynıdır. Bu çok daha kolay bir hesaplamadır ve mantık uygun Contains yöntemi kullanmaya devam edebilir:
public partial class BitmapRotationPage : ContentPage
{
···
// Touch information
Dictionary<long, SKPoint> touchDictionary = new Dictionary<long, SKPoint>();
···
void OnTouchEffectAction(object sender, TouchActionEventArgs args)
{
// Convert Xamarin.Forms point to pixels
Point pt = args.Location;
SKPoint point =
new SKPoint((float)(canvasView.CanvasSize.Width * pt.X / canvasView.Width),
(float)(canvasView.CanvasSize.Height * pt.Y / canvasView.Height));
switch (args.Type)
{
case TouchActionType.Pressed:
if (!touchDictionary.ContainsKey(args.Id))
{
// Invert the matrix
if (matrix.TryInvert(out SKMatrix inverseMatrix))
{
// Transform the point using the inverted matrix
SKPoint transformedPoint = inverseMatrix.MapPoint(point);
// Check if it's in the untransformed bitmap rectangle
SKRect rect = new SKRect(0, 0, bitmap.Width, bitmap.Height);
if (rect.Contains(transformedPoint))
{
touchDictionary.Add(args.Id, point);
}
}
}
break;
case TouchActionType.Moved:
if (touchDictionary.ContainsKey(args.Id))
{
// Single-finger drag
if (touchDictionary.Count == 1)
{
SKPoint prevPoint = touchDictionary[args.Id];
// Adjust the matrix for the new position
matrix.TransX += point.X - prevPoint.X;
matrix.TransY += point.Y - prevPoint.Y;
canvasView.InvalidateSurface();
}
// Double-finger rotate, scale, and drag
else if (touchDictionary.Count >= 2)
{
// Copy two dictionary keys into array
long[] keys = new long[touchDictionary.Count];
touchDictionary.Keys.CopyTo(keys, 0);
// Find index non-moving (pivot) finger
int pivotIndex = (keys[0] == args.Id) ? 1 : 0;
// Get the three points in the transform
SKPoint pivotPoint = touchDictionary[keys[pivotIndex]];
SKPoint prevPoint = touchDictionary[args.Id];
SKPoint newPoint = point;
// Calculate two vectors
SKPoint oldVector = prevPoint - pivotPoint;
SKPoint newVector = newPoint - pivotPoint;
// Find angles from pivot point to touch points
float oldAngle = (float)Math.Atan2(oldVector.Y, oldVector.X);
float newAngle = (float)Math.Atan2(newVector.Y, newVector.X);
// Calculate rotation matrix
float angle = newAngle - oldAngle;
SKMatrix touchMatrix = SKMatrix.MakeRotation(angle, pivotPoint.X, pivotPoint.Y);
// Effectively rotate the old vector
float magnitudeRatio = Magnitude(oldVector) / Magnitude(newVector);
oldVector.X = magnitudeRatio * newVector.X;
oldVector.Y = magnitudeRatio * newVector.Y;
// Isotropic scaling!
float scale = Magnitude(newVector) / Magnitude(oldVector);
if (!float.IsNaN(scale) && !float.IsInfinity(scale))
{
SKMatrix.PostConcat(ref touchMatrix,
SKMatrix.MakeScale(scale, scale, pivotPoint.X, pivotPoint.Y));
SKMatrix.PostConcat(ref matrix, touchMatrix);
canvasView.InvalidateSurface();
}
}
// Store the new point in the dictionary
touchDictionary[args.Id] = point;
}
break;
case TouchActionType.Released:
case TouchActionType.Cancelled:
if (touchDictionary.ContainsKey(args.Id))
{
touchDictionary.Remove(args.Id);
}
break;
}
}
float Magnitude(SKPoint point)
{
return (float)Math.Sqrt(Math.Pow(point.X, 2) + Math.Pow(point.Y, 2));
}
···
}
Olayın mantığı Moved önceki program gibi başlar. ve newVector adlı oldVector iki vektör, hareket eden parmağın önceki ve geçerli noktasına ve hareketsiz parmağın pivot noktasına göre hesaplanır. Ancak bu vektörlerin açıları belirlenir ve fark döndürme açısıdır.
Ölçeklendirme de söz konusu olabilir, bu nedenle eski vektör döndürme açısına göre döndürülür. İki vektörünün göreli büyüklüğü artık ölçeklendirme faktörüdür. Ölçeklendirmenin izotropik olması için yatay ve dikey ölçeklendirme için aynı scale değerin kullanıldığına dikkat edin. Alan matrix hem döndürme matrisi hem de ölçek matrisi tarafından ayarlanır.
Uygulamanızın tek bir bit eşlem (veya başka bir nesne) için dokunma işleme uygulaması gerekiyorsa, bu üç örnekten gelen kodu kendi uygulamanız için uyarlayabilirsiniz. Ancak birden çok bit eşlem için dokunma işleme uygulamanız gerekiyorsa, büyük olasılıkla bu dokunma işlemlerini diğer sınıflarda kapsüllemek istersiniz.
Dokunma İşlemlerini Kapsülleme
Dokunma düzenleme sayfası, tek bir bit eşlemin dokunma manipülasyonunu gösterir, ancak yukarıda gösterilen mantığın büyük bölümünü kapsülleyen diğer birkaç dosya kullanılır. Bu dosyaların TouchManipulationMode ilki, göreceğiniz kod tarafından uygulanan farklı dokunma işleme türlerini gösteren numaralandırmadır:
enum TouchManipulationMode
{
None,
PanOnly,
IsotropicScale, // includes panning
AnisotropicScale, // includes panning
ScaleRotate, // implies isotropic scaling
ScaleDualRotate // adds one-finger rotation
}
PanOnly , çeviri ile uygulanan tek parmaklı bir sürüklemedir. Sonraki tüm seçenekler de kaydırmayı içerir ancak iki parmağınızı içerir: IsotropicScale nesnenin yatay ve dikey yönlerde eşit şekilde ölçeklenmesine neden olan bir sıkıştırma işlemidir. AnisotropicScale eşit olmayan ölçeklendirmeye izin verir.
Seçenek ScaleRotate , iki parmakla ölçeklendirme ve döndürme içindir. Ölçeklendirme izotropiktir. Daha önce belirtildiği gibi, anisotropik ölçeklendirme ile iki parmak döndürme uygulamak sorunludur çünkü parmak hareketleri temelde aynıdır.
seçeneği ScaleDualRotate tek parmak döndürme ekler. Tek bir parmak nesneyi sürüklediğinde, sürüklenen nesne ilk olarak orta çevresinde döndürülür, böylece nesnenin merkezi sürükleme vektörünü hizalar.
TouchManipulationPage.xaml dosyası, numaralandırmanın TouchManipulationMode üyeleriyle birlikte bir Picker içerir:
<?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:skia="clr-namespace:SkiaSharp.Views.Forms;assembly=SkiaSharp.Views.Forms"
xmlns:tt="clr-namespace:TouchTracking"
xmlns:local="clr-namespace:SkiaSharpFormsDemos.Transforms"
x:Class="SkiaSharpFormsDemos.Transforms.TouchManipulationPage"
Title="Touch Manipulation">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Picker Title="Touch Mode"
Grid.Row="0"
SelectedIndexChanged="OnTouchModePickerSelectedIndexChanged">
<Picker.ItemsSource>
<x:Array Type="{x:Type local:TouchManipulationMode}">
<x:Static Member="local:TouchManipulationMode.None" />
<x:Static Member="local:TouchManipulationMode.PanOnly" />
<x:Static Member="local:TouchManipulationMode.IsotropicScale" />
<x:Static Member="local:TouchManipulationMode.AnisotropicScale" />
<x:Static Member="local:TouchManipulationMode.ScaleRotate" />
<x:Static Member="local:TouchManipulationMode.ScaleDualRotate" />
</x:Array>
</Picker.ItemsSource>
<Picker.SelectedIndex>
4
</Picker.SelectedIndex>
</Picker>
<Grid BackgroundColor="White"
Grid.Row="1">
<skia:SKCanvasView x:Name="canvasView"
PaintSurface="OnCanvasViewPaintSurface" />
<Grid.Effects>
<tt:TouchEffect Capture="True"
TouchAction="OnTouchEffectAction" />
</Grid.Effects>
</Grid>
</Grid>
</ContentPage>
Alta doğru, onu çevreleyen tek hücreye Grid bağlı bir TouchEffect ve şeklindedirSKCanvasView.
TouchManipulationPage.xaml.cs arka planda kod dosyası bir bitmap alana sahiptir, ancak türünde SKBitmapdeğildir. Türü ( TouchManipulationBitmap kısa süre içinde göreceğiniz bir sınıf):
public partial class TouchManipulationPage : ContentPage
{
TouchManipulationBitmap bitmap;
...
public TouchManipulationPage()
{
InitializeComponent();
string resourceID = "SkiaSharpFormsDemos.Media.MountainClimbers.jpg";
Assembly assembly = GetType().GetTypeInfo().Assembly;
using (Stream stream = assembly.GetManifestResourceStream(resourceID))
{
SKBitmap bitmap = SKBitmap.Decode(stream);
this.bitmap = new TouchManipulationBitmap(bitmap);
this.bitmap.TouchManager.Mode = TouchManipulationMode.ScaleRotate;
}
}
...
}
Oluşturucu, katıştırılmış bir TouchManipulationBitmap kaynaktan alınan bir oluşturucuya SKBitmap geçirerek bir nesne örneği oluşturur. Oluşturucu, nesnesinin Mode TouchManager özelliğini TouchManipulationBitmap numaralandırmanın bir üyesine TouchManipulationMode ayarlayarak son bulur.
işleyicisi SelectedIndexChanged Picker de bu Mode özelliği ayarlar:
public partial class TouchManipulationPage : ContentPage
{
...
void OnTouchModePickerSelectedIndexChanged(object sender, EventArgs args)
{
if (bitmap != null)
{
Picker picker = (Picker)sender;
bitmap.TouchManager.Mode = (TouchManipulationMode)picker.SelectedItem;
}
}
...
}
TouchAction XAML dosyasında örneği oluşturulan işleyicisi TouchEffect ve HitTest ProcessTouchEventiçinde TouchManipulationBitmap iki yöntemi çağırır:
public partial class TouchManipulationPage : ContentPage
{
...
List<long> touchIds = new List<long>();
...
void OnTouchEffectAction(object sender, TouchActionEventArgs args)
{
// Convert Xamarin.Forms point to pixels
Point pt = args.Location;
SKPoint point =
new SKPoint((float)(canvasView.CanvasSize.Width * pt.X / canvasView.Width),
(float)(canvasView.CanvasSize.Height * pt.Y / canvasView.Height));
switch (args.Type)
{
case TouchActionType.Pressed:
if (bitmap.HitTest(point))
{
touchIds.Add(args.Id);
bitmap.ProcessTouchEvent(args.Id, args.Type, point);
break;
}
break;
case TouchActionType.Moved:
if (touchIds.Contains(args.Id))
{
bitmap.ProcessTouchEvent(args.Id, args.Type, point);
canvasView.InvalidateSurface();
}
break;
case TouchActionType.Released:
case TouchActionType.Cancelled:
if (touchIds.Contains(args.Id))
{
bitmap.ProcessTouchEvent(args.Id, args.Type, point);
touchIds.Remove(args.Id);
canvasView.InvalidateSurface();
}
break;
}
}
...
}
HitTest Yöntem döndürürse (yani bit eşlem tarafından kaplanmış alan içinde bir parmağın ekrana dokunduğu anlamına gelir true ), dokunmatik kimlik koleksiyona TouchIds eklenir. Bu kimlik, parmak ekrandan kaldırana kadar bu parmak için dokunma olaylarının sırasını temsil eder. Bit eşleme birden çok parmak dokunduğunda touchIds koleksiyonda her parmak için bir dokunmatik kimlik bulunur.
İşleyici, TouchAction içinde TouchManipulationBitmapsınıfını ProcessTouchEvent da çağırır. Gerçek dokunma işleminin bir kısmının (ancak tümünün değil) gerçekleştiği yerdir.
TouchManipulationBitmap sınıfı, bit eşlem işlemek ve dokunma olaylarını işlemek için SKBitmap kod içeren bir sarmalayıcı sınıfıdır. Bir sınıftaki daha genelleştirilmiş kodla (kısa süre içinde TouchManipulationManager göreceksiniz) birlikte çalışır.
TouchManipulationBitmap Oluşturucu, öğesini kaydeder SKBitmap ve türün özelliği ve Matrix türünün TouchManipulationManager özelliği olan iki özelliğin TouchManager örneğini SKMatrixoluşturur:
class TouchManipulationBitmap
{
SKBitmap bitmap;
...
public TouchManipulationBitmap(SKBitmap bitmap)
{
this.bitmap = bitmap;
Matrix = SKMatrix.MakeIdentity();
TouchManager = new TouchManipulationManager
{
Mode = TouchManipulationMode.ScaleRotate
};
}
public TouchManipulationManager TouchManager { set; get; }
public SKMatrix Matrix { set; get; }
...
}
Bu Matrix özellik, tüm dokunma etkinliğinden kaynaklanan birikmiş dönüşümdür. Gördüğünüz gibi, her dokunma olayı bir matrise çözümlenir ve bu matris, özelliği tarafından Matrix depolanan değerle SKMatrix birleştirilir.
TouchManipulationBitmap nesnesi kendi yönteminde Paint kendini çizer. Bağımsız değişken bir SKCanvas nesnedir. Buna SKCanvas zaten bir dönüşüm uygulanmış olabilir, bu nedenle Paint yöntemi bit eşlemle ilişkili özelliği mevcut dönüşümle birleştirir Matrix ve tamamlandığında tuvali geri yükler:
class TouchManipulationBitmap
{
...
public void Paint(SKCanvas canvas)
{
canvas.Save();
SKMatrix matrix = Matrix;
canvas.Concat(ref matrix);
canvas.DrawBitmap(bitmap, 0, 0);
canvas.Restore();
}
...
}
Yöntemi, HitTest kullanıcı bit eşlem sınırları içinde bir noktada ekrana dokunursa döndürür true . Bu, Bit Eşlem Döndürme sayfasında daha önce gösterilen mantığı kullanır:
class TouchManipulationBitmap
{
...
public bool HitTest(SKPoint location)
{
// Invert the matrix
SKMatrix inverseMatrix;
if (Matrix.TryInvert(out inverseMatrix))
{
// Transform the point using the inverted matrix
SKPoint transformedPoint = inverseMatrix.MapPoint(location);
// Check if it's in the untransformed bitmap rectangle
SKRect rect = new SKRect(0, 0, bitmap.Width, bitmap.Height);
return rect.Contains(transformedPoint);
}
return false;
}
...
}
içindeki TouchManipulationBitmap ikinci genel yöntem: ProcessTouchEvent. Bu yöntem çağrıldığında, dokunma olayının bu bit eşleme ait olduğu önceden belirlenmiştir. yöntemi, yalnızca her parmağın TouchManipulationInfo önceki ve yeni noktası olan bir nesne sözlüğü tutar:
class TouchManipulationInfo
{
public SKPoint PreviousPoint { set; get; }
public SKPoint NewPoint { set; get; }
}
Sözlük ve yöntemin kendisi aşağıdadır ProcessTouchEvent :
class TouchManipulationBitmap
{
...
Dictionary<long, TouchManipulationInfo> touchDictionary =
new Dictionary<long, TouchManipulationInfo>();
...
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;
}
}
...
}
Moved ve Released olaylarında yöntemi öğesini çağırırManipulate. Bu zamanlarda, bir touchDictionary veya daha fazla TouchManipulationInfo nesne içerir. touchDictionary bir öğe içeriyorsa, ve NewPoint değerlerinin PreviousPoint eşit olmayabilecek ve bir parmağın hareketini temsil eden bir öğe olması muhtemeldir. Bit eşlem birden çok parmakla dokunuyorsa, sözlük birden fazla öğe içerir, ancak bu öğelerden yalnızca birinin farklı PreviousPoint ve NewPoint değerleri vardır. Geri kalanların tümü eşittir PreviousPoint ve NewPoint değerlerine sahiptir.
Bu önemlidir: yöntemi, Manipulate yalnızca bir parmağın hareketini işlediğini varsayabilir. Bu çağrı sırasında diğer parmaklardan hiçbiri hareket etmemektedir ve gerçekten hareket ediyorlarsa (büyük olasılıkla), bu hareketler için gelecek çağrılarda Manipulateişlenecektir.
yöntemi, Manipulate kolaylık sağlamak için önce sözlüğü bir diziye kopyalar. İlk iki girdi dışında herhangi bir şeyi yoksayar. İkiden fazla parmak bit eşlemi işlemeye çalışırsa, diğerleri yoksayılır. Manipulate , son üyesidir TouchManipulationBitmap:
class TouchManipulationBitmap
{
...
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;
}
}
Bir parmağınızla bit eşlem işleniyorsa, Manipulate nesnesinin OneFingerManipulate TouchManipulationManager yöntemini çağırır. İki parmak için öğesini çağırır TwoFingerManipulate. Bu yöntemlerin bağımsız değişkenleri aynıdır: prevPoint ve newPoint bağımsız değişkenleri hareket eden parmağı temsil eder. pivotPoint Ancak bağımsız değişken iki çağrı için farklıdır:
Tek parmakla işleme için bit pivotPoint eşlem merkezidir. Bu, tek parmak döndürmeye izin vermek içindir. İki parmaklı işleme için olay yalnızca bir parmağın hareketini gösterir, böylece pivotPoint hareket etmeyen parmaktır.
Her iki durumda da yöntemin TouchManipulationManager bit eşlemi işlemek için kullanılan geçerli Matrix özellik ile birleşerek TouchManipulationPage bir SKMatrix değer döndürür.
TouchManipulationManager genelleştirilmiştir ve dışında TouchManipulationModebaşka dosya kullanmaz. Bu sınıfı kendi uygulamalarınızda değişiklik yapmadan kullanabilirsiniz. türünde TouchManipulationModetek bir özellik tanımlar:
class TouchManipulationManager
{
public TouchManipulationMode Mode { set; get; }
...
}
Ancak bu seçenekten kaçınmak AnisotropicScale isteyebilirsiniz. Bu seçenekle bit eşlemi işlemek çok kolaydır, böylece ölçeklendirme faktörlerinden biri sıfır olur. Bu da bit eşleminin gözden kaybolmasını, asla geri dönmemesini sağlar. Anisotropik ölçeklendirmeye gerçekten ihtiyacınız varsa, istenmeyen sonuçlardan kaçınmak için mantığı geliştirmek istersiniz.
TouchManipulationManager vektörleri kullanır, ancak SkiaSharp'ta yapı olmadığından SKVector bunun SKPoint yerine kullanılır. SKPoint çıkarma işlecini destekler ve sonuç vektör olarak işlenebilir. Eklenmesi gereken vektöre özgü tek mantık bir Magnitude hesaplamadır:
class TouchManipulationManager
{
...
float Magnitude(SKPoint point)
{
return (float)Math.Sqrt(Math.Pow(point.X, 2) + Math.Pow(point.Y, 2));
}
}
Döndürme seçildiğinde, önce hem tek parmak hem de iki parmaklı işleme yöntemleri döndürmeyi işler. Herhangi bir döndürme algılanırsa, döndürme bileşeni etkili bir şekilde kaldırılır. Geriye kalanlar, kaydırma ve ölçeklendirme olarak yorumlanır.
Yöntemi aşağıdadır OneFingerManipulate . Tek parmak döndürme etkinleştirilmediyse mantık basittir; yalnızca çeviriye tam olarak karşılık gelen adlı delta bir vektör oluşturmak için önceki noktayı ve yeni noktayı kullanır. Tek parmak döndürme etkinleştirildiğinde yöntem, döndürme matrisi oluşturmak için pivot noktasından (bit eşlem merkezinin merkezi) önceki noktaya ve yeni noktadan açıları kullanır:
class TouchManipulationManager
{
public TouchManipulationMode Mode { set; get; }
public SKMatrix OneFingerManipulate(SKPoint prevPoint, SKPoint newPoint, SKPoint pivotPoint)
{
if (Mode == TouchManipulationMode.None)
{
return SKMatrix.MakeIdentity();
}
SKMatrix touchMatrix = SKMatrix.MakeIdentity();
SKPoint delta = newPoint - prevPoint;
if (Mode == TouchManipulationMode.ScaleDualRotate) // One-finger rotation
{
SKPoint oldVector = prevPoint - pivotPoint;
SKPoint newVector = newPoint - pivotPoint;
// Avoid rotation if fingers are too close to center
if (Magnitude(newVector) > 25 && Magnitude(oldVector) > 25)
{
float prevAngle = (float)Math.Atan2(oldVector.Y, oldVector.X);
float newAngle = (float)Math.Atan2(newVector.Y, newVector.X);
// Calculate rotation matrix
float angle = newAngle - prevAngle;
touchMatrix = SKMatrix.MakeRotation(angle, pivotPoint.X, pivotPoint.Y);
// Effectively rotate the old vector
float magnitudeRatio = Magnitude(oldVector) / Magnitude(newVector);
oldVector.X = magnitudeRatio * newVector.X;
oldVector.Y = magnitudeRatio * newVector.Y;
// Recalculate delta
delta = newVector - oldVector;
}
}
// Multiply the rotation matrix by a translation matrix
SKMatrix.PostConcat(ref touchMatrix, SKMatrix.MakeTranslation(delta.X, delta.Y));
return touchMatrix;
}
...
}
yönteminde TwoFingerManipulate pivot noktası, bu dokunma olayında hareket etmeyen parmağın konumudur. Döndürme, tek parmak döndürmeye çok benzer ve ardından adlı oldVector vektör (önceki noktaya göre) döndürme için ayarlanır. Kalan hareket ölçeklendirme olarak yorumlanır:
class TouchManipulationManager
{
...
public SKMatrix TwoFingerManipulate(SKPoint prevPoint, SKPoint newPoint, SKPoint pivotPoint)
{
SKMatrix touchMatrix = SKMatrix.MakeIdentity();
SKPoint oldVector = prevPoint - pivotPoint;
SKPoint newVector = newPoint - pivotPoint;
if (Mode == TouchManipulationMode.ScaleRotate ||
Mode == TouchManipulationMode.ScaleDualRotate)
{
// Find angles from pivot point to touch points
float oldAngle = (float)Math.Atan2(oldVector.Y, oldVector.X);
float newAngle = (float)Math.Atan2(newVector.Y, newVector.X);
// Calculate rotation matrix
float angle = newAngle - oldAngle;
touchMatrix = SKMatrix.MakeRotation(angle, pivotPoint.X, pivotPoint.Y);
// Effectively rotate the old vector
float magnitudeRatio = Magnitude(oldVector) / Magnitude(newVector);
oldVector.X = magnitudeRatio * newVector.X;
oldVector.Y = magnitudeRatio * newVector.Y;
}
float scaleX = 1;
float scaleY = 1;
if (Mode == TouchManipulationMode.AnisotropicScale)
{
scaleX = newVector.X / oldVector.X;
scaleY = newVector.Y / oldVector.Y;
}
else if (Mode == TouchManipulationMode.IsotropicScale ||
Mode == TouchManipulationMode.ScaleRotate ||
Mode == TouchManipulationMode.ScaleDualRotate)
{
scaleX = scaleY = Magnitude(newVector) / Magnitude(oldVector);
}
if (!float.IsNaN(scaleX) && !float.IsInfinity(scaleX) &&
!float.IsNaN(scaleY) && !float.IsInfinity(scaleY))
{
SKMatrix.PostConcat(ref touchMatrix,
SKMatrix.MakeScale(scaleX, scaleY, pivotPoint.X, pivotPoint.Y));
}
return touchMatrix;
}
...
}
Bu yöntemde açık çeviri olmadığını fark edeceksiniz. Ancak, hem MakeRotation ve MakeScale yöntemleri özet noktasını temel alır ve örtük çeviri içerir. Bit eşlem üzerinde iki parmağınızı kullanıyor ve aynı yönde sürüklerseniz, TouchManipulation iki parmak arasında değişen bir dizi dokunma olayı elde edersiniz. Her parmak diğerine göre hareket ettikçe ölçeklendirme veya döndürme sonuçları, ancak diğer parmağın hareketi tarafından olumsuzlanır ve sonuç çeviri olur.
Dokunma İşleme sayfasının kalan tek bölümü arka planda kod dosyasındaki TouchManipulationPage işleyicidirPaintSurface. Bu, birikmiş dokunma etkinliğini temsil eden matrisi uygulayan yöntemini TouchManipulationBitmapçağırırPaint:
public partial class TouchManipulationPage : ContentPage
{
...
MatrixDisplay matrixDisplay = new MatrixDisplay();
...
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
// Display the bitmap
bitmap.Paint(canvas);
// Display the matrix in the lower-right corner
SKSize matrixSize = matrixDisplay.Measure(bitmap.Matrix);
matrixDisplay.Paint(canvas, bitmap.Matrix,
new SKPoint(info.Width - matrixSize.Width,
info.Height - matrixSize.Height));
}
}
İşleyici, PaintSurface birikmiş dokunma matrisini gösteren bir MatrixDisplay nesne görüntüleyerek son bulur:
Birden Çok Bit Eşlemi Düzenleme
ve TouchManipulationManager gibi TouchManipulationBitmap sınıflarda dokunma işleme kodunu yalıtma avantajlarından biri, kullanıcının birden çok bit eşlemi işlemesine olanak tanıyan bir programda bu sınıfları yeniden kullanabilmektir.
Bit Eşlem Dağılım Görünümü sayfası bunun nasıl yapıldığını gösterir. sınıfı, BitmapScatterPage türünde TouchManipulationBitmapbir alan tanımlamak yerine bit eşlem nesnelerini tanımlarList:
public partial class BitmapScatterViewPage : ContentPage
{
List<TouchManipulationBitmap> bitmapCollection =
new List<TouchManipulationBitmap>();
...
public BitmapScatterViewPage()
{
InitializeComponent();
// Load in all the available bitmaps
Assembly assembly = GetType().GetTypeInfo().Assembly;
string[] resourceIDs = assembly.GetManifestResourceNames();
SKPoint position = new SKPoint();
foreach (string resourceID in resourceIDs)
{
if (resourceID.EndsWith(".png") ||
resourceID.EndsWith(".jpg"))
{
using (Stream stream = assembly.GetManifestResourceStream(resourceID))
{
SKBitmap bitmap = SKBitmap.Decode(stream);
bitmapCollection.Add(new TouchManipulationBitmap(bitmap)
{
Matrix = SKMatrix.MakeTranslation(position.X, position.Y),
});
position.X += 100;
position.Y += 100;
}
}
}
}
...
}
Oluşturucu, ekli kaynaklar olarak kullanılabilen bit eşlemlerin tümüne yüklenir ve bunları öğesine bitmapCollectionekler. Özelliğin her TouchManipulationBitmap nesnede başlatıldığına Matrix dikkat edin, bu nedenle her bit eşlemin sol üst köşeleri 100 piksel kaydırılır.
Sayfanın BitmapScatterView birden çok bit eşlem için dokunma olaylarını da işlemesi gerekir. Bu program, şu anda manipüle edilmiş TouchManipulationBitmap nesnelerin dokunmatik kimliklerini tanımlamak List yerine bir sözlük gerektirir:
public partial class BitmapScatterViewPage : ContentPage
{
...
Dictionary<long, TouchManipulationBitmap> bitmapDictionary =
new Dictionary<long, TouchManipulationBitmap>();
...
void OnTouchEffectAction(object sender, TouchActionEventArgs args)
{
// Convert Xamarin.Forms point to pixels
Point pt = args.Location;
SKPoint point =
new SKPoint((float)(canvasView.CanvasSize.Width * pt.X / canvasView.Width),
(float)(canvasView.CanvasSize.Height * pt.Y / canvasView.Height));
switch (args.Type)
{
case TouchActionType.Pressed:
for (int i = bitmapCollection.Count - 1; i >= 0; i--)
{
TouchManipulationBitmap bitmap = bitmapCollection[i];
if (bitmap.HitTest(point))
{
// Move bitmap to end of collection
bitmapCollection.Remove(bitmap);
bitmapCollection.Add(bitmap);
// Do the touch processing
bitmapDictionary.Add(args.Id, bitmap);
bitmap.ProcessTouchEvent(args.Id, args.Type, point);
canvasView.InvalidateSurface();
break;
}
}
break;
case TouchActionType.Moved:
if (bitmapDictionary.ContainsKey(args.Id))
{
TouchManipulationBitmap bitmap = bitmapDictionary[args.Id];
bitmap.ProcessTouchEvent(args.Id, args.Type, point);
canvasView.InvalidateSurface();
}
break;
case TouchActionType.Released:
case TouchActionType.Cancelled:
if (bitmapDictionary.ContainsKey(args.Id))
{
TouchManipulationBitmap bitmap = bitmapDictionary[args.Id];
bitmap.ProcessTouchEvent(args.Id, args.Type, point);
bitmapDictionary.Remove(args.Id);
canvasView.InvalidateSurface();
}
break;
}
}
...
}
Mantığın Pressed tersten nasıl döngüye geçtiğine bitmapCollection dikkat edin. Bit eşlemler genellikle birbiriyle çakışıyor. Koleksiyonun sonraki bölümlerindeki bit eşlemler, koleksiyonun önceki bölümlerindeki bit eşlemlerin üzerinde görsel olarak bulunur. Parmağın altında ekrana basan birden çok bit eşlem varsa, en üstteki bu parmak tarafından yönlendirilen bit eşlem olmalıdır.
Ayrıca mantığın Pressed bu bit eşlemi koleksiyonun sonuna taşır ve böylece görsel olarak diğer bit eşlemlerin yığınının en üstüne taşınır.
Moved ve olaylarında işleyici, TouchAction önceki programda TouchManipulationBitmap olduğu gibi içinde yöntemini çağırır ProcessingTouchEvent Released.
Son olarak, PaintSurface işleyici her TouchManipulationBitmap nesnenin Paint yöntemini çağırır:
public partial class BitmapScatterViewPage : ContentPage
{
...
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKCanvas canvas = args.Surface.Canvas;
canvas.Clear();
foreach (TouchManipulationBitmap bitmap in bitmapCollection)
{
bitmap.Paint(canvas);
}
}
}
Kod, koleksiyonda döngüler düzenler ve koleksiyonun başından sonuna kadar bit eşlem yığınını görüntüler:
Tek Parmakla Ölçeklendirme
Ölçeklendirme işlemi genellikle iki parmağınızla sıkıştırma hareketi gerektirir. Ancak, bir bit eşlem köşelerini hareket ettirerek tek parmakla ölçeklendirme uygulamak mümkündür.
Bu, Tek ParmakLı Köşe Ölçeği sayfasında gösterilmiştir. Bu örnek, sınıfında uygulanandan biraz farklı bir ölçeklendirme türü kullandığından TouchManipulationManager , bu sınıfı veya TouchManipulationBitmap sınıfı kullanmaz. Bunun yerine, tüm dokunma mantığı arka planda kod dosyasındadır. Her seferinde yalnızca bir parmağı izlediğinden ve ekrana dokunabilecek ikincil parmaklarını yoksaydığından bu, normalden biraz daha basit bir mantıktır.
SingleFingerCornerScale.xaml sayfası, sınıfın örneğini SKCanvasView oluşturur ve dokunma olaylarını izlemek için bir TouchEffect nesne oluşturur:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:skia="clr-namespace:SkiaSharp.Views.Forms;assembly=SkiaSharp.Views.Forms"
xmlns:tt="clr-namespace:TouchTracking"
x:Class="SkiaSharpFormsDemos.Transforms.SingleFingerCornerScalePage"
Title="Single Finger Corner Scale">
<Grid BackgroundColor="White"
Grid.Row="1">
<skia:SKCanvasView x:Name="canvasView"
PaintSurface="OnCanvasViewPaintSurface" />
<Grid.Effects>
<tt:TouchEffect Capture="True"
TouchAction="OnTouchEffectAction" />
</Grid.Effects>
</Grid>
</ContentPage>
SingleFingerCornerScalePage.xaml.cs dosyası Media dizininden bir bit eşlem kaynağı yükler ve alan olarak tanımlanan bir SKMatrix nesne kullanarak görüntüler:
public partial class SingleFingerCornerScalePage : ContentPage
{
SKBitmap bitmap;
SKMatrix currentMatrix = SKMatrix.MakeIdentity();
···
public SingleFingerCornerScalePage()
{
InitializeComponent();
string resourceID = "SkiaSharpFormsDemos.Media.SeatedMonkey.jpg";
Assembly assembly = GetType().GetTypeInfo().Assembly;
using (Stream stream = assembly.GetManifestResourceStream(resourceID))
{
bitmap = SKBitmap.Decode(stream);
}
}
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
canvas.SetMatrix(currentMatrix);
canvas.DrawBitmap(bitmap, 0, 0);
}
···
}
Bu SKMatrix nesne, aşağıda gösterilen dokunma mantığıyla değiştirilir.
Arka planda kod dosyasının geri kalanı olay işleyicisidir TouchEffect . Bu, parmağın geçerli konumunu bir SKPoint değere dönüştürerek başlar. eylem türü için Pressed işleyici, başka bir parmağın ekrana dokunmadığını ve parmağın bit eşlem sınırları içinde olduğunu denetler.
Kodun önemli bölümü, yöntemine yapılan iki çağrıyı Math.Pow içeren bir if deyimdir. Bu matematik, parmak konumunun bit eşlemi dolduran üç noktanın dışında olup olmadığını denetler. Öyleyse, bu bir ölçeklendirme işlemidir. Parmak bit eşlem köşelerinden birinin yakınındadır ve karşı köşe olan bir özet noktası belirlenir. Parmak bu üç noktanın içindeyse, bu normal bir kaydırma işlemidir:
public partial class SingleFingerCornerScalePage : ContentPage
{
SKBitmap bitmap;
SKMatrix currentMatrix = SKMatrix.MakeIdentity();
// Information for translating and scaling
long? touchId = null;
SKPoint pressedLocation;
SKMatrix pressedMatrix;
// Information for scaling
bool isScaling;
SKPoint pivotPoint;
···
void OnTouchEffectAction(object sender, TouchActionEventArgs args)
{
// Convert Xamarin.Forms point to pixels
Point pt = args.Location;
SKPoint point =
new SKPoint((float)(canvasView.CanvasSize.Width * pt.X / canvasView.Width),
(float)(canvasView.CanvasSize.Height * pt.Y / canvasView.Height));
switch (args.Type)
{
case TouchActionType.Pressed:
// Track only one finger
if (touchId.HasValue)
return;
// Check if the finger is within the boundaries of the bitmap
SKRect rect = new SKRect(0, 0, bitmap.Width, bitmap.Height);
rect = currentMatrix.MapRect(rect);
if (!rect.Contains(point))
return;
// First assume there will be no scaling
isScaling = false;
// If touch is outside interior ellipse, make this a scaling operation
if (Math.Pow((point.X - rect.MidX) / (rect.Width / 2), 2) +
Math.Pow((point.Y - rect.MidY) / (rect.Height / 2), 2) > 1)
{
isScaling = true;
float xPivot = point.X < rect.MidX ? rect.Right : rect.Left;
float yPivot = point.Y < rect.MidY ? rect.Bottom : rect.Top;
pivotPoint = new SKPoint(xPivot, yPivot);
}
// Common for either pan or scale
touchId = args.Id;
pressedLocation = point;
pressedMatrix = currentMatrix;
break;
case TouchActionType.Moved:
if (!touchId.HasValue || args.Id != touchId.Value)
return;
SKMatrix matrix = SKMatrix.MakeIdentity();
// Translating
if (!isScaling)
{
SKPoint delta = point - pressedLocation;
matrix = SKMatrix.MakeTranslation(delta.X, delta.Y);
}
// Scaling
else
{
float scaleX = (point.X - pivotPoint.X) / (pressedLocation.X - pivotPoint.X);
float scaleY = (point.Y - pivotPoint.Y) / (pressedLocation.Y - pivotPoint.Y);
matrix = SKMatrix.MakeScale(scaleX, scaleY, pivotPoint.X, pivotPoint.Y);
}
// Concatenate the matrices
SKMatrix.PreConcat(ref matrix, pressedMatrix);
currentMatrix = matrix;
canvasView.InvalidateSurface();
break;
case TouchActionType.Released:
case TouchActionType.Cancelled:
touchId = null;
break;
}
}
}
Eylem Moved türü, parmağın ekrana bastığı zamandan bu zamana kadar dokunma etkinliğine karşılık gelen matrisi hesaplar. Bu matrisi, parmağın bit eşlem tuşuna ilk bastığı anda matrisin etkin olduğu birleştirir. Ölçeklendirme işlemi her zaman parmağın dokunduğu köşenin karşısındaki köşeye göredir.
Küçük veya dikdörtgen bit eşlemler için, iç üç nokta bit eşlemin çoğunu kaplayabilir ve bit eşlemi ölçeklendirmek için köşelerde küçük alanlar bırakabilir. Biraz farklı bir yaklaşım tercih edebilirsiniz. Bu durumda, şu kodla ayarlandığı isScaling true bloğun tamamını if değiştirebilirsiniz:
float halfHeight = rect.Height / 2;
float halfWidth = rect.Width / 2;
// Top half of bitmap
if (point.Y < rect.MidY)
{
float yRelative = (point.Y - rect.Top) / halfHeight;
// Upper-left corner
if (point.X < rect.MidX - yRelative * halfWidth)
{
isScaling = true;
pivotPoint = new SKPoint(rect.Right, rect.Bottom);
}
// Upper-right corner
else if (point.X > rect.MidX + yRelative * halfWidth)
{
isScaling = true;
pivotPoint = new SKPoint(rect.Left, rect.Bottom);
}
}
// Bottom half of bitmap
else
{
float yRelative = (point.Y - rect.MidY) / halfHeight;
// Lower-left corner
if (point.X < rect.Left + yRelative * halfWidth)
{
isScaling = true;
pivotPoint = new SKPoint(rect.Right, rect.Top);
}
// Lower-right corner
else if (point.X > rect.Right - yRelative * halfWidth)
{
isScaling = true;
pivotPoint = new SKPoint(rect.Left, rect.Top);
}
}
Bu kod, bit eşlem alanını etkin bir şekilde iç baklava şekline ve köşelerde dört üçgene böler. Bu, köşelerdeki çok daha büyük alanların bit eşlemi alıp ölçeklendirmesine olanak tanır.

