정리가 필요한 카테고리(추후 정리)/Android, iOS

[Android] Xamarin으로 카메라 플래시 사용하기

TwinParadox 2017. 9. 17. 16:03
728x90

우리나라에는 아직까지(혹은 앞으로도) 자마린 관련한 한글 가이드가 없다.

앞으로는 어떻게 될지 모르겠지만, 이런저런 이유로 국내에서는 자마린 활용도가 떨어지는 편인데, 그럼에도 불구하고 C#과 Visual Studio를 포기할 수 없다면서 자마린을 시도하려는 사람들에게 작은 도움이 되고자 계속 글을 올리고 있다. 나 또한 배우고 있는 입장이고, 이런저런 자투리 정보들을 끌어모은 결과물을 바탕으로 글을 쓰고 있다.


앞으로 선 보일 예제 등에서도 파워포인트로 도형 그리기를 해서 자작한 버튼으로, 디자인 쪽에서의 퀄리티가 다소 떨어질 수 있으나, 예제에 사용할 목적으로 만들었으니 신경쓰지 않았으면 한다.


제목에 적혀 있듯, 오늘 만들어 볼 예제용 앱은 카메라 플래시를 켜고 끄는 앱(속칭 손전등 앱)이다. 아래처럼 어느 곳을 터치하더라도 플래시를 켜고 끄는 손전등 앱을 만들어보도록 하자.






기본적으로 프로젝트를 생성하면 LinearLayout 하나가 생성되어 있는데, 이것에 클릭 이벤트가 발생할 때마다 플래시를 켜고 끈다. 화면 자체를 하나의 버튼처럼 사용한다고 생각하는 것이 좋다.




LinearLayout의 id를 "layout"으로 변경하고, 배경색을 검정색으로 선택한다.

디자이너에서 보면, 가운데 사진처럼 나타날 것이다. 다소 거슬리는 것이 있는데 앱의 상단에 위치한 타이틀 바다. 이것을 삭제하는 방법도 이 글에서 설명하겠다.





영상에 나온 테스트 기기는 안드로이드 4.3(API 18)이 설치된 갤럭시 S4이며 갤럭시 노트4, 그리고 플래시 라이트가 없는 갤럭시 플레이어 5.8에서 테스트를 거쳤다. 원래 동영상 촬영 당시에는 새로로 촬영했는데, 인코딩 과정에서 가로로 누워버렸다. 정상 작동한다는 것만 보면 되니까 다시 수정하진 않았다.


이제 소스를 살펴보자. Main.axml이나 AndroidManfiest.xml의 소스코드에 대해서는 상세하게 다루지 않겠다.

추가된 리소스가 많은 것도 아니고, 퍼미션 같은 경우 속성에서 설정할 수 있어서 직접 코드로 입력할 필요가 없기 때문이다.



Main.axml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/layout"
    android:padding="0.0dp"
    android:minWidth="25px"
    android:minHeight="25px"
    android:background="@android:color/black">
</LinearLayout>




AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="SimpleLighter.SimpleLighter" android:versionCode="1" android:versionName="1.0" android:installLocation="auto">
	<uses-sdk android:minSdkVersion="14" />
	<uses-permission android:name="android.permission.FLASHLIGHT" />
	<uses-permission android:name="android.permission.CAMERA" />
	<application android:allowBackup="true" android:label="@string/app_name" android:icon="@drawable/Icon"></application>
</manifest>


앱을 정상적으로 구동시키기 위해서는 카메라(CAMERA)와 플래시라이트(FLASHLIGHT) 퍼미션을 설정해주어야 한다. 안드로이드 버전은 크게 신경 쓸 필요는 없지만 최소 버전을 안드로이드 4.0(API 14 - Ice Cream Sandwich)으로 설정했다. 이유는 그저 테스트 할 수 있는 기기 중 가장 오래된 OS 버전이 4.0.3이었기 때문이다.




namespace SimpleLighter
{
    [Activity(Label = "SimpleLighter", MainLauncher = true, Icon = "@drawable/Icon", Theme = "@android:style/Theme.NoTitleBar")]
    public class MainActivity : Activity
    {
        private bool isFlashOn = false;

        private bool hasFlash;

        private Camera camera;
        private Parameters mParams;

        private LinearLayout layout;
        
        protected override void OnCreate(Bundle savedInstanceState)
        {
            base.OnCreate(savedInstanceState);

            // Set our view from the "main" layout resource
            SetContentView(Resource.Layout.Main);
            
            layout = FindViewById<LinearLayout>(Resource.Id.layout);

            hasFlash = ApplicationContext.PackageManager.HasSystemFeature(Android.Content.PM.PackageManager.FeatureCameraFlash);

            GetCamera();
            layout.Click += delegate
              {
                  ChangeFlash();
              };
        }


isFlashOn - 현재 플래시의 상태

hasFlash - 기기의 플래시 유무

camera, mParams - 카메라, 플래시 접근을 위한 변수들

layout - LinearLayout을 접근하기 위한 변수


OnCreate에서 리소스 변수에 리소스를 할당해주고, 기기의 카메라 플래시 여부를 hasFlash 변수로 받아낸다. 기기가 가지고 있는 기능 및 센서들에 대해서는 이와 같은 방식으로 체크해줄 수 있으며, 지원하지 않는 것에 대해서는 사용이 불가능하다는 토스트 메세지를 통해 알리는 것이 좋다.



        // 카메라
        private void GetCamera()
        {
            if (camera == null)
            {
                try
                {
                    camera = Camera.Open();
                    mParams = camera.GetParameters();
                }
                catch (RuntimeException ex)
                {
                    Log.Info("Error", ex.Message);
                }
            }
        }


GetCamera에서는 플래시를 사용하기 위해서 카메라에 접근하는 작업을 수행한다.

카메라 퍼미션을 주지 않은 경우와 같은 런타임 예외가 발생하면 로그에 에러를 남기도록 처리한다.



        // 플래시 버튼 클릭 이벤트
        private void ChangeFlash()
        {
            if (!hasFlash)
            {
                AlertDialog alert = new AlertDialog.Builder(this).Create();
                alert.SetTitle("오류");
                alert.SetMessage("기기에 카메라 플래시가 존재하지 않습니다.");
                alert.Show();
                return;
            }
            else
            {
                FlashLight();
            }
        }


디스플레이에 클릭(터치) 이벤트가 발생하면 ChangeFlash 메서드가 호출된다.

이 메서드에서는 기기에 플래시가 있는지 없는지 확인한 후, 없는 경우에는 카메라에 플래시가 없다는 토스트 메세지를 출력하고, 플래시가 존재하는 경우에는 FlashLIght 메서드를 호출한다.



        // 플래시
        private void FlashLight()
        {
            if (camera == null || mParams == null)
                return;
            if (isFlashOn)
            {
                mParams = camera.GetParameters();
                mParams.FlashMode = Parameters.FlashModeOff;
                camera.SetParameters(mParams);
                camera.StartPreview();
                isFlashOn = false;
                ChangeBackground();
            }
            else
            {
                mParams = camera.GetParameters();
                mParams.FlashMode = Parameters.FlashModeTorch;
                camera.SetParameters(mParams);
                camera.StartPreview();
                isFlashOn = true;
                ChangeBackground();
            }
        }

