본문 바로가기

프로젝트/블루투스 무드등

[비전공자도 만들 수 있는 블루투스 무드등] 4. 안드로이드 앱 part 1-1 레이아웃 작성

안녕하세요? 닉네임간편입니다. 이번 시간부터는 본격적으로 아두이노를 스마트폰에서 제어할 수 있도록 안드로이드 앱을 만들어보겠습니다.


전체 소스는 여기에 있습니다.

https://github.com/creativeduck/MyLED

앱을 미리 사용해보고 계신 분들은, 이 링크를 타고 설치해주시면 됩니다.

https://play.google.com/store/apps/details?id=com.mybest.myled


우선 이번 시간에는 자바 코드를 작성하기에 앞서, 기본적인 앱의 레이아웃을 만들어보겠습니다.

레이아웃을 만드는 것도 꽤 복잡하고 어려운 작업이므로, 완성된 소스를 차례대로 분석해 설명하겠습니다.

우선 완성된 레이아웃은 다음과 같습니다.

 

 

완성 레이아웃

0. 전체 소스

<activity_main.xml>

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#fff0f0f0">

    <ImageButton
        android:id="@+id/btnReconnect"
        android:layout_width="29dp"
        android:layout_height="39dp"
        android:background="@drawable/bluetooth"
        app:layout_constraintBottom_toTopOf="@id/colorPick"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.96"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <ImageView
        android:id="@+id/colorPick"
        android:layout_width="match_parent"
        android:layout_height="400dp"
        android:src="@drawable/colorroll"
        app:layout_constraintBottom_toTopOf="@id/guideline1"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.81" />

    <com.google.android.material.imageview.ShapeableImageView
        android:id="@+id/selectedColor"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:background="@color/orange"
        app:layout_constraintBottom_toBottomOf="@id/colorPick"
        app:layout_constraintEnd_toEndOf="@id/colorPick"
        app:layout_constraintStart_toStartOf="@id/colorPick"
        app:layout_constraintTop_toTopOf="@id/colorPick"
        app:shapeAppearanceOverlay="@style/roundedCorners" />


    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.6867305" />

    <TextView
        android:id="@+id/currentState"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="#FF000000"
        android:textSize="23sp"
        android:text=""
        app:layout_constraintBottom_toBottomOf="@id/guideline1"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@id/guideline1" />

    <SeekBar
        android:id="@+id/bar"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:layout_margin="7dp"
        android:background="@drawable/seekbar"
        android:max="255"
        android:min="0"
        android:thumb="@drawable/thumb"
        app:layout_constraintBottom_toTopOf="@id/guideline2"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.461"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/currentState" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.8235294" />

    <ImageButton
        android:id="@+id/btnRainbow"
        android:layout_width="70dp"
        android:layout_height="70dp"
        android:background="@drawable/myrainbow"
        app:layout_constraintBottom_toTopOf="@id/guideline3"
        app:layout_constraintEnd_toStartOf="@+id/btnPower"
        app:layout_constraintHorizontal_bias="0.23"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/guideline2" />

    <ImageButton
        android:id="@+id/btnPower"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:background="@drawable/powerbtn"
        app:layout_constraintBottom_toTopOf="@id/guideline3"
        app:layout_constraintEnd_toStartOf="@id/btnColorPicker"
        app:layout_constraintStart_toEndOf="@id/btnRainbow"
        app:layout_constraintTop_toBottomOf="@id/guideline2" />

    <ImageButton
        android:id="@+id/btnColorPicker"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:background="@drawable/colorbtn"
        app:layout_constraintBottom_toTopOf="@id/guideline3"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@id/btnPower"
        app:layout_constraintTop_toBottomOf="@id/guideline2" />


    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.97811216" />

</androidx.constraintlayout.widget.ConstraintLayout>

 

현재 이것만 복사해서 사용한다면 오류가 날 것입니다. 여기에 있는 드로어블, 이미지, 색상 등의 없기 때문입니다. 해당 부분은 다음 포스팅에서 만들 것이므로 오류가 나더라도 당황하지 않으셔도 됩니다.

 

지금부터는 레이아웃 혹은 뷰의 종류에 따라 설명드릴 것이므로, 코드의 위치에 관계없이 동일한 종류의 뷰는 같이 설명할 것입니다. 따라서 예제를 공부하시는 데에 있어 혼란이 생기는 것을 방지하기 위해 전체 소스를 먼저 올려드렸습니다.

 

1. 제약 레이아웃

<androidx.constraintlayout.widget.ConstraintLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#fff0f0f0">

 

1) ConstraintLayout

이번 예제에서는 제약 레이아웃(ConstraintLayout)을 사용할 것입니다.

제약 레이아웃은 현재 안드로이드 스튜디오에서 자동으로 설정되는 디폴트 레이아웃이며, 처음에는 다소 어렵지만 이 레이아웃의 장점이 있으므로 저는 이 레이아웃을 사용하겠습니다.

 

제약 레이아웃은 말 그대로 화면에 배치될 뷰들(버튼, 이미지 등 화면에 보이는 것들을 의미한다고 생각하시면 됩니다)에 제약을 가해서 화면에 배치하는 것입니다. 예를 들어 사과와 배가 있다면, '사과는 배 옆에 둔다'라는 식으로 배치를 하는 것입니다.


2) xmlns

xmlns 로 시작하는 것은 레이아웃을 만들 때 사용되는 속성을 의미합니다.
위 코드에 있는 건 프로젝트 생성 시 자동으로 만들어지며, 프로젝트에 포함되어야 하는 속성이 있다면 적어야 하고, 그렇지 않다면 제거해도 무방합니다.

xmlns:android는 안드로이드 기본 SDK에 내장되어 있는 속성들을 사용할 때 필요합니다.

만일 기본 SDK에 내장되어있는 속성을 사용할 경우, android:layout_width처럼 사용합니다.

xmlns:app 은 외부 라이브러리에 있는 속성을 사용할 때 필요합니다.

만일 외부 라이브러리에 있는 속성을 사용할 경우, app:srcCompat처럼 사용합니다.

 

3) layout_width, layout_height

이들은 각각 각각 폭과 높이, 가로와 세로라고 보시면 됩니다.

이때 match_parent는 해당 뷰가 채울 수 있는 영역을 모두 차지하는 것입니다. 이때 채울 수 있다는 것의 의미는 다른 영역이 차지한 공간을 제외한 나머지 공간이 있을 때 해당 공간을 차지할 수 있다는 것입니다.

즉, 남는 공간이 있을 때 이 공간을 모두 차지하는 것입니다.

만일 wrap_content 로 설정하면 해당 뷰의 크기에 맞게 그 크기가 결정됩니다. 그러나 현재 ConstraintLayout은 가장 상위에 존재하는, 즉 다른 모든 뷰들을 포함하는 부모 레이아웃이므로 폭과 높이 모두 match_parent로 설정하겠습니다. 그러면 앱 내의 화면 모두를 차지하게 됩니다.

 

4) background

뷰의 배경을 설정합니다. 이때 배경은 색상도 되고, 이미지도 가능합니다.

색상을 선택할 때에는 색상표의 헥스 코드 방식에다 앞에 투명도를 앞에 붙여 #ff000000처럼 표시합니다.

각각 두 자리씩 나눠서 투명도 / 빨간색 / 초록색 / 파란색이며, 16진수를 사용하므로 f가 가장 큰 값입니다.

즉, 만일 투명하지 않다면 투명도 부분에 FF를 입력하고, 완전한 빨간색을 표시하려면 ff/ff/00/00 이런 식으로 입력합니다. 또한 색을 입력할 때에는 #을 붙여줍니다.

저는 회색 계열의 배경색을 설정할 것이므로 #fff0f0f0으로 색을 설정했습니다. 그러나 이는 여러분들의 취향에 맞게 자유롭게 설정해주셔도 좋습니다.

 

2. 이미지 버튼(ImageButton)

<!--블루투스 연결 버튼-->
<ImageButton
        android:id="@+id/btnReconnect"
        android:layout_width="29dp"
        android:layout_height="39dp"
        android:background="@drawable/bluetooth"
        app:layout_constraintBottom_toTopOf="@id/colorPick"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.96"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

<!--레인보우 버튼-->        
<ImageButton
        android:id="@+id/btnRainbow"
        android:layout_width="70dp"
        android:layout_height="70dp"
        android:background="@drawable/myrainbow"
        app:layout_constraintBottom_toTopOf="@id/guideline3"
        app:layout_constraintEnd_toStartOf="@+id/btnPower"
        app:layout_constraintHorizontal_bias="0.23"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/guideline2" />

<!--전원 버튼-->    
<ImageButton
        android:id="@+id/btnPower"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:background="@drawable/powerbtn"
        app:layout_constraintBottom_toTopOf="@id/guideline3"
        app:layout_constraintEnd_toStartOf="@id/btnColorPicker"
        app:layout_constraintStart_toEndOf="@id/btnRainbow"
        app:layout_constraintTop_toBottomOf="@id/guideline2" />

<!--컬러피커 버튼-->    
<ImageButton
        android:id="@+id/btnColorPicker"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:background="@drawable/colorbtn"
        app:layout_constraintBottom_toTopOf="@id/guideline3"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@id/btnPower"
        app:layout_constraintTop_toBottomOf="@id/guideline2" />

예제에 사용될 이미지버튼은 총 4개로, 구성요소 간 차이가 없으므로 맨 마지막에 있는 컬러피커 버튼을 예시로 설명하겠습니다.

 

0) 이미지버튼에 대해

이미지버튼은 버튼의 기능을 수행하면서 이미지를 배경으로 할 수 있는 뷰입니다. 특히 버튼이 클릭되었을 때와 그렇지 않을 때 다른 이미지를 주어 효과를 줄 수 있으므로 유용하게 사용됩니다.

 

1) id

id는 XML 파일 내에서 다른 뷰들과 구분할 때, 그리고 자바 소스 코드에서 뷰를 참조할 때 사용합니다.

id를 정의할 입력할 때에는 '@+id/아이디' 식으로 입력해야 하며, 밑의 코드에서 볼 수 있듯 해당 뷰를 XML 파일 내에서 id로 참조할 때는 '@id/아이디'로 참조합니다.

id는 어떤 값을 입력해도 무방하나 나중에 해당 뷰를 참조하거나 코드를 수정할 때 어떤 기능을 하는 뷰인지 알아보기 쉽도록 작성하는 것이 좋습니다. 따라서 전 컬러피커를 하는 버튼이라는 의미에서 btnColorPicker로 작성했습니다.

 

2) layout_width, layout_height

뷰의 폭과 넓이는 match_parent, wrap_content 이외에도 직접 값을 입력해서 설정할 수 있습니다. 이때 주로 dp라는 단위를 이용하는데, dp는 밀도에 독립적인 픽셀입니다. 쉽게 말해, 특정 화면에 구애받지 않고 레이아웃에 배치할 수 있도록 합니다.
스마트폰은 기기마다 디스플레이 크기가 다르기 때문에, 이를 하나하나 신경쓰면서 레이아웃을 구성하는 건 굉장히 어려운 일일 것입니다.
그러나 dp 단위는 기기의 밀도, 화면 크기와 독립적이기 때문에, 화면의 크기가 달라져도 레이아웃에 배치한 비율과 동일한 비율로 모든 디스플레이에 보일 수 있습니다.
따라서 dp 사용을 권장합니다.

 

3) background

앞서 설명드렸듯 배경을 설정합니다. 이때 '@drawable/이미지 이름' 형식으로 이미지를 배경으로 설정할 수 있습니다. 이때 drawable 폴더 내부에 이미지 파일이 존재해야 합니다.

drawable 폴더는 프로젝트 내부에 있으며, 프로젝트 -> app -> src -> main -> res -> drawable 폴더로 가시면 됩니다.

예를 들어 바탕화면에 있는 프로젝트의 경우

'C:\Users\[이름]\Desktop\[프로젝트 이름]\app\src\main\res\drawable' 경로를 찾아가시면 됩니다.

이때 이미지 파일의 이름은 소문자로 하셔야 합니다. 물론 언더스코어(_)와 숫자는 가능합니다.

 

사용된 사진

각 아이디별 사용된 사진은 다음과 같습니다. 해당 이미지는 픽사베이(https://pixabay.com/ko/)라는 사이트에서 구하거나 직접 만들어서 사용했습니다. 이 이미지가 아니더라도 원하시는 이미지를 사용하지면 되겠습니다.

 

 

4) 제약 레이아웃 배치

제약 레이아웃은 앞서 설명한 듯 어떤 제약을 가해 뷰를 배치합니다.

이때 사용되는 것이 다음과 같습니다.

 

a) app:layout_constraintBottom_toTopOf="@id/guideline3"

-> 이 뷰의 아래쪽 제약은 guideline3의 위쪽입니다. 따라서 이 뷰는 guideline3의 위쪽까지 위치할 수 있습니다.

 

b) app:layout_constraintEnd_toEndOf="parent"

-> 이 뷰의 끝쪽(오른쪽) 제약은 부모 레이아웃의 끝쪽입니다. 이때 부모 레이아웃이 최상위 레이아웃이므로, 이 뷰는 앱 화면의 오른쪽 끝부분까지 위치할 수 있습니다.

 

c) app:layout_constraintStart_toEndOf="@id/btnPower"

-> 이 뷰의 시작쪽(왼쪽) 제약은 btnPower의 끝쪽(오른쪽)입니다. 이 뷰는 btnPower의 끝쪽까지 위치할 수 있습니다.


d) app:layout_constraintTop_toBottomOf="@id/guideline2"

-> 이 뷰의 위쪽 제약은 guideline2의 아래쪽입니다. 이 뷰는 guideline2의 아래쪽까지 위치할 수 있습니다.

 

배치 예시

 

이 그림처럼, 이 4개의 제약을 바탕으로 해당 뷰의 위치가 결정되는 것입니다.

btnColorPicker는 부모 레이아웃의 오른쪽 끝에, guideline2 밑에, btnPower 오른쪽 끝에, guideline3 위에 위치한 것을 확인할 수 있습니다.

따라서 한 뷰의 배치를 위해선 상하좌우 총 4개의 제약이 필요하며, 이 제약이 있어야만 의도한 대로 배치가 됩니다.

또한 배치의 종류는 다양하므로 상황에 맞게 응용하시면 되겠습니다.

 

 

*XML 파일에서 주석 처리를 할 때에는 주석 처리할 내용을 선택한 후 Ctrl + / 를 눌러서 주석 처리를 할 수 있습니다.

만약 주석 처리할 내용이 한 줄이 아니라면, 주석 처리할 내용을 모두 드래그하신 후 Ctrl + Shift + / 를 누르시면 되겠습니다.

이는 윈도우에 대한 내용이며, 맥의 경우 Ctrl 대신 Cmd를 누르시면 되겠습니다.

 

*안드로이드에선 left와 right를 start와 end로 사용하기도 합니다. 이러한 기능은 오른쪽에서 왼쪽으로 읽는 언어권을 위한 속성이 추가됨에 따라 등장했습니다. 해당 언어권에선 start가 right, end가 left에 대응됩니다. 우리나라에선 그 반대로 대응됩니다.

 

3. 마무리

이번 시간에는 제약 레이아웃과 이미지 버튼에 대해 알아보았습니다. 다음 시간에는 나머지 뷰에 대해 알아보겠습니다.

오늘 알려드렸던 정보가 많은 도움이 되었길 바랍니다.

728x90
반응형