2017/4/18

Xamarin.Forms 教學系列文(十三.貳)從串流與不同平台載入圖片 & Toolbars

學習目標
  • ImageSource.FromStream() - 從串流讀圖
  • ImageSource.FromFile() - 各平台讀圖
  • 了解各平台圖片解析度
  • Toolbars

十三.壹 章節介紹了讀取圖片四種方法的前兩種,這節會介紹剩下的 FromStream() FromFile()

特別是 FromFile() 方法會從不同平台讀取圖片,更需要了解各圖片解析度之特性。

先從 FromStream() 開始:


FromStream()

Stream 是 .NET 使用很久的一種類別,Xamarin.Forms 在讀圖也提供了 FromStream 的方法對應使用。

比較奇怪一點是, ImageSource.FromStream() 並不是一個 Stream 物件,而是一個會回傳 Stream 的 Func 物件 (簡單說就是一個方法)

來看程式碼,就會發現 FromStream 的寫法都會接著 Lambda 並在方法內回傳 Stream。

先在前端放兩個 Image 準備讀圖:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="ImageBrowser.ImageBrowserPage">
    <StackLayout>
        <Image x:Name="image1" HorizontalOptions="Center" VerticalOptions="CenterAndExpand" />
        <Image x:Name="image2" HorizontalOptions="Center" VerticalOptions="CenterAndExpand" />
    </StackLayout>

</ContentPage>


第一張圖從 Resource 內取出資源後塞到 Stream 內:
        public BitmapStreamsPage()
        {
            InitializeComponent();
            // Load embedded resource bitmap. 
            string resourceID = "BitmapStreams.Images.IMG_0722_512.jpg";
            image1.Source = ImageSource.FromStream(() =>
            {
                Assembly assembly = GetType().GetTypeInfo().Assembly;
                Stream stream = assembly.GetManifestResourceStream(resourceID);

                return stream;
            });
        }


第二張圖從 Uri 塞到 Stream 內:
        public BitmapStreamsPage()
        {
            // Load web bitmap. 
            Uri uri = new Uri("https://developer.xamarin.com/demo/IMG_0925.JPG?width=512");
            WebRequest request = WebRequest.Create(uri);

            request.BeginGetResponse((IAsyncResult arg) =>
            {
                Stream stream = request.EndGetResponse(arg).GetResponseStream();
                
                ImageSource imageSource = ImageSource.FromStream(() => stream);

                Device.BeginInvokeOnMainThread(() => image2.Source = imageSource);
            }, null);
        }

用 Stream 的寫法看起來有更多的控制權,但直接使用 FromUri 的好處是會自動幫你快取,Stream 則沒有。


FromFile()

通常搭配 OnPlatform 來讀取不同平台的圖片,且會自動去取得最適解析度的檔案。

當新增一個專案時,會發現有一些預設的圖檔在各平台的專案內,Android 和 iOS 都在 Resource 資料夾內,而 Windows 在 assetes 資料夾內。

*OnPlatform 寫法已更改,請參考此篇文章
來看一下 .cs 寫法:
        Image image = new Image
        {
            Source = new FileImageSource
            {
                File = Device.OnPlatform(iOS: "Icon-Small-40.png",
                                         Android: "icon.png",
                                         WinPhone: "Assets/StoreLogo.png")
            },
            HorizontalOptions = LayoutOptions.Center,
            VerticalOptions = LayoutOptions.CenterAndExpand
        }
要注意的是讀取圖只需鍵入你要取得圖片的檔名

Xaml 寫法:
    <Image.Source>
        <OnPlatform x:TypeArguments="ImageSource" 
                               iOS="Icon-Small-40.png"
                               Android="icon.png" 
                               WinPhone="Assets/StoreLogo.png" />
    </Image.Source>




圖片解析度

上一小節程式可以看到 iOS 的圖檔叫做 Icon-Small-40.png ,但你若是將 Resource 資料夾打開,會發現有三個檔名類似但解析度不一樣的圖檔:
  • Icon-Small-40.png - 40 pixels square
  • Icon-Small-40@2x.png - 80 pixels square
  • Icon-Small-40@3x.png - 120 pixels square

本書用的 iPhone6 會將此 Icon render 成 40 device-independent units。
而 iPhone6 一個 device-independent units 對應兩個 pixel,此時程式就會採用 Icon-Small-40@2x.png 這張圖。

同理,若是 iPad 2 會使用 Icon-Small-40@3x.png。
Xamarin 單位相關的教學可以參考 第五章

再來看一下 Android 的 Resource 資料夾,你會發現以下圖檔:
  • drawable/icon.png - 72 pixels square
  • drawable - hdpi/icon.png - 72 pixels square
  • drawable - xdpi/icon.png - 96 pixels square
  • drawable - xxdpi/icon.png - 144 pixels square

以本書的 Nexus5 為例,icon 會 render 成 48 device-independent units,而 Nexus5 一個 device-independent units 對應三個 pixel ,所以會使用 xxdpi 這張圖。

Windows phone 就不討論了。

Android 還有其他大小:
  • ldpi ( 0.75 pixels to the DIU )
  • mdpi ( 1 pixel to the DIU )
  • hdpi ( 1.5 pixel to the DIU )
  • xhdpi ( 2 pixel to the DIU )
  • xxhdpi ( 3 pixel to the DIU )
  • xxxhdpi ( 4 pixel to the DIU )

不過你不需要產生全部解析度的圖片,一般來說 hdpi 和  xhdpi 和 xxhdpi 就夠了。


Toolbars

其中一個最常用 bitmaps 的物件為 Xamarin.Forms toolbar,通常會出現在頁面的上方作為功能列,toolbar items 是可以被點擊的。

但 Xamarin.Forms 沒有直接提供 toolbar 類別可以使用,必須先宣告一個 ToolbarItem 的集合,再指派給 Page 的 ToolbarItems 屬性。

ToolbarItem 有以下三個屬性:
  • Text - 可出現可不出現,端看平台或是 Order
  • Icon - FileImageSource 來自各個平台的專案的 bitmap 物件
  • Order - 有以下三個順序可以使用,  Default, Primary, or Secondary

Order 這個屬性管理 ToolbarItem 是否出現 image(Primary),或是 Text(Secondary)。

ToolbarItem 最重要的就是 Icon 屬性,由於各個平台讀圖不逕相同,也因為這樣才會使用 FileImageResource。

看一下 Xaml 寫法:
*OnPlatform 寫法已更改,請參考此篇文章
 <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" 
                  xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
                  x:Class="ToolbarDemo.ToolbarDemoPage" 
                  Title="Toolbar Demo">

    <Label x:Name="label" 
                FontSize="Medium"
                HorizontalOptions="Center" 
                VerticalOptions="Center" />

    <ContentPage.ToolbarItems>

        <!--第一個 item-->
        <ToolbarItem Text="edit" 
                         Order="Primary" 
                         Clicked="OnToolbarItemClicked">

            <ToolbarItem.Icon>
                <OnPlatform x:TypeArguments="FileImageSource" 
                             iOS="edit.png" 
                             Android="ic_action_edit.png" 
                             WinPhone="Images/edit.png" />
            </ToolbarItem.Icon>
        </ToolbarItem>

        <!--第二個 item-->
        <ToolbarItem Text="search" 
                     Order="Primary" 
                     Clicked="OnToolbarItemClicked">

            <ToolbarItem.Icon>
                <OnPlatform x:TypeArguments="FileImageSource" 
                               iOS="search.png" 
                               Android="ic_action_search.png" 
                               WinPhone="Images/feature.search.png" />
            </ToolbarItem.Icon>
        </ToolbarItem>
        
        <!--若加入太多 Item 的話,各平台顯示的方式也不太一樣-->

    </ContentPage.ToolbarItems>

</ContentPage>


點擊右邊點點點:


Button images

Button 本身就有 Image 屬性可以修改,但這個特性不是用來做單張圖片的按鈕,通常是在按鈕文字的旁的圖示。

直接來看 code 和結果:
*OnPlatform 寫法已更改,請參考此篇文章
<Button Text="Oh Yeah">

    <Button.Image>
        <OnPlatform x:TypeArguments="FileImageSource" 
                         iOS="ic_action_good.png" 
                         Android="ic_action_good.png"
                         WinPhone="Images/ic_action_good.png" />
    </Button.Image>
    
</Button>




原文書上提供三種平台可下載 Icon 的地方:

Android : http://developer.android.com/design/downloads

IOS:http://smashingmagazine.com/2010/07/14/gcons-free-all-purpose-icons-for-designers-and-developers-100-icons-psd/

Win : C:\Program Files (x86)\Microsoft SDKs\Windows Phone\v8.0\Icons

不論是從哪邊下載的 Icons,放到專案內都要確定 Build Action 有設定好。










8 則留言:

  1. 作者已經移除這則留言。

    回覆刪除
    回覆
    1. 痾正想回你留言,所以問題已經解決了嗎?

      刪除
  2. 你好,我是最近開始在學Xamarin的新手,關於ToolbarItem這邊是不是少寫了一些東西
    因為我的toolbar一直都是神隱的狀態。
    直到去google才發現,似乎要將App.xaml.cs那邊將Mainpage改寫成:

    MainPage = new NavigationPage(new MainPage());

    請問除了這個方式外,還有別的方式嗎?
    謝謝

    回覆刪除
    回覆
    1. 當初翻譯書時沒注意到這個優先條件,目前來說還可以用 Shell

      刪除
    2. 可以請問一下如果是在自己新增的頁面,怎麼讓新增的頁面也有toolbar

      刪除
    3. 用 Shell 建立 App 或是如上所說的在 App.xaml.cs 將頁面用 NavigationPage 包起來,才會出現

      刪除
    4. 很感謝您的回覆,最近想透過Xamarin.Form製作一個軟體
      爬了很多與妳相關的文章受益良多近期也想買您出的書
      想了解一下書有解說gmail認證的部分嗎?

      刪除
    5. gmail 認證是指第三方登入嗎? 書中沒講太多應用層面的東西,都是以基礎的觀念內容為主,若有需要我可以幫你研究看看

      刪除

注意:只有此網誌的成員可以留言。