        // 버튼 이미지 및 색상 변경
        private void ChangeBackground()
        {
            if (isFlashOn)
                layout.SetBackgroundColor(Android.Graphics.Color.White);
            else
                layout.SetBackgroundColor(Android.Graphics.Color.Black);
        }

FlashLight 메서드는 플래시를 켜고 끄며, ChangeBackground 메서드는 디스플레이(LinearLayout)의 배경을 흰색과 검은색으로 바꾸는 작업을 수행한다. 이 모든 작업은 isFlashOn 변수를 조건문으로 판별하며 수행한다.



플래시가 꺼져 있는 경우 - 배경색 검은색

플래시가 켜져 있는 경우 - 배경색 흰색



앱의 기본적인 동작 과정은 이정도로 설명을 마치고, 이제 기본값으로 앱 상단에 생성되는 앱의 타이틀 바를 없애는 방법에 대해서 알아보자.


MainActivity.cs를 살펴보면 Activity에 관련하여 라벨, 아이콘 등에 대한 정보를 설정한다. 이곳에서 기본적인 설정들을 바꿔줄 수 있는데, Theme="@android:style/Theme.NoTitleBar"를 추가해준 뒤 에뮬레이터나 기기에서 앱을 실행해보면, 타이틀 바가 사라져 있을 것이다. Main.axml에 남아 있는 것은 신경 쓰지 않아도 된다.




전체 소스

using Android.App;
using Android.Widget;
using Android.OS;
using System;
using Android.Views;
using Java.Lang;
using Android.Hardware;
using static Android.Hardware.Camera;
using Android.Util;
using Android.Media;

namespace SimpleLighter
{
    [Activity(Label = "SimpleLighter", MainLauncher = true, Icon = "@drawable/Icon", Theme = "@android:style/Theme.NoTitleBar")]
    public class MainActivity : Activity
    {
        private bool isFlashOn = false;

        private bool hasFlash;

        private Camera camera;
        private Parameters mParams;

        private LinearLayout layout;
        
        protected override void OnCreate(Bundle savedInstanceState)
        {
            base.OnCreate(savedInstanceState);

            // Set our view from the "main" layout resource
            SetContentView(Resource.Layout.Main);
            
            layout = FindViewById<LinearLayout>(Resource.Id.layout);

            hasFlash = ApplicationContext.PackageManager.HasSystemFeature(Android.Content.PM.PackageManager.FeatureCameraFlash);

            GetCamera();
            layout.Click += delegate
              {
                  ChangeFlash();
              };
        }

        // 플래시 버튼 클릭 이벤트
        private void ChangeFlash()
        {
            if (!hasFlash)
            {
                AlertDialog alert = new AlertDialog.Builder(this).Create();
                alert.SetTitle("오류");
                alert.SetMessage("기기에 카메라 플래시가 존재하지 않습니다.");
                alert.Show();

            }
            FlashLight();
        }

        // 카메라
        private void GetCamera()
        {
            if (camera == null)
            {
                try
                {
                    camera = Camera.Open();
                    mParams = camera.GetParameters();
                }
                catch (RuntimeException ex)
                {
                    Log.Info("Error", ex.Message);
                }
            }
        }

        // 플래시
        private void FlashLight()
        {
            if (camera == null || mParams == null)
                return;
            if (isFlashOn)
            {
                mParams = camera.GetParameters();
                mParams.FlashMode = Parameters.FlashModeOff;
                camera.SetParameters(mParams);
                camera.StartPreview();
                isFlashOn = false;
                ChangeBackground();
            }
            else
            {
                mParams = camera.GetParameters();
                mParams.FlashMode = Parameters.FlashModeTorch;
                camera.SetParameters(mParams);
                camera.StartPreview();
                isFlashOn = true;
                ChangeBackground();
            }
        }

        // 버튼 크기 및 배경 조정
        private void InitializeSettings()
        {
            var metrics = Resources.DisplayMetrics;
            var heightInDp = ConvertPixelsToDp(metrics.HeightPixels);
            var widthInDp = ConvertPixelsToDp(metrics.WidthPixels);
        }

        // 픽셀에서 Dp로
        private int ConvertPixelsToDp(float pixelValue)
        {
            var dp = (int)((pixelValue) / Resources.DisplayMetrics.Density);
            return dp;
        }

        // 버튼 이미지 및 색상 변경
        private void ChangeBackground()
        {
            if (isFlashOn)
                layout.SetBackgroundColor(Android.Graphics.Color.White);
            else
                layout.SetBackgroundColor(Android.Graphics.Color.Black);
        }
    }
}


728x90