UWP開發入門(十五)——在FlipView中通過手勢操作圖片


  本篇的最終目的,是模擬系統的照片APP可以左右滑動,縮放圖片的操作。在實現的過程中,我們會逐步分析UWP編寫UI的一些思路和技巧。

  首先我們先實現一個橫向的可以瀏覽圖片的功能,也是大部分APP中的實現。最簡單的方式是使用FlipView,再將FlipViewItemTemplate設置成Image。大體代碼如下:

    <FlipView ItemsSource="{Binding Photos,Mode=OneTime}">
        <FlipView.ItemTemplate>
            <DataTemplate>
                <Image Source="{Binding ImageUri,Mode=OneTime}"></Image>
            </DataTemplate>
        </FlipView.ItemTemplate>
    </FlipView>

  上述代碼很簡單,同時效果也非常好。問題圖片如果縱橫比例較大,比如長微博那種豎長的圖片在手機上就沒法方便地閱讀了。這時候我們需要能夠縮放和拖動圖片,對圖片的局部進行觀察。請注意這是一個強需求!特別是打開一張柳岩照片卻尷尬地發現無法縮放時的強需求!

  分析一下我們遇到的問題,需要支持手勢對圖片的縮放和移動。UWP里一般通過UIElement類型的Manipulation相關事件來處理。接下來我們來創建一個支持手勢的控件。

  一開始的想法是繼承Image來實現一個支持縮放的ScalableImage,但不幸的是Image類是不允許繼承的sealed類型。那我們索性搞大一點,實現一個ScalableGrid,該Grid允許將內部的元素通過Manipulation進行操作。

    public class ScalableGrid : Grid
    {
        private TransformGroup transformGroup;
        private ScaleTransform scaleTransform;
        private TranslateTransform translateTransform;

        public ScalableGrid()
        {
            this.scaleTransform = new ScaleTransform();
            this.translateTransform = new TranslateTransform();
            this.transformGroup = new TransformGroup();
            this.transformGroup.Children.Add(scaleTransform);
            this.transformGroup.Children.Add(translateTransform);
            this.RenderTransform = transformGroup;

            this.ManipulationMode = ManipulationModes.System | ManipulationModes.Scale;
            this.ManipulationDelta += ScalableGrid_ManipulationDelta;
            this.Loaded += ScalableGrid_Loaded;
            this.SizeChanged += (a, b) =>
            {
                this.scaleTransform.CenterX = this.ActualWidth / 2;
                this.scaleTransform.CenterY = this.ActualHeight / 2;
            };
            this.DoubleTapped += ScalableGrid_DoubleTapped;
        }

        private void ScalableGrid_DoubleTapped(object sender, DoubleTappedRoutedEventArgs e)
        {
            scaleTransform.ScaleX = scaleTransform.ScaleY = 1;
            this.translateTransform.X = 0;
            this.translateTransform.Y = 0;
            this.ManipulationMode = ManipulationModes.System | ManipulationModes.Scale;
        }

        private void ScalableGrid_Loaded(object sender, Windows.UI.Xaml.RoutedEventArgs e)
        {
            this.Loaded -= ScalableGrid_Loaded;
            scaleTransform.CenterX = this.ActualWidth / 2;
            scaleTransform.CenterY = this.ActualHeight / 2;
        }

        private void ScalableGrid_ManipulationDelta(object sender, Windows.UI.Xaml.Input.ManipulationDeltaRoutedEventArgs e)
        {
            if (scaleTransform.ScaleX == 1 && scaleTransform.ScaleY == 1)
            {
                this.ManipulationMode = ManipulationModes.System | ManipulationModes.Scale;
            }
            else
            {
                this.ManipulationMode = ManipulationModes.TranslateX | ManipulationModes.TranslateY | ManipulationModes.Scale | ManipulationModes.TranslateInertia;
            }

            scaleTransform.ScaleX *= e.Delta.Scale;
            scaleTransform.ScaleY *= e.Delta.Scale;
            if (scaleTransform.ScaleY < 1)
            {
                scaleTransform.ScaleX = scaleTransform.ScaleY = 1;
            }

            translateTransform.X += e.Delta.Translation.X;
            translateTransform.Y += e.Delta.Translation.Y;
            StopWhenTranslateToEdge();
        }

  TranslateTransformScaleTransform分別對應平移操作和縮放操作。

this.ManipulationMode = ManipulationModes.System | ManipulationModes.Scale;

  ManipulationMode在構造函數中,初始設置支持SystemScale,沒有TranslateXTranslateY是因為初始打開的時候不希望可以有平移操作,只有縮放后,才根據放大的具體情況放開對平移的支持。

 this.SizeChanged += (a, b) =>
            {
                this.scaleTransform.CenterX = this.ActualWidth / 2;
                this.scaleTransform.CenterY = this.ActualHeight / 2;
            };

  SizeChanged事件是為了在窗口大小變化,比如桌面縮放窗口或手機橫豎屏切換時,重新定位縮放的中心點。

        private void ScalableGrid_DoubleTapped(object sender, DoubleTappedRoutedEventArgs e)
        {
            scaleTransform.ScaleX = scaleTransform.ScaleY = 1;
            this.translateTransform.X = 0;
            this.translateTransform.Y = 0;
            this.ManipulationMode = ManipulationModes.System | ManipulationModes.Scale;
        }

  DoubleTapped事件是為了雙擊還原到初始狀態。

  對手勢的支持代碼是在private void ScalableGrid_ManipulationDelta(object sender, Windows.UI.Xaml.Input.ManipulationDeltaRoutedEventArgs e)方法中。其中判斷Scale大於1,也就是放大后才支持平移操作。同時去除System枚舉,這是因為不希望對圖片的平移被判斷為滑動FlipView控件,導致切換Image。

   StopWhenTranslateToEdge()方法是希望避免將圖片滑出屏幕邊緣導致無法繼續操作。

  將完成的ScalableGrid放置到FlipView的ItemTemplate中:

    <FlipView ItemsSource="{Binding Photos,Mode=OneTime}">
        <FlipView.ItemTemplate>
            <DataTemplate>
                <local:ScalableGrid>
                    <Image Source="{Binding ImageUri,Mode=OneTime}"></Image>
                </local:ScalableGrid>
            </DataTemplate>
        </FlipView.ItemTemplate>
    </FlipView>

  至此,一個滑動查看圖片的功能算是完成了。我們可以左右切換圖片,對FilpView的某一張圖片進行縮放和平移的操作,閱讀長微博也不是問題。

  那是不是完美無缺了呢?變態的用戶們會發現,我們在放大圖片后,如果當前的圖片沒有撐滿整個FilpViewItem,通過在空白處滑動屏幕,可以切換到另一張圖片。雖然也不是什么大問題,但是用戶老爺會不爽,那如何解決呢?我們祭出神器Live Visual Tree,來檢查一下到底是誰無視當前的ManipulationMode,硬是將手勢事件傳遞給了FilpView

  

  從截圖中的Visual Tree可以看出,選中ScalableGrid時,Gird實際是撐滿整個FilpViewItem的,也就是說ScalableGrid在非圖片區域不作為,不僅沒有截獲處理內部的Manipulation事件,反而直接冒泡傳遞給了上層FilpViewItem

  原先我的猜測是ScalableGird無法撐滿FlipViewItemManipulation事件不經過ScalableGrid。這種情況我需要在ScalableGrid外層再套一個PanelBorder遮蓋整個FlipViewItem的面積,然后綁定二者的ManipulationMode

  實際情況比想象的還要簡單,我只需要設置ScalableGirdBackground屬性為Transparent即可。最終的XAML如下:

    <FlipView ItemsSource="{Binding Photos,Mode=OneTime}">
        <FlipView.ItemTemplate>
            <DataTemplate>
                <local:ScalableGrid Background="Transparent">
                    <Image Source="{Binding ImageUri,Mode=OneTime}" Stretch="None"></Image>
                </local:ScalableGrid>
            </DataTemplate>
        </FlipView.ItemTemplate>
    </FlipView>

  好了,可以用你的Lumia 950XL或者Surface Pro 4來試一試了,沒有的話趕緊去買,最近大降價了,你值得擁有。另外StopWhenTranslateToEdge的算法實現得不是很好,期待評論中有好的思路,最好能不依賴外部UIElement  

  GitHub

  https://github.com/manupstairs/UWPSamples/tree/master/UWPSamples/PhotosBrowser


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM