Xamarin 앱 개발 테스트 #6

지난 #5에서 PCL과 Shared 프로젝트를 통해서 C# 코드를 공유하는 방법을 알아보았다.

이번에는 UI 구현을 통해서 Windows Phone과 유사하게 동작하는 Xamarin.Android 앱을 만들어 보고자 한다. Android 앱 개발을 잘 모르다 보니, Android UI 모델과 Xamarin.Android를 동시에 공부해야 해서 조금 어려운 부분은 있었지만 결과적으로 포팅은 큰 어려움 없이 성공적으로 진행되었다.

다음은 Xamarin.Android의 MainActivity.cs 파일이다.

    public class MainActivity : Activity
    {
        int count = 1;

        protected override void OnCreate(Bundle bundle)
        {
            base.OnCreate(bundle);
            
            // Set our view from the "main" layout resource
            SetContentView(Resource.Layout.Main);

            string searchTerm = "flowers";

            SearchFlickr(searchTerm);
        }

        async void SearchFlickr(string searchTerm)
        {
            List<FlickrPhotoResult> results = await FlickrSearcher.SearchAsync(searchTerm);
            var gridview = FindViewById<GridView>(Resource.Id.gridview);
            gridview.Adapter = new ImageAdapter(this, results);
        }
    }

Main.axml 파일에 GridView를 하나 만들어 놓고, 여기에 Adapter를 이용해서 FlickrSearcher에서 받은 List 데이터를 넘겨주고 있다. 같은 부분의 Windows Phone 코드를 살펴보면 다음과 같다.

        async void SearchFlickr(string searchTerm)
        {
            List<FlickrPhotoResult> results = await FlickrSearcher.SearchAsync(searchTerm);

            this.DataContext = results;

            ...
        }

Windows Phone에서는 XAML 페이지의 DataContext로 지정하고, GridView의 ItemsSource에 데이터 바인딩을 통해서 XAML의 각 오브젝트에서 처리를 하게 된다.

<GridView x:Name="gridView" ItemsSource="{Binding}" Grid.Row="2" Margin="10,0,0,0">
            <GridView.ItemTemplate>
                <DataTemplate>
                    <Grid HorizontalAlignment="Left" Width="460" Height="300">
                        <Border Background="{ThemeResource ListViewItemPlaceholderBackgroundThemeBrush}">
                            <Image Source="{Binding ImageUrl}" Stretch="UniformToFill" AutomationProperties.Name="{Binding Title}"/>
                        </Border>
                        <StackPanel VerticalAlignment="Bottom" Background="{ThemeResource ListViewItemOverlayBackgroundThemeBrush}">
                            <TextBlock Text="{Binding Title}" Foreground="{ThemeResource ListViewItemOverlayForegroundThemeBrush}" Style="{StaticResource TitleTextBlockStyle}" Height="60" Margin="15,0,15,0"/>
                        </StackPanel>
                    </Grid>
                </DataTemplate>
            </GridView.ItemTemplate>
        </GridView>

처음에는 이 개념이 이해하기 어려운데, 막상 데이터 바인딩 없이 Android UI 개발을 하려고 하니 머리가 더 복잡해지는 느낌이다.

어쨌든 Android에서는 데이터 바인딩 대신 Adapter를 만들어서 UI에 데이터를 뿌려 주어야 한다. Adapter는 BaseAdapter를 상속하여 클래스로 직접 구현해야 하는데, 아래와 같이 구현해 보았다.

using Android.App;
using Android.Content;
using Android.Graphics;
using Android.Views;
using Android.Widget;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;

namespace FlickrSearch.Android
{
    class ImageAdapter : BaseAdapter
    {
        Context context;
        List<FlickrPhotoResult> list;
        public ImageAdapter(Context c, List<FlickrPhotoResult> data)
        {
            context = c;
            list = data;
        }

        public override int Count
        {
            get { return list.Count; }
        }

        public override Java.Lang.Object GetItem(int position)
        {
            return null;
        }

        public override long GetItemId(int position)
        {
            return position;
        }

        public override global::Android.Views.View GetView(int position, global::Android.Views.View convertView, global::Android.Views.ViewGroup parent)
        {
            /*
            // 직접 ImageView를 생성해서 전달하는 경우 
            ImageView imageView;
            var item = list[position];

            if (convertView == null)
            {
                imageView = new ImageView(context);
                imageView.LayoutParameters = new AbsListView.LayoutParams(1200,900);
                imageView.SetScaleType(ImageView.ScaleType.CenterCrop);
                //imageView.SetPadding(8,8,8,8);
            }
            else
            {
                imageView = (ImageView)convertView;
            }
            Bitmap bitmap = GetImageBitmapFromUrl(item.ImageUrl);
            imageView.SetImageBitmap(bitmap);
            return imageView;
             */

            // GridViewItem.axml 파일을 이용해서 값만 전달하는 경우
            View row = convertView;
            RecordHolder holder = null;

            if (row == null)
            {
                LayoutInflater inflater = ((Activity)context).LayoutInflater;
                row = inflater.Inflate(Resource.Layout.GridViewItem, parent, false);

                holder = new RecordHolder();
                holder.txtTitle = row.FindViewById<TextView>(Resource.Id.item_text);
                holder.imageItem = row.FindViewById<ImageView>(Resource.Id.item_image);
                row.Tag = holder;
            }
            else
            {
                holder = row.Tag as RecordHolder;
            }
            holder.txtTitle.Text = list[position].Title;
            Bitmap bitmap = GetImageBitmapFromUrl(list[position].ImageUrl);
            holder.imageItem.SetImageBitmap(bitmap);
            return row;
        }

        private Bitmap GetImageBitmapFromUrl(string p)
        {
            Bitmap imageBitmap = null;
            using (var wc = new WebClient())
            {
                var imageBytes = wc.DownloadData(p);
                if (imageBytes != null && imageBytes.Length > 0)
                {
                    imageBitmap = BitmapFactory.DecodeByteArray(imageBytes, 0, imageBytes.Length);
                }
            }
            return imageBitmap;
        }
    }

    public class RecordHolder : Java.Lang.Object
    {
        public TextView txtTitle;
        public ImageView imageItem;
    }
}

추상 클래스인 BaseAdapter를 상속하여 구현하는데, VS에서 자동으로 생성해주는 코드에 Count, GetItem, GetItemId, GetView 등 일부 메소드의 구현부만 추가하면 된다. GetView에서 2가지 방식으로 구현을 해보았는데, 주석처리된 위 쪽은 직접 ImageView를 생성하여 이미지를 뿌려주는 것이고, 아래 쪽은 별도의 axml 파일을 생성해서 이를 템플릿처럼 사용하는 것이다. 해당 코드들은 Android와 Xamarin의 문서 및 포럼 글들을 참고로 하였다. 별도로 생성하는 GridViewItem.axml의 내용은 다음과 같다.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">
  <ImageView 
    android:id="@+id/item_image" 
    android:layout_width="480dp" 
    android:layout_height="360dp" 
    android:layout_marginRight="0dp" > 
  </ImageView> 
  <TextView 
    android:id="@+id/item_text" 
    android:layout_width="wrap_content" 
    android:layout_height="wrap_content" 
    android:layout_marginTop="0dp" 
    android:textSize="30sp" > 
  </TextView>
</LinearLayout>

Android UI 레퍼런스와 샘플 코드를 찾아보느라 조금 시간이 걸리기는 했지만, 어렵지 않게 구성할 수 있었고 전체적으로 생각보다 매끄럽게 포팅이 진행되었다. 그러나, 에뮬레이터 자체가 느리기도 하고, 코드를 최적화하지 않아서 동작이 버벅거렸다. 특히, 이미지 스크롤 할 때 끊김이 심했는데, 데이터 로딩 코드를 최적화 해야 할 듯 싶다.(Windows Phone에서는 URL만 바인딩 시키면 지가 알아서 퍼포먼스 지장 없도록 로드해와서 뿌려 주는데… 내가 Android를 잘 몰라서 그렇겠지…)

xamarin

Windows Phone(좌), Xamarin.Android(우)

UI가 아주 예쁘게 나오진 않았지만 결과적으로 같은 기능을 가진 앱을 포팅할 수 있었다.

이번 Xamarin 앱 개발 테스트를 통해서 Xamarin으로 크로스플랫폼 앱 개발이 충분히 가능한 시나리오이며, 실제 구현하기 위해서 알아야 하는 기본적인 개념과 테크닉들을 알아볼 수 있었다. 아직 국내에 Xamarin이 잘 알려지지 않았고, 실제로 이 도구를 이용해서 앱을 개발한 사례도 극히 드문 것으로 알고 있다. 국내에서 최근에 유행하고 있는 Unity도 같은 오픈소스 프레임워크를 사용하고 있는데, 이 글을 통해서 더 많은 개발자들이 C#의 다양한 활용 가능성에 대해서 알릴 수 있으면 좋겠다.

계속해서 기회가 된다면 디바이스에 올려서 퍼포먼스 테스트를 해보거나, Xamarin.iOS 쪽도 살펴볼까 싶다.

사용한 소스 코드는 여기서 다운로드할 수 있다.

 

Advertisements

답글 남기기

아래 항목을 채우거나 오른쪽 아이콘 중 하나를 클릭하여 로그 인 하세요:

WordPress.com 로고

WordPress.com의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Twitter 사진

Twitter의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Facebook 사진

Facebook의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Google+ photo

Google+의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

%s에 연결하는 중