자알못 자마린: Xamarin.Forms 이미지 기초

Table of Content

Xamarin.Forms의 Image

Platform-independent Image

플랫폼 독립적인, 모든 플랫폼에 공통적으로 보이는 이미지. 통합 자원 식별자(Uniform Resource Identifier, URI)를 사용하거나 공유 프로젝트에 이미지 파일을 가져와(embed) 사용한다.

  • 예: 배경

Platform-specific Image

플랫폼 별로 고유한, 각 플랫폼(.Android, .iOS, .UWP 등)마다 고유하게 보이는 이미지. 플랫폼 별로 고유한 이미지 작업을 하려면 각 애플리케이션 프로젝트에 서로 다른 이미지를 포함시켜야 한다.

  • 예: 아이콘, 툴바, 스플래시 이미지 등

 

URI를 사용한 Image

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="MyFirstXamarinApp.ImagePage">
    <Image Source="https://upload.wikimedia.org/wikipedia/ko/8/87/Kakaofriends.png"></Image>
</ContentPage>

C#

이미지 캐싱 설정 등은 XAML로는 불가능하므로 C# 코드로 설정해주어야 한다.

<?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="MyFirstXamarinApp.ImagePage">
    <Image x:Name="image"></Image>
</ContentPage>
using System;
using Xamarin.Forms;

namespace MyFirstXamarinApp
{
    public partial class ImagePage : ContentPage
    {
        public ImagePage()
        {
            InitializeComponent();

            /* UriImageSource 객체 생성 */
            var imageSource = new UriImageSource { Uri = new Uri("https://upload.wikimedia.org/wikipedia/ko/8/87/Kakaofriends.png") };
            
            /* UriImageSource 객체의 주요 속성 */
            imageSource.CachingEnabled = false; // 변경 가능한 프로필 사진 등을 실시간으로 사진을 확인해야 하는 경우 바람직하지 않음
            imageSource.CacheValidity = TimeSpan.FromHours(1); // 기본 캐싱 시간: 24시간

            /* URI를 이용한 Image 추가 방법 1: UriImageSource 객체를 만들어 Image 객체에 지정 */
            image.Source = imageSource;

            /* URI를 이용한 Image 추가 방법 2: Image 객체에 직접 URI 지정 */
            image.Source = "https://upload.wikimedia.org/wikipedia/ko/8/87/Kakaofriends.png"; // UriImageSource 타입으로 암시적 캐스팅 됨
        }
    }
}

 

Image의 Aspect 속성

레이아웃 비율과 이미지 비율이 서로 다를 경우 이미지를 어떻게 보여줄 것인지를 나타내는 속성이다.

속성 설명

  • Aspect.Fill: 이미지를 꽉 채우되 비율은 무시
  • Aspect.AspectFit: 이미지 비율에 맞춰 표시
  • Aspect.AspectFill 이미지를 꽉 채우되 비율은 유지
왼쪽부터 차례대로 Fill, AspectFit, AspectFill

XAML

<Image x:Name="image" Aspect="fill"></Image>
<Image x:Name="image" Aspect="AspectFill"></Image>
<Image x:Name="image" Aspect="AspectFit"></Image>

C#

image.Aspect = Aspect.Fill;
image.Aspect = Aspect.AspectFit;
image.Aspect = Aspect.AspectFill;

 

Activity Indicator

이미지를 불러오는 동안 Activity Indicator로 작업이 진행중임을 표시하는 예제

<?xml version="1.0" encoding="UTF-8"?>
<ContentPage BackgroundColor="Black" xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="MyFirstXamarinApp.ImagePage">

    <!-- ActivityIndicator를 중앙에 표시하기 위해 AbsoluteLayout 사용 -->
    <AbsoluteLayout>
        <Image x:Name="image" 
               Source="https://upload.wikimedia.org/wikipedia/ko/8/87/Kakaofriends.png"
               Aspect="AspectFit"
               AbsoluteLayout.LayoutBounds="0, 0, 1, 1"
               AbsoluteLayout.LayoutFlags="All"></Image>
        
        <!-- IsRunning 상태를 다른 View로부터 받기 위해 데이터 바인딩 사용 -->
        <ActivityIndicator 
            Color="White"
            IsRunning="{Binding Source={x:Reference image}, Path=IsLoading}"
            AbsoluteLayout.LayoutBounds="0.5, 0.5, 100, 100"
            AbsoluteLayout.LayoutFlags="PositionProportional"></ActivityIndicator>
        
    </AbsoluteLayout>
</ContentPage>
이미지가 로딩중일 때 Activity Indicator가 활성화된다.

 

Embedded Image

Xamarin.Forms에서는 모든 플랫폼에 공통적으로 보여줄 이미지를 프로젝트로 가져와 사용할 수 있다. 공유 프로젝트에 이미지를 추가한 후 이미지에 부여된 리소스 ID를 사용하면 된다.

공유 프로젝트에 Images 폴더를 만들어 그 안에 이미지 추가
추가된 이미지 마우스 우클릭 -> 빌드작업 -> EmbeddedResource 설정 후 부여된 리소스 ID 확인

XAML에서 Embedded Image 사용

XAML에서 Image 태그의 Source 값은 URI로 인식되므로 Embedded Image의 리소스 ID를 그대로 사용할 수 없다. 이를 해결하려면 사용자 정의 XAML 태그 확장을 사용해야 한다.

사용자 정의 XAML 태그 확장을 사용하기 위해 공용 프로젝트에 폴더를 하나를 만들고(예: MarkupExtensions) 그 안에 C# 클래스 파일을 추가한다. (예: EmbeddedImage.cs)

EmbeddedImage 클래스는 리소스 ID를 받기 위한 string 타입 프로퍼티를 가져야 한다. 또한 IMarkupExtension 인터페이스를 상속받아 ProvideValue 메소드를 구현해야 한다. 이 메소드는 object 객체를 반환한다.

EmbeddedImage.cs

using System;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;

namespace MyFirstXamarinApp.MarkupExtensions
{
    // XAML로 기록될 때 Content가 기록되는 속성이 무엇인지 명시해주는 애트리뷰트
    [ContentProperty("ResourceId")]
    public class EmbeddedImage : IMarkupExtension
    {
        // 리소스 ID를 받기 위한 프로퍼티
        public string ResourceId { get; set; }

        // 이 메소드에서 object(Image)를 반환
        public object ProvideValue(IServiceProvider serviceProvider)
        {
            // null 입력 방지용
            if (String.IsNullOrWhiteSpace(ResourceId))
                return null;

            return ImageSource.FromResource(ResourceId);
        }
    }
}

ImagePage.xaml

<?xml version="1.0" encoding="UTF-8"?>

<!-- 사용자 정의 XAML 태그 확장을 사용할 때 관습적으로 접두사 local을 사용 -->
<-- 접두사 local에 MarkupExtensions를 매핑 -->
<ContentPage 
    xmlns="http://xamarin.com/schemas/2014/forms" 
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
    xmlns:local="clr-namespace:MyFirstXamarinApp.MarkupExtensions"
    x:Class="MyFirstXamarinApp.ImagePage">

        <Image 
            x:Name="image" 
            Source="{local:EmbeddedImage ResourceId=MyFirstXamarinApp.Images.KakaoFriends.jpg}"></Image>
        
</ContentPage>

C#에서 Embedded Image 사용

image.Source = ImageSource.FromResource("MyFirstXamarinApp.Images.KakaoFriends.jpg");

 

Platform-specific Image

버튼, 툴바 등 각 플랫폼마다 고유한 설계 가이드라인을 따르기 위해서는 플랫폼마다 이미지를 다르게 설정해야 한다.

플랫폼 별 이미지 파일 관리법

Android는 프로젝트 내 여러 drawable 폴더에 같은 이름의 이미지를 복사하면 하나의 이미지로 인식한다.

  • .Android/Resources/drawable/image.png
  • .Android/Resources/drawable-hdpi/image.png
  • .Android/Resources/drawable-xhdpi/image.png
  • .Android/Resources/drawable-xxhdpi/image.png
  • .Android/Resources/drawable-xxxhdpi/image.png

iOS는 원본파일과 원본파일명 뒤에 @2x, @3x를 붙인 파일을 Resources 폴더 내에 복사하면 하나의 이미지로 인식한다.

  • .iOS/Resources/image.png
  • .iOS/Resources/image@2x.png
  • .iOS/Resources/image@3x.png

Windows

  • 정해진 규칙은 딱히 없는듯. 기본적으로 이미지 파일을 루트 경로에 복사.

XAML에서 Platform-specific Image 사용

View의 Image 속성에 Image 파일명을 부여

<Button x:Name="button" Image="clock.png"/>

C#에서 Platform-specific Image 사용

button.Image = (FileImageSource)ImageSource.FromFile("clock.png");

 

Windows 플랫폼에서의 Image 경로 처리

Windows 플랫폼은 Image 파일 위치에 제약을 두지 않는다. Windows 프로젝트에서 특정 폴더에서 Image 파일을 관리한다면 플랫폼 별로 Image 파일 위치를 잡아줘야 한다.

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="MyFirstXamarinApp.ImagePage">
    
    <Button x:Name="button">
        <Button.Image>
            <OnPlatform x:TypeArguments="FileImageSource"
                Android="clock.png"
                iOS="clock.png"
                WinPhone="images/clock.png">
            </OnPlatform>
        </Button.Image>
    </Button>
    
</ContentPage>

C#

using Xamarin.Forms;

namespace MyFirstXamarinApp
{
    public partial class ImagePage : ContentPage
    {
        public ImagePage()
        {
            InitializeComponent();

            switch (Device.RuntimePlatform)
            {
                case Device.Android:
                    button.Image = (FileImageSource)ImageSource.FromFile("clock.png");
                    break;
                case Device.iOS:
                    button.Image = (FileImageSource)ImageSource.FromFile("clock.png");
                    break;
                case Device.UWP:
                    button.Image = (FileImageSource)ImageSource.FromFile("images/clock.png");
                    break;
            }
        }
    }
}

 

애플리케이션 아이콘 설정

Android

Visual Studio for Mac에서 .Android 프로젝트 마우스 우클릭 -> 옵션 -> 빌드 – Android 응용 프로그램 메뉴에서 응용 프로그램 아이콘을 설정할 수 있다.

Visual Studio에서 .Android 프로젝트 마우스 우클릭 -> 속성 -> Android Manifest 메뉴에서 Application Icon을 설정할 수 있다.

iOS

Visual Studio for Mac에서 .iOS 프로젝트 내 Assets.xcastsets를 더블클릭하면 AppIcon을 지정할 수 있다.

Visual Studio에서 .iOS 프로젝트 내 Info.plist를 더블클릭하면 AppIcon을 지정할 수 있다.

Windows(UWP)

Visual Studio에서 .UWP 프로젝트 내 Package.appxmanifest를 더블클릭 -> Visual Assets 탭에서 앱의 로고를 설정할 수 있다.

 

둥근 이미지 만들기

NuGet을 이용하여 Image Circle Plugin 추가

Image Circle Plugin의 GitHub 사이트는  https://github.com/jamesmontemagno/ImageCirclePlugin

위 사이트를 접속해보면 이 플러그인의 NuGet 패키지 이름은 Xam.Plugins.Forms.ImageCircle임을 알 수 있다. 이 이름을 복사해두자.

각 프로젝트 별로 Image Circle Plugin 추가

공용 프로젝트 마우스 우클릭 -> 추가 -> NuGet 패키지 추가 클릭.
위에서 복사해 둔 NuGet 주소를 붙여넣어 검색한 다음 ‘패키지 추가’ 클릭
나머지 프로젝트(.Andoird, .iOS)에도 똑같이 추가

 

