2017/4/17

Xamarin.Forms 教學系列文(十三.壹 - 1)非同步讀取 Uri 圖片



學習目標
  • Device.BeginInvokeOnMainThread 非同步
  • ActivityIndicator 轉圈圈等待圖

會將這支小程式獨立出來講解,主要原因是從 Uri 讀取圖片時會有短暫的延遲,

若有大量圖片要讀取話,一定會產生不好的使用者經驗,所以必須搭配非同步的方法來載圖。

直接上程式碼:

前端很單純,最上方一個 Image,中間一個 Label 顯示檔案名稱,一個 ActivityIndicator 轉圈圈,兩個 Button 切換前後張圖。


要注意的是 ActivityIndicator 這 Visual Element,屬性 IsRunning 預設值是 False,所以預設是隱藏的 。

若將 IsRunning 設為 True 就會顯示 轉圈圈的的等待圖,每個平台都有預設這個物件和方法,但若你知道程式 執行的時間,可以用下一章節提到的 ProgressBar。

另外 Image 的 OnImagePropertyChanged 是用來配合 ActivityIndicator,讓 ActivityIndicator 知道圖片什麼時候載入。

*OnPlatform 寫法已更改,請參考此篇文章
XAML
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="ImageBrowser.ImageBrowserPage">

    <ContentPage.Padding>
        <OnPlatform x:TypeArguments="Thickness" 
                iOS="0, 20, 0, 0"/>
    </ContentPage.Padding>

    <StackLayout>
        <!--Image-->
        <Image x:Name="image"
           VerticalOptions="CenterAndExpand"
           PropertyChanged="OnImagePropertyChanged" />

        <Label x:Name="filenameLabel"
           HorizontalOptions="Center" />

        <!--ActivityIndicator-->
        <ActivityIndicator x:Name="activityIndicator"/>

        <StackLayout Orientation="Horizontal">
            <Button x:Name="prevButton" 
              Text="Previous"
              IsEnabled="false"
              HorizontalOptions="CenterAndExpand" 
              Clicked="OnPreviousButtonClicked" />

            <Button x:Name="nextButton"
              Text="Next" 
              IsEnabled="false"
              HorizontalOptions="CenterAndExpand" 
              Clicked="OnNextButtonClicked" />

        </StackLayout>

    </StackLayout>
</ContentPage>


.cs 要注意的是,Xamarin.Forms 內只有 WebRequest 這個物件可以下載 Http 的東西。

而 WebRequestCallback 內有個 Device.BeginInvokeOnMainThread(() => ,用來開啟另一條 Tread 非同步執行動作,程式才不會因為讀圖的延遲造成 MainThread 鎖死。

WebRequest 讀取結束後利用 .EndGetResponse(result).GetResponseStream() 回傳 Stream 物件,再將回傳的 Stream 物件解序列成 ImageList 類別的物件。

最後 FetchPhoto() 方法內將圖片載入。


Code - behind
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Net;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using Xamarin.Forms;

namespace ImageBrowser
{
    public partial class ImageBrowserPage : ContentPage
    {
        [DataContract]
        class ImageList
        {
            [DataMember(Name = "photos")]
            public List Photos = null;
        }

        WebRequest request;
        ImageList imageList;

        int imageListIndex = 0;

        public ImageBrowserPage()
        {
            InitializeComponent(); // Get list of stock photos. 
            Uri uri = new Uri("https://developer.xamarin.com/demo/stock.json");

            request = WebRequest.Create(uri);
            request.BeginGetResponse(WebRequestCallback, null);
        }

        private void InitializeComponent()
        {
            throw new NotImplementedException();
        }

        void WebRequestCallback(IAsyncResult result)
        {
            //非同步方法,開啟另一條 Thread
            Device.BeginInvokeOnMainThread(() =>
            {
                try
                {
                    Stream stream = request.EndGetResponse(result).GetResponseStream();

                    // Deserialize the JSON into imageList; 
                    var jsonSerializer = new DataContractJsonSerializer(typeof(ImageList));
                    imageList = (ImageList)jsonSerializer.ReadObject(stream);

                    if (imageList.Photos.Count > 0)
                        //載圖
                        FetchPhoto();
                }
                catch (Exception exc)
                {
                    filenameLabel.Text = exc.Message;
                }
            });
        }

        void OnPreviousButtonClicked(object sender, EventArgs args)
        {
            imageListIndex--;
            FetchPhoto();
        }

        void OnNextButtonClicked(object sender, EventArgs args)
        {
            imageListIndex++; FetchPhoto();
        }

        void FetchPhoto()
        { 
            // 準備新的 Image
            image.Source = null;
            string url = imageList.Photos[imageListIndex];

            // 檔案名稱 
            filenameLabel.Text = url.Substring(url.LastIndexOf('/') + 1);

            // Create the UriImageSource. 
            UriImageSource imageSource = new UriImageSource
            {
                Uri = new Uri(url + "?Width=1080"),
                CacheValidity = TimeSpan.FromDays(30)
            };

            // 設定 Source
            image.Source = imageSource;

            // 設定 Button 是否能作用
            prevButton.IsEnabled = imageListIndex > 0;
            nextButton.IsEnabled = imageListIndex < imageList.Photos.Count - 1;
        }

        void OnImagePropertyChanged(object sender, PropertyChangedEventArgs args)
        {
            if (args.PropertyName == "IsLoading")
            {
                activityIndicator.IsRunning = ((Image)sender).IsLoading;
            }
        }
    }
}


前一章節有提到從 Uri 讀取圖片時,會有快取暫存在手機內,所以當按鈕切換到已經載入過的圖,會發現速度快很多。




沒有留言:

張貼留言