Windows Phone 7 - Image加上Zoom in/out功能
最近不小心迷上用手機看漫畫這件事,所以想說寫一個自己的漫畫程式,這樣可以選擇自己覺得
取得圖像資料比較快且穩定的網站。不過在撰寫過程裡,對於Image的處理卻是最為困難的地方。
不過還好,在網路上找到了這一篇資料<Windows Phone 7: correct pinch zoom in Silverlight>,裡面
針對圖像的處理提供了最完整的解釋,往下針對該篇文章提到的部分,加以說明。
〉定義XAML中Image的呈現樣式:
1: <Grid x:Name="LayoutRoot" Background="Transparent">
2: <Image x:Name="ImgZoom"
3: Source="sample.jpg"
4: Stretch="UniformToFill"
5: RenderTransformOrigin="0,0">
6: <toolkit:GestureService.GestureListener>
7: <!-- 定義Pinch系列的事件,DoubleTap回到原圖,Drag圖像移動-->
8: <toolkit:GestureListener
9: PinchStarted="OnPinchStarted"
10: PinchDelta="OnPinchDelta"
11: DragDelta="OnDragDelta"
12: DoubleTap="OnDoubleTap"/>
13: </toolkit:GestureService.GestureListener>
14: <Image.RenderTransform>
15: <!-- 定義CompositeTransform支援Scale與Translate的特效 -->
16: <CompositeTransform
17: ScaleX="1" ScaleY="1"
18: TranslateX="0" TranslateY="0"/>
19: </Image.RenderTransform>
20: </Image>
21: </Grid>
如果針對GestureService不是非常熟悉的地方,可以參考<Windows Phone 7 - 淺談手勢(Gestures)運作>的說明。
上述程式主要針對Pinch、Drag與DoubleTap三種手勢進行事件的定義,並且實作了二種RenderTransform支援
Image圖像內容的Scale、Translate二種特效。
‧Image:
在Image控件裡幾個屬性,在使用RenderTransform時需要注意的,如下:
屬性 | 說明 |
ScaleX | 屬於ScaleTransform的重要屬性。 控制物件的Scale X比例(横向擴大),一種二維的X-Y坐標系統。 |
SacleY | 屬於ScaleTransform的重要屬性。 控制物件的Scale Y比例(直向擴大),一種二維的X-Y坐標系統。 |
TranslateX | 屬於TranslateTransform的重要屬性。 控制物件的TranslatesX比例(横向移動),一種二維的X-Y坐標系統。 |
TranslateY | 屬於TranslateTransform的重要屬性。 控制物件的TranslatesX比例(直向移動),一種二維的X-Y坐標系統。 |
在處理RenderTransform裡還有包括:RotateTransform、SkewTransform、TranslateTransform、MatrixTransform等,
這些Transform可透過TransformGroup組合起來一同管理,讓套用的物件可同時具有這些效果。
定義好了圖像的基本效果與XAML後,接下來便來看看,實際如何處理:Pinch、Drag與DoubleTap的手勢事件;
〉處理Pinch事件(PinchStarted與PinchDelta):
Pinch代表運用二個手指按在螢幕上,進行二指間的拉近與拉遠移動所觸發的事件。
在<Windows Phone 7 - 淺談手勢(Gestures)運作>提到二個事件的定義,如下:
PinchStarted:代表Pinched事件的開始,即是二個手指剛觸控於螢幕時;
PinchDelta:代表Pinched事件執行的過程,即時二個手指間距移動時觸發的事件;
(1) 在PinchStarted記下目前圖像控件的二個手指點擊的位置與設定Scale比例為1;
1: /// <summary>
2: /// Initializes the zooming operation
3: /// </summary>
4: private void OnPinchStarted(object sender, PinchStartedGestureEventArgs e)
5: {
6: //取得目前二個手指在圖像控件的座置,並且設定目前Scale比率為1
7: _oldFinger1 = e.GetPosition(ImgZoom, 0);
8: _oldFinger2 = e.GetPosition(ImgZoom, 1);
9:
10: //設定為1,代表以目前圖像的狀態進行Scale的調整
11: _oldScaleFactor = 1;
12: }
(2) 在PinchDelta進行一系列移動時的圖像Scale與座標移動的計算;
1: /// <summary>
2: /// Computes the scaling and translation to correctly zoom around your fingers.
3: /// </summary>
4: private void OnPinchDelta(object sender, PinchGestureEventArgs e)
5: {
6: //取得移動的距離比率 / 目前的Scale比率,取得對應的Scale比率
7: var scaleFactor = e.DistanceRatio / _oldScaleFactor;
8: //檢查新的Scale比率是否合法
9: if (!IsScaleValid(scaleFactor))
10: return;
11:
12: //取得新的二個手指座標
13: var currentFinger1 = e.GetPosition(ImgZoom, 0);
14: var currentFinger2 = e.GetPosition(ImgZoom, 1);
15:
16: //根據目前的二手指座標、上一個二手指座標、圖像的座標、新Scale比率,重新建立一個Point
17: var translationDelta = GetTranslationDelta(
18: currentFinger1,
19: currentFinger2,
20: _oldFinger1,
21: _oldFinger2,
22: ImagePosition,
23: scaleFactor);
24:
25: //更新儲存現在的手指座標與Scale比率
26: _oldFinger1 = currentFinger1;
27: _oldFinger2 = currentFinger2;
28: _oldScaleFactor = e.DistanceRatio;
29:
30: //更新圖像Scale比率與圖像座標
31: UpdateImageScale(scaleFactor);
32: UpdateImagePosition(translationDelta);
33: }
透過在該PinchDelta擷取到的(1) 移動距離搭配目前的Scale比率,計算出的Scale比率;並且因為比率的改變,也要讓
(2) 圖像放大的位置是二指間的圖像區域,因此再透過二指間的座標與Scale比率等參數,計算新的座標來配合Scale
的變動;
在上述的程式裡有二個事件參數,它們均繼承GestureEventArgs加上了Pinch的部分:
‧PinchStartedGestureEventArgs:定義了二個觸控點在啟動時需要擷取到的基本資料;
屬性/方法 | 說明 |
GetPosition | 回傳在相對應UIElement中二個觸控點(0或1)任一個的座標位置。 |
Distance | 二個觸控點之間的距離。 |
‧PinchGestureEventArgs:定義在Pinch過程中相對於距離、角度的處理值;
屬性/方法 | 說明 |
DistanceRatio | 回傳接觸點之間的當前距離/原來的接觸點之間的距離的比值。 |
TotalAngleDelta | 回傳當前的觸摸位置和原始觸摸位置之間的角度差。 |
〉處理Drag事件:
Drag負責一個手指按在螢幕上,移動時連著帶動有監聽Drag事件的物件跟著移動。此程式增加Drag的機制讓圖像
可以跟著Drag的新座標進行移動的效果。如下程式:
1: /// <summary>
2: /// Moves the image around following your finger.
3: /// </summary>
4: private void OnDragDelta(object sender, DragDeltaGestureEventArgs e)
5: {
6: //取得移動的新座標
7: var translationDelta = new Point(e.HorizontalChange, e.VerticalChange);
8:
9: //驗證是否超過Scale後可移動範圍、目前圖像移動與實體大小的差距
10: if (IsDragValid(1, translationDelta))
11: UpdateImagePosition(translationDelta);
12: }
〉處理DoubleTab事件:
DoubleTab事件發生連續快速二次Tap(輕敲)動作於UIElement。此程式增加了在圖像上進行DoubleTab之後,將圖像復原
成原來的樣子。如下程式:
1: /// <summary>
2: /// Resets the image scaling and position
3: /// </summary>
4: private void OnDoubleTap(object sender, Microsoft.Phone.Controls.GestureEventArgs e)
5: {
6: TotalImageScale = 1;
7: ImagePosition = new Point(0, 0);
8: //呼叫ApplyScale()與ApplyPosition()根據TotalImageScale與ImagePosition
9: //復原圖像的原始比例
10: ApplyScale();
11: ApplyPosition();
12: }
〉相關的副程式:
(1) 取得隨著Scale與座標改變圖像的新座標:GetTranslationDelta();
1: /// <summary>
2: /// Computes the translation needed to keep the image centered between your fingers.
3: /// </summary>
4: private Point GetTranslationDelta(
5: Point currentFinger1, Point currentFinger2,
6: Point oldFinger1, Point oldFinger2,
7: Point currentPosition, double scaleFactor)
8: {
9: var newPos1 = new Point(
10: currentFinger1.X + (currentPosition.X - oldFinger1.X) * scaleFactor,
11: currentFinger1.Y + (currentPosition.Y - oldFinger1.Y) * scaleFactor);
12:
13: var newPos2 = new Point(