공용 프로젝트, .Android, .iOS 프로젝트 등에 모두 NuGet 패키지를 추가해준다.

각 프로젝트에서 플러그인 초기화

.Android 프로젝트 내의 MainActivity.cs 파일을 열어 Image Circle Plugin을 초기화하는 코드를 추가한다.

using Android.App;
using Android.Content.PM;
using Android.OS;
using ImageCircle.Forms.Plugin.Droid; // Image Circle Render 네임스페이스

namespace MyFirstXamarinApp.Droid
{
    [Activity(Label = "MyFirstXamarinApp", Icon = "@mipmap/icon", Theme = "@style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
    public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
    {
        protected override void OnCreate(Bundle savedInstanceState)
        {
            TabLayoutResource = Resource.Layout.Tabbar;
            ToolbarResource = Resource.Layout.Toolbar;

            base.OnCreate(savedInstanceState);
            global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
            ImageCircleRenderer.Init(); // 앱이 로드되기 전 Image Circle Plugin 초기화

            LoadApplication(new App());
        }
    }
}

.iOS 프로젝트 내의 AppDelegate.cs 파일을 열어 Image Circle Plugin을 초기화하는 코드를 추가한다.

using Foundation;
using ImageCircle.Forms.Plugin.iOS; // Image Circle Render 네임스페이스
using UIKit;

namespace MyFirstXamarinApp.iOS
{
    [Register("AppDelegate")]
    public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate
    {
        public override bool FinishedLaunching(UIApplication app, NSDictionary options)
        {
            global::Xamarin.Forms.Forms.Init();
            ImageCircleRenderer.Init(); // App이 로드되기 전 Image Circle Plugin 초기화

            LoadApplication(new App());

            return base.FinishedLaunching(app, options);
        }
    }
}

.UWP 프로젝트 내의 App.xaml.cs 파일을 열어 Xamarin.Forms.Forms.Init(e); 코드 다음에 Image Circle Plugin을 초기화하는 코드를 추가한다.

Image Circle Plugin 사용하기

이 플러그인의 GitHub 사이트를 접속해보면 xmlns namespace를 지정하는 문구가 나온다. 이를 이용하여 임의의 xmlns 접두어를 선언한 후 이를 사용해야 한다.

아래 xaml은 접두어 ic에 Image Circle Plugin에 대한 네임스페이스와 어셈블리를 정의한 후 Embedded Image를 원형으로 불러오는 코드다. 사용자 정의 XAML 태그 확장을 사용했으며, 이에 대한 설명은 상단을 참고.

<?xml version="1.0" encoding="UTF-8"?>

<ContentPage 
    xmlns="http://xamarin.com/schemas/2014/forms" 
    xmlns:ic="clr-namespace:ImageCircle.Forms.Plugin.Abstractions;assembly=ImageCircle.Forms.Plugin"
    xmlns:local="clr-namespace:MyFirstXamarinApp.MarkupExtensions"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
    x:Class="MyFirstXamarinApp.ImagePage">
    
    <ic:CircleImage
        WidthRequest="100"
        HeightRequest="100"
        Aspect="AspectFill"
        VerticalOptions="Center"
        HorizontalOptions="Center"
        Source="{local:EmbeddedImage ResourceId=MyFirstXamarinApp.Images.ryan.jpg}"></ic:CircleImage>
    
</ContentPage>

실행결과

 

연습문제: 포토 갤러리

좌우 버튼을 터치하여 이미지를 넘기면서 볼 수 있는 포토 갤러리를 만들어 보기

  • 좌우 버튼 이미지 출처: https://icons8.com
  • 이미지는 Embedded 및 URI를 이용
  • 첫번째 이미지에서 되돌아가려 할 때, 마지막 이미지에서 다음으로 이동하려 할 때 경고창 띄우기

ImageExercise.xaml

Absolute Layout에서 Button을 먼저 정의한 후 Image를 정의한 경우 Button이 Image에 가려 터치가 안 될 수 있으니 주의.

<?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="MyFirstXamarinApp.ImagePage"
             BackgroundColor="Black">
    
    <AbsoluteLayout>
        <Image x:Name="imageView"
               AbsoluteLayout.LayoutBounds="0.5, 0, 1, 1"
               AbsoluteLayout.LayoutFlags="All"></Image>

        <ActivityIndicator Color="White" IsRunning="{Binding Source={x:Reference imageView}, Path=IsLoading}"
                           AbsoluteLayout.LayoutBounds="0.5, 0.05, 50, 50"
                           AbsoluteLayout.LayoutFlags="PositionProportional"></ActivityIndicator>
                           
        <Button x:Name="btnLeft" Image="left.png" BackgroundColor="Transparent" Clicked="Left_Clicked"
                AbsoluteLayout.LayoutBounds="0.05, 0.05, 100, 100"
                AbsoluteLayout.LayoutFlags="PositionProportional"></Button>
        
        <Button x:Name="btnRight" Image="right.png" BackgroundColor="Transparent" Clicked="Right_Clicked"
                AbsoluteLayout.LayoutBounds="0.95, 0.05, 100, 100"
                AbsoluteLayout.LayoutFlags="PositionProportional"></Button>
    </AbsoluteLayout>
</ContentPage>

ImageInfo.cs

namespace MyFirstXamarinApp
{
    /* 이미지 유형을 구분하기 위한 열거형 */
    public enum ImageType { Embedded, Uri }

    /* 이미지 정보를 담는 클래스 */
    class ImageInfo
    {
        public ImageType Type { get; }
        public string Resource { get; }

        public ImageInfo()
        {
            Type = ImageType.Embedded;
            Resource = "MyFirstXamarinApp.Images.KakaoFriends.jpg";
        }

        public ImageInfo(ImageType Type, string Resource)
        {
            this.Type = Type;
            this.Resource = Resource;
        }
    }
}

ImageExercise.xaml.cs

using System;
using Xamarin.Forms;
using Xamarin.Forms.PlatformConfiguration.iOSSpecific;

namespace MyFirstXamarinApp
{
    public partial class ImagePage : ContentPage
    {
        ImageInfo[] images;
        int index = 0;

        public ImagePage()
        {
            InitializeComponent();

            /* iPhone X Safe Area 설정 */
            if (Device.RuntimePlatform == Device.iOS)
            On<Xamarin.Forms.PlatformConfiguration.iOS>().SetUseSafeArea(true);

            /* 포토 갤러리에서 보여줌 이미지 */
            images = new ImageInfo[3];
            images[0] = new ImageInfo(ImageType.Embedded, "MyFirstXamarinApp.Images.KakaoFriends.jpg");
            images[1] = new ImageInfo(ImageType.Uri, "https://upload.wikimedia.org/wikipedia/ko/8/87/Kakaofriends.png");
            images[2] = new ImageInfo(ImageType.Embedded, "MyFirstXamarinApp.Images.ryan.jpg");

            /* 앱 실행시 맨 처음 보여줄 이미지 */
            imageView.Source = ImageSource.FromResource(images[0].Resource);
        }

        void ShowImage(ImageInfo image)
        {
            if (image.Type == ImageType.Embedded)
                imageView.Source = ImageSource.FromResource(image.Resource);
            else if (image.Type == ImageType.Uri)
                imageView.Source = ImageSource.FromUri(new Uri(image.Resource));
        }

        void Left_Clicked(object sender, System.EventArgs e)
        {
            if (index > 0)
                ShowImage(images[--index]);
            else
                DisplayAlert("알림", "첫번째 이미지입니다.", "OK");
        }

        void Right_Clicked(object sender, System.EventArgs e)
        {
            if (index < images.Length - 1)
                ShowImage(images[++index]);
            else
                DisplayAlert("알림", "마지막 이미지입니다.", "OK");
        }
    }
}

 

댓글 남기기