1. 程式人生 > >[UWP]使用Picker構建應用內圖片公共裁剪組件

[UWP]使用Picker構建應用內圖片公共裁剪組件

按鍵 mat edev 項目 quest media 平臺 Locator fig

原文:[UWP]使用Picker構建應用內圖片公共裁剪組件

在上一篇博文《[UWP]如何實現UWP平臺最佳圖片裁剪控件》中我講解了編寫ImageCropper控件的過程及知識分享。在那篇文章裏,我大言不慚的稱其為UWP平臺最佳圖片裁剪控件(主要是沒有別的類似控件來充當對手??)。其實寫博客時的那個版本連交互動畫都還沒有,遠遠稱不上“最佳”。

不過,這一個月來ImageCropper經過數次叠代,它已經具有非常棒的體驗了!另外還有一件令人興奮的事,它即將加入WindowsCommunityToolkit中(PR已經接近尾聲)!這將讓更多UWP開發者可以體驗它。

今天這篇博文我來講下ImageCropper的一個特殊應用實例。

先來思考一下這個問題:

如果我們的應用只有一個界面需要調用圖片剪裁,那很好,直接在這個頁面嵌入ImageCropper控件就可以輕松完成了。但是,如果這個應用有多處需要裁剪圖片的地方(譬如說上傳圖片、修剪照片、裁剪頭像...),我們是否可以為這些需要裁剪圖片的地方構建一個公共的可調用組件呢?

事實上,我們可以借助HHChaosToolkit中的Picker組件來完成這一想法。

之前我寫過一系列關於Picker的文章,不了解Picker的話建議可以抽時間閱讀:

  • [UWP]不那麽好用的ContentDialog
  • [UWP]使用Popup構建UWP Picker
  • [UWP]使用Picker實現一個簡單的ColorPicker彈窗

那麽,我們今天使用Picker和ImageCropper來為應用構建一個公共裁剪組件。

先看下效果圖:

技術分享圖片

如何實現的呢?

項目結構

創建一個空白UWP應用項目,並引入了HHChaosToolkit.UWP以及ImageCropper.UWP這兩個Nuget包。

技術分享圖片

我在HHChaosToolkit項目首頁寫過:

HHChaosToolkit是一套適用於MVVM框架下使用的彈窗層組件庫。

HHChaosToolkit其中包含了Picker彈窗組件、SubWindows子窗口組件以及輕量級MVVM框架。我們在這個例子中使用到的是它的Picker以及MVVM部分。

ImageCropper則是這個例子中主要依賴的功能組件。

項目結構如下圖:

技術分享圖片

其中包含了兩個頁面,ImageCropperPickerPage即是這個例子中我們要構建的圖片公共裁剪組件,MainPage中包含了調用使用公共裁剪組件的示例,邏輯代碼均在其對應的ViewModel中。

圖片裁剪Picker

ImageCropperPicker就是我們想要的圖片公共裁剪組件的Picker實現,它可以在任意地方調用(無論是在View還是ViewModel的代碼中),且不會中斷當前的操作,其調用原理可以查看我之前的博文《[UWP]使用Popup構建UWP Picker》。

這個頁面中包含了ImageCropper控件,用於實現圖片裁剪功能。

技術分享圖片

界面布局如下:

<Page
    x:Class="ImageCropperPicker.Views.ImageCropperPickerPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:cropper="using:ImageCropper.UWP"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:local="using:ImageCropperPicker.Views"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    DataContext="{Binding ImageCropperPickerViewModel, Source={StaticResource Locator}}"
    RequestedTheme="Dark"
    mc:Ignorable="d">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition Width="Auto" />
        </Grid.ColumnDefinitions>
        <cropper:ImageCropper
            x:Name="ImageCropper"
            Padding="80"
            AspectRatio="{Binding AspectRatio}"
            Background="Transparent"
            CircularCrop="{Binding CircularCrop}"
            Mask="#af000000"
            SourceImage="{Binding SourceImage}" />
        <Grid Grid.Column="1" Background="#af000000">
            <StackPanel Padding="55" VerticalAlignment="Center">
                <AppBarButton
                    Margin="0,15"
                    Click="Button_Click"
                    Icon="Accept"
                    Label="OK" />
                <AppBarButton
                    Command="{Binding ExitCommand}"
                    Foreground="White"
                    Icon="Cancel"
                    Label="Cancel" />
            </StackPanel>
        </Grid>
    </Grid>
</Page>

邏輯層代碼中,我使用ImageCropperConfig類來接受調用方傳來的參數,並在打開Picker時讀取參數,初始化Picker。

ImageCropperConfig定義如下:

    public class ImageCropperConfig
    {
        public StorageFile ImageFile { get; set; }
        public double AspectRatio { get; set; } = -1;
        public bool CircularCrop { get; set; }
    }

ImageCropperPicker的ViewModel代碼:

using HHChaosToolkit.UWP.Mvvm;
using ImageCropperPicker.Models;
using System;
using Windows.UI.Xaml.Media.Imaging;
using Windows.UI.Xaml.Navigation;

namespace ImageCropperPicker.ViewModels
{
    public class ImageCropperPickerViewModel : ObjectPickerBase<WriteableBitmap>
    {
        private WriteableBitmap _sourceImage;
        public WriteableBitmap SourceImage
        {
            get => _sourceImage;
            set => Set(ref _sourceImage, value);
        }
        private double _aspectRatio;
        public double AspectRatio
        {
            get => _aspectRatio;
            set => Set(ref _aspectRatio, value);
        }
        private bool _circularCrop;
        public bool CircularCrop
        {
            get => _circularCrop;
            set => Set(ref _circularCrop, value);
        }
        public async override void OnNavigatedTo(NavigationEventArgs e)
        {
            if (e.Parameter is ImageCropperConfig config)
            {
                var writeableBitmap = new WriteableBitmap(1, 1);
                using (var stream = await config.ImageFile.OpenReadAsync())
                {
                    await writeableBitmap.SetSourceAsync(stream);
                }

                SourceImage = writeableBitmap;
                AspectRatio = config.AspectRatio;
                CircularCrop = config.CircularCrop;
            }
            base.OnNavigatedTo(e);
        }
    }
}

在這裏,完成裁剪圖片的操作我放在了界面上OK按鍵的後臺代碼中(實際上還是調用的Picker中的方法):

        private async void Button_Click(object sender, RoutedEventArgs e)
        {
            var img = await ImageCropper.GetCroppedBitmapAsync();
            ViewModel?.SetResult(img);
        }

應用內調用

調用方法非常的簡單,直接使用HHChaosToolkit中的ObjectPickerService來啟動Picker,並且等待Picker返回結果即可。

比較特別的是,在這裏我通過使用PickerOpenOption讓裁剪圖片Picker可以覆蓋到整個應用界面,UI表現看起來更佳。

調用代碼:

        private async Task<ImageSource> CropImage(ImageCropperConfig config)
        {
            var startOption = new PickerOpenOption
            {
                VerticalAlignment = VerticalAlignment.Stretch,
                HorizontalAlignment = HorizontalAlignment.Stretch,
            };
            var ret = await ViewModelLocator.Current.ObjectPickerService.PickSingleObjectAsync<WriteableBitmap>(
                typeof(ImageCropperPickerViewModel).FullName, config, startOption);
            if (!ret.Canceled)
            {
                return ret.Result;
            }
            return null;
        }

是不是看起來很簡單?是的,優雅的調用方式就應該這麽簡單!

結尾

正如我之前所說,Picker組件在很多地方都可以派上用處,我們幾乎可以用它來實現一切UWP自定義彈出框,非常歡迎有類似需求的開發者們試用!

這個示例項目開源在Github中,感興趣的可以自行Clone編譯,同時項目中的代碼請隨意享用,喜歡的話請不要忘記Star!

本篇博客到此結束!謝謝大家閱讀!

[UWP]使用Picker構建應用內圖片公共裁剪組件