나 만의 웹 브라우저

 

기능 소개

웹 서핑용으로 사용할 수 있고 나만의 몇 몇 메뉴와 컨텍스트 메뉴를 가지고 있습니다.

 

핵심이 되는 주제

웹 뷰 (인터넷 사용 권한 필요)

메뉴 구성

컨텍스트 메뉴 구성

암시적 인텐트 사용

 

실습

1) 빈 액티비티를 생성 (앱 이름 : MyWeb) & Anko 라이브러리 추가

     잘 모르면 이전의 앱 제작 과정을 참고하세요~

 

2) 레이아웃 작업

 

① Plain Text 뷰 배치 (id: URLEditText , inputType: textUri, hint: http://, imeOptions: actionSearch)
  (입력 자료형을 textUri로 선택하면 입력할때 소프트키보드도 URL 입력에 편리한 자판 배열로 표시됩니다) 
  (또 hint를 설정하면 사용자에게 웹주소를 입력해야 함을 알려줄 수 있죠)
  actionSearch : 소프트키보드의 서치아이콘(돋보기 처럼 생긴...) 활성화 시켜 줍니다.

② webView 뷰 배치 (id: webView)
  인터넷 권한 추가 (앱 설치시 사용자에게 권한 허용을 요청하게 됨)
 

  -- AndroidManifest.xml

<manifest xmlns:...>
    ...
    <uses-permission android:name="android.permission.INTERNET" />
    ...
</manifestxmlns>

 

③ 코딩 (웹뷰를 위한)
  -- MainActivity.kt

class MainActivity : AppCompatActivity() {

   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContentView(R.layout.activity_main)

       webView.apply {
          settings.javaScriptEnabled = true      // 자바스크립트 사용 설정
          webViewClient = WebViewClient()     // webViewClient 객체를 생성하여 전달
                                                  // 이 행이 없으면 폰 자체 웹 브라우저가 동작함
       }
       webView.loadUrl("http://www.google.com")  // 구글페이지 로드

       URLEditText.setOnEditorActionListener { _, actionId, _ ->  // 자동완성 기능으로 보면 v, actionId, event
                                                      // (반응한 뷰, 액션ID, 이벤트)
                                                      // 세 개의 인수를 사용하지만 여기선 actionId만 사용
                                                      // (사용하지 않는 인자는 _로 작성)
                                                      // 이 리스너는 editText에 글자가 입력될 때마다 호출됨
         if (actionId == EditorInfo.IME_ACTION_SEARCH) {    // 검색버튼이 눌렸는가?
            webView.loadUrl(URLEditText.text.toString())
            true                                            // true를 반환하며 이벤트 종료
         } else {
            false
         }
            
     }

  override fun onBackPressed() {    // 액티비티에서 뒤로가기 키 이벤트 onBackPressed() 재정의(오버라이드)
     if (webView.canGoBack()) { 
	       webView.goBack()           // 이전페이지로 갈 수 있으면 이전페이지로 이동하고,
     } else {
	       super.onBackPressed()       // 그렇지 않다면 본래의 동작을 수행(즉, 종료)함
     }
  }
     
}

 

일단 이쯤에서 실행하면 기본적인 웹 서핑이 가능합니다.

 

 

이제 옵션 메뉴와 컨텍스트 메뉴를 만들어 볼까요? 일단 메뉴는 별도의 디렉토리로 관리합니다.

  File > New > Android Resource Directory 로 리소스 디렉토리 생성 (Resource type : menu)

 

 

④ 옵션 메뉴 (앱 우측 상단에 …으로 표시되는 메뉴)

  i) 메뉴용 리소스 준비
    생성된 Android > app > res > menu 마우스 우클릭 > Menu resource file (File name: main)

      이렇게 하면 main.xml이 생성됩니다.


    메뉴에 사용할 벡터 이미지 준비 (여기서는 홈 버튼에 사용할 이미지를 준비해야 하는데,

      Vector Asset의 Clip Art의 검색 창에서 home으로 검색하면 곧바로 찾아줍니다~)

  ii) 메뉴 구성하기
    팔레트 창에서 'Menu Item'뷰를 끌어다 컴포넌트트리 창 menu 하위에 배치 (title: 검색사이트)
      검색사이트 메뉴 아이템 하위에 'Menu'뷰를 끌어다 배치
        Menu 뷰 하위에 세 개의 'Menu Itemp'뷰를 끌어다 배치
           (id: action_daum, title: 다음)
           (id: action_google, title: 구글)
           (id: action_naver, title: 네이버)


같은 방법으로 검색사이트 레벨의 메뉴 아이템과 그 하위에 세 개의 메뉴 아이템들을 갖는 메뉴를
      하나 더 생성 (메뉴아이템title: 개발자 정보) 하세요.
        (id: action_call, title: 전화하기)
        (id: action_send_text: 문자보내기)
        (id: action_email: 이메일보내기)

검색사이트 레벨의 메뉴 아이템을 하나 더 추가 하세요.
        (id: action_home, title: Home, icon: 위에서 만든 집 모양 벡터이미지)
        툴바 밖으로 이 메뉴 아이템을 노출시키기 위해 showAsAction을 ifRoom으로 설정해 줍니다.
        (아래 그림에서 보면 집 모양 아이콘이 메뉴에서 빠져나와 앱의 툴바 영역에 나타났음)
         
    ※ 참고

       never

       ifRoom(툴바에 여유가 있을때만)

       withText(글자와 아이콘을 함께 표시)
       collapseActionView (액션 뷰와 결합하면 축소되는 메뉴 생성 가능)

 

[ 최종적인 메뉴 모양 ]

 

  iii) 코딩 (메뉴를 위한)

   -- MainActivity.kt

class MainActivity : AppCompatActivity() {
     ...

     // 액티비티에서 onCreateOptionsMenu()를 오버라이드하여 메뉴 리소스 파일을 지정하면 메뉴가 표시됨
     override fun onCreateOptionsMenu() : Boolean { 
        menuInflater.inflate(R.menu.main, menu)       // MainActivity의 메뉴로 등록 (* inflate 바람넣다. 부풀리다(과장하다))
        return true                          // 반드시 true를 반환하여 액티비티가 있음을 인식시켜줘야 함
     }
     
     // 각 옵션 메뉴 클릭 이벤트 처리기(이벤트 핸들러)
     override fun onOptionsItemSelected(item: MenuItem): Boolean {
        when (item?.itemId) {
            R.id.action_daum -> {
                webView.loadUrl("http://www.daum.net")
                return true        // 처리를 끝낸후엔 정상종료 됐음을 알리기 위해 true를 반환
                                 //  (그리고 안드로이드에서는 자신의 작업을 하는 경우를 제외한 모든 경우에
                                 //    super 메소드를 호출하는 것이 기본규칙임
            }
            R.id.action_google, R.id.action_home -> {          // 구글 메뉴 또는 홈버튼
                webView.loadUrl("http://www.google.com")
                return true
            }
            R.id.action_naver -> {
                webView.loadUrl("http://www.naver.com")
                return true
            }
            R.id.action_call -> {
                // 전화 걸기 코드 - '암시적 인텐트'를 사용 (예제 끝 부분에 별도로 설명)
                val intent = Intent(Intent.ACTION_DIAL)
                intent.data = Uri.parse("tel:02-1234-5678")
                if (intent.resolveActivity(packageManager) != null) {
                    startActivity(intent)
                }
                return true
            }
            R.id.action_send_text -> {
                // 메시지 발송 코드 (*k 마지막에 별도 정리)
                return true
            }
            R.id.action_email -> {
                // 이메일 발송 코드 (*k 마지막에 별도 정리)
                return true
            }
        }
        return super.onOptionsItemSelected(item)
     }
  }

 

⑤ 컨텍스트 메뉴 (특정 뷰를 길게 누르고 있을 때 나타나는 메뉴)
  

   이번에는 컨텍스트 메뉴를 추가해 봅시다~


  i) 컨텍스트 메뉴용 리소스 준비
    Android > app > res > menu 마우스 우클릭 > Menu resource file (File name: context)     --> context.xml이 생성됩니다. 

  ii) 메뉴 구성하기
    팔레트 창에서 'Menu Item'뷰 2개를 끌어다 컴포넌트트리 창 menu 하위에 배치 (title: 검색사이트, 기본 브라우저에서 열기)

[ 컨텍스트 메뉴 - 실행하면 길게 누른 지점에 메뉴가 표시됩니다 ]

 

  iii) 코딩 (컨텍스트 메뉴를 위한)

   -- MainActivity.kt

// *1, *2 순으로 작업
   class MainActivity : AppCompatActivity() {
      override fun onCreate(savedInstanceState: Bundle?) {
         ... 
         registerForContextMenu(webView)            // *2
      }

      override fun onCreateContextMenu(             // *1
         menu: ContextMenu?,
         v: View?,
         menuInfo: ContextMenu.ContextMenuInfo?
      ) {
         super.onCreateContextMenu(menu, v, menuInfo)
         menuInflater.inflate(R.menu.context, menu)        // MainActivity의 컨텍스트 메뉴로 등록
      }

      // 컨텍스트 메뉴 클릭 이벤트 처리
      override fun onContextItemSelected(item: MenuItem): Boolean {
         when (item?.itemId) {
             R.id.action_share -> {
                // 페이지 공유 코드 (*k 마지막에 별도 정리)
                return true
             }
             R.id.action_browser -> {
                // 기본 웹 브라우저에서 열기 코드 (*k 마지막에 별도 정리)
                return true
             }
         }
         return super.onContextItemSelected(item)
      }
   }

 

※ 암시적 인텐트
미리 정의된 인텐트들을 말합니다.

사용 예들) 출처: https://developer.android.com/guide/components/intents-common 에서 더 많은 예를 볼 수 있어요.
그런데, Anko 라이브러리를 사용하면 아래의 사용 예들 보다 훨씬 간편하게 코딩할 수 있죠 (거의 한 줄로 처리 가능!)

(일단 암시적 인텐트 사용 예들을 볼까요)

// 전화 걸기
val intent = Intent(Intent.ACTION_DIAL)                // Intent 클래스에 정의된 액션 중 전화거는 액션을 선택한 것임
intent.data = Uri.parse("tel:02-1234-5678")             // "tel:"로 시작하는 Uri는 전화번호를 나타내는 국제표준임
if (intent.resolveActivity(packageManager) != null) {   // intent.resolveActivity()는 인텐트를 수행하는 액티비티가 있는지를 검사
                                                   // 전화 앱이 없는 태블릿 같은 기기에서는 null값을 반환함
  startActivity(intent)
}

// 문자열 보내기
val intent = Intent(Intent.ACTION_SEND)
intent.apply {
   type = "text/plain"
   putExtra(Intent.EXTRA_TEXT, "보낼 문자열")
   var chooser = Intent.createChooser(intent, null)
   if (intent.resolveActivity(packageManager) != null) {
      startActivity(chooser)
   }
}

// 웹 브라우저 띄우기
val intent = Intent(Intent.ACTION_VIEW)
intent.data = Uri.parse("http://www.google.com")
if (intent.resolveActivity(packageManager) != null) {
   startActivity(intent)
}

...
...

 

(이번에는 Anko 라이브러리를 사용한 예 입니다)

Anko라이브러리를 사용한 예)
makeCall(전화번호)             // 전화 걸기
sendSms(전화번호, [문자열])   // 문자 보내기
browse(url)                     // 웹 브라우저에서 열기
share(문자열, [제목])           // 문자열 공유
email(받는메일주소, [제목], [내용])   // 이메일 보내기

 

와! 놀랍도록 간단하군요!!

 

※ 상기 본 프로그램 코드에서 생략했던 코드들을 아래에 정리 했습니다. 채워 넣으세요!

   주의) 특별한 경우가 아니라면 전화걸기에 암시적 인텐트를 사용하지 않을 것을 권장합니다.
         별도의 권한을 필요로 하고 전화번호 입력까지만 제공하면 사용자 의지대로 전화를 걸면 되기 때문이에요.

// 메시지 발송 코드
sendSMS("02-1234-5678", webView.url + "에 들어가봐!")

// 이메일 발송 코드
email("test@daum.net", "이 쇼핑몰이 젤 좋아!", webView.url)

// 페이지 공유 코드
share(webView.url)

// 기본 웹 브라우저에서 열기 코드
browse(webView.url)

  
실행결과) '메뉴>검색사이트>다음'을 눌러본 상태입니다.

잘 동작하는 군요!  수고 하셨습니다~!

 

 

<<< 이전 글 보기      다음 글 보기 >>>

 

 

 

본 블로그를 찾은 분들에게 오늘도 축복 있으시길...

스톱워치(초시계)

 

기능 소개

타이머 시작, 일시정지, 초기화, 랩타임 표시등의 기능을 갖는 초시계를 제작해 봅니다.

 

핵심이 되는 주제

timer

백그라운드 스레드

runOnUiThread

ScrollView

FloatingActionButton

 

 

실습

1) 빈 액티비티를 생성 (앱 이름 : StopWatch) 

     벡터 이미지를 사용할 것이므로 vectorDrawables.useSupportLibrary = true 설정

      (모듈수준 bundle.gradle의 defaultConfig{} 에 작성하고, 'New Sync' 클릭해 주는 거 있지 않으셨죠?^^ )

 

2) 레이아웃 작업

 

  ① 두 개의 TextView (초, 백분의 1초 시간 표시용)를 배치합니다.

       (id : 각각 secTextView, milliTextView)

 

      참고) 뷰의 컨텍스트 메뉴에서 기준라인을 보이게 함으로써 두 뷰의 글자 하단을 정렬할 수 있어요.

       

[ 텍스트 기준선 정렬 ]

 

② 세 개의 벡터이미지 준비해 주세요.

     play arrow(시작), pause(일시정지), refresh(초기화)

     

[ 세 개의 벡터 이미지 ]

 

③ 2개의 FloatingActionButton(FAB)을 배치해야 하는데요.

   - FAB는 벡터 이미지로 깔끔한 버튼을 만들기에 적합한 이미지합성 버튼입니다.

   - 구글 머티리얼 디자인에 자주 사용됩니다.
   - 컴포넌트 팔레트 창에서 처음 이 컴포넌트를 추가하려고 하면 네트워크 상에서 다운로드 받도록 되어 있습니다.

      (다운로드 받으세요)

   - 다운로드가 끝났으면 FAB를 드래그앤드롭으로 가져다 놓고,

     벡터 이미지(시작, 초기화 이미지)를 선택하고 id와 색상을 적당히 설정하세요.

     총 2개의 버튼을 만들면 됩니다.

      참고) 시작 버튼 id: playFab, 초기화 버튼 id: resetFab

             

[ 두 개의 FAB ]

  ④ 1개의 Button (랩 타임용)을 배치하세요 - id: labButton

  ⑤ 중앙에 ScrollView (랩 타임 표시용)를 배치해 주세요.
     수직으로 차곡차곡 쌓이는 레이아웃을 LinearLayout이라고 합니다.
     (컴포넌트 트리창을 보면 ScrollView 하위에 LinearLayout이 들어있을 거예요. 
      ScrollView 뷰는 자식 뷰룰 하나만 갖는 특수한 뷰이고,
      LinearLayout 뷰는 여러 개의 자식 뷰를 갖습니다)
      LinearLayout 안에 동적으로 타임랩 값들을 새로운 형태로 쌓이도록 만들것입니다.

     컴포넌트 트리 창에서 LinearLayout 뷰를 선택하고 id를 입력해 주세요. ( 예: lapLayout )

[ 최종 레이아웃 ]

  

 

 

3) 코딩

 

timer (타이머)

    안드로이드의 두 가지 스레드 모드가 있습니다.

       ① 메인스레드 (일반적인 UI 가능)

       워커스레드 (작업 시간이 오래 걸리고 화면에 표시되지 않음. 당연히 UI 조작을 불가)

 

  타이머 코딩 형식

    timer (period = 1000) {        // 1000 = 1

        // 워커스레드 (UI조작 불가)

        runOnUiThread {

            // 메인스레드 (UI조작 가능)

        }

    }

 

 

- MainActivity.kt

 

package com.tistory.stopwatch

import android.os.Bundle
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*
import java.util.*
import kotlin.concurrent.timer

class MainActivity : AppCompatActivity() {


    private var time = 0
    private var timerTask: Timer? = null      // null을 허용
    private var isRunning = false
    private var lap = 1

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        playFab.setOnClickListener {
            isRunning = !isRunning

            if (isRunning) { start() } else { pause() }
        }

        labButton.setOnClickListener {
            recordLapTime()
        }

        resetFab.setOnClickListener {
            reset()
        }
    }

    private fun start() {
        playFab.setImageResource(R.drawable.ic_pause_black_24dp)   // 일시정지 이미지

        timerTask = timer(period=10) {       // 타이머 인터벌 10 ms
            time++

            val sec = time / 100
            val milli = time % 100

            runOnUiThread {                   // UI 조작이 가능한 블럭
                secTextView.text = "$sec"
                milliTextView.text = "$milli"
            }
        }
    }

    private fun pause() {
        playFab.setImageResource(R.drawable.ic_play_arrow_black_24dp)   // 시작 이미지

        timerTask?.cancel()                  // 실행중인 타이머 취소
    }

    private fun recordLapTime() {
        if (!isRunning) return         // 타이머가 실행 중이 아니라면 리턴
        val lapTime = this.time
        val textView = TextView(this)      // 동적으로 TextView 생성
        textView.text = "$lap LAB : ${lapTime / 100}. ${lapTime % 100}"

        lapLayout.addView(textView, 0)     // 0 : 맨 위쪽에 추가
        lap++
    }

    private fun reset() {
        timerTask?.cancel()       // 실행중인 타이머 취소

        // 모든 변수 초기화
        time = 0
        isRunning = false
        playFab.setImageResource(R.drawable.ic_play_arrow_black_24dp)
        secTextView.text = "0"
        milliTextView.text = "0"

        // 모든 랩타임 기록 삭제
        lapLayout.removeAllViews()
        lap = 1
    }

}

 

간단하지만 실행해보면 훌륭하게 동작하는 타이머가 완성됐습니다!

이번 프로그램은 비교적 간단했네요. 

 

다음에는 자신만의 웹 브라우저를 제작해 봅시다~

 

 

<<< 이전 글 보기      다음 글 보기 >>>

 

 

 

본 블로그를 찾은 분들에게 오늘도 축복 있으시길...

비만도 계산기

 

기능 소개

키와 몸무게를 입력하고 결과보기 버튼을 누르면 새 화면에서 비만도를 문자와 그림으로 표시합니다.

부수 기능으로 마지막으로 입력한 키와 몸무게 자료를 저장합니다.

 

핵심이 되는 주제

입력 및 출력 두 개 화면('액티비티')을 사용합니다.

두 화면 사이에 데이터 전달은 인텐트라는 화면 전환을 사용합니다.

SharedPreference로 자료를 저장합니다.

 

기타 : Anko 라이브러리 사용 방법 ( 라이브러리 의존성 추가)

        벡터 이미지 사용 방법

실습

1) 빈 액티비티를 갖는 프로젝트 생성 (앱 이름 : BMICalculator) 

2) 코드 작성을 쉽게 해주는 Anko 라이브러리 추가 
    
참고: Anko 라이브러리의 구성 
 ① Anko Commons (인텐트, 다이얼로그, 로그)  <-- 우리가 사용할 모듈  
 ② Anko Layouts (레이아웃) 
 ③ Anko SQLite (SQLite) 
 ④ Anko Coroutines (코루틴) 

프로젝트 탐색기 창에서 모듈 수준의 그레이들 파일인 build.gradle 파일을 더블 클릭하여 편집.

 (파일명 뒤에 Module 수준과 Project 수준의 구분이 표시되어 있음)

dependencies 항목에 다음의 Anko 라이브러리를 추가.

 ('라이브러리 의존성 추가'라고 하며 안드로이드 스튜디오에서 자동 다운로드 설치를 하게 됨)

   implementation "org.jetbrains.anko:anko:$anko_version"

[ Anko 라이브러리 추가 ]

 

프로젝트 탐색기 창에서 프로젝트 수준의 그레이들 파일인 build.project 파일을 더블 클릭하여

Anko 라이브러리 버전을 변수로 지정.

 

buildscript {

    ext.kotlin_version = '1.3.50'

    ext.anko_version='0.10.5'

    repositories {

        google()

        jcenter()

       

    }

    dependencies {

    ....

    }

}

 

추가한 그레이들 파일들을 적용하기 위해 에디트창 탭 이름 바로 아래에 'Sync Now'를 클릭해서

프로젝트를 재 빌드.

 

 

3) 레이아웃 작업 1 - , 몸무게 입력 화면

 

'Plain Text' 배치

  ID : heightEditText

  input Type : (숫자만 입력할 있도록) number

  hint : (입력 전에 표시할 문자열) (cm)  --> 문자열이 보이도록 text 란의 Name 삭제

 

Plain Text 뷰도 되지만, 숫자만 입력할 거니까 이번에는 'Number' 뷰를 배치

  ID : weightEditText

 

'Button' 배치

  ID : resultButton

[ 입력 화면 ]

 

 

4) 레이아웃 작업 2 - 결과 화면

에디터 창을 디자인이 아닌 텍스트 모드로 전환한 후, 결과를 출력할 새로운 액티비티 추가.
  File > New > Activity > Empty Activity
  (Activity Name : ResultActivity)

① activity_result.xml 창에 TextView 뷰 배치
   ID : resultTextView
   textAppearance : appCompat.Large

[ 결과 화면 ]

② 이미지 뷰 배치
  
  참고) 안드로이드 스튜디오에서 제공하는 이미지 파일들
    비트맵 이미지: PNG, JPG
    벡터 이미지 : SVG, EPS

   벡터 이미지를 사용해 보자. (에셋 스튜디오에서 생성할 있음)
      프로젝트 창 > res 폴더 우클릭 > New > Vector Asset에서 벡터 이미지 생성
      Clip Art: 에서 원하는 이미지 선택
   같은 방법으로 비만도 정상, 비만, 저체중 세 가지 이미지를 추가.

   
activity_result.xml 화면에서 ImageView 뷰를 배치
   project 항목에서 '정상'용 이미지를 선택하고 OK   (: 스마일 아이콘)
   ImageView 속성 : 사이즈 조정 (: 가로(layout_width), 세로(layout_height : 각각 100 dp),

                         색상(tint) 조정
   
※ 벡터 이미지는 vectorDrawable 리소스로 분류하는데,

   백터드로어블은 Android 5.0부터 동작함.
   우리는 4.4버전으로 생성했으므로 모듈 수준의 build.gradle 파일에

  다음을 추가해 이를 지원케 해야 함. (Sync Now 잊지말고!)

    defaultConfig {
       vectorDrawables.useSupportLibrary = true
    }

[ 결과 화면 ]

 


5) 뒤로가기 기능 추가

 

결과 화면에서 다시 입력화면으로 돌아가기 위한, '뒤로가기' 기능을 상단에 추가해 보죠.

 

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.tistory.bmicalcurator">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".ResultActivity" android:parentActivityName=".MainActivity"></activity>
    </application>

</manifest>

앱을 실행해 보면, 결과화면 상단에 뒤로가기 링크가 표시되고, 터치하면 입력 화면으로 되돌아 갑니다.

[ 뒤로가기 링크 ]

 

 


6) 코딩

간단한 데이터와 함께 다른 화면(액티비티) 구동해 주는 인텐트를 이용합니다.

 

  

 

<< 자료 입력 화면 MainActivity >>

-- MainActivity.kt (코틀린 일반 코딩)

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import android.content.Intent     // 자동으로 임포트됨
import kotlinx.android.synthetic.main.activity_main.*  // 레이아웃 정보가 자동으로 임포트되어 있음
                                // 이 덕택에 앞에서 추가했던 텍스트뷰나 버튼 사용이 가능한 것임
                                // 자동 임포트는 'kotlin-android-extensions' 플러그인에 의한 기능임

class MainActivity : AppCompatActivity() { 

    override fun onCreate(savedInstanceState: Bundle?) { 
        super.onCreate(savedInstanceState) 
        setContentView(R.layout.activity_main) 

        loadData()

        resultButton.setOnClickListener { 
            saveData(heightEditText.text.toString().toInt(), weightEditText.text.toString().toInt())
                    
            val intt = Intent(this, ResultActivity::class.java) 
            startActivity(intt) 
        } 
    } 
    
    // SharedPreference로 자료 저장
    private fun saveData(h: Int, w: Int) {
        val pref = PreferenceManager.getDefaultSharedPreferences(this)   // 프리퍼런스 객체 생성
        val editor = pref.edit()         // 에디터 객체 얻어오기
                                     // 에디터 객체는 프리퍼런스 객체에 데이터를 넣어주는 역할을 함

        editor.putInt("HEIGHT",  h)            // 키-값 쌍으로 자료 저장
             .putInt("WEIGHT", w)
             .apply()                        // 적용
    }

    // 자료 읽어 오기
    private fun loadData() {
        val pref = PreferenceManager.getDefaultSharedPreferences(this)   // 프리퍼런스 객체 생성
        val h = pref.getInt("HEIGHT", 0)    // 0은 값이 없을 때의 디폴트 설정 값
        val w = pref.getInt("WEIGHT", 0)

        if (h !=0 && w!=0) {
            heightEditText.setText(h.toString())
            weightEditText.setText(w.toString())
        }
    }

} 

 

 

-- MainActivity.kt (Anko 코딩)

 

우리는 Anko 라이브러리를 추가해서 빌드하고 있는데, Anko 라이브러리를 경우 버튼 리스너

다음과 같이 간결하게 적어도 됩니. (, anko 라이브러리를 임포트해 줘야 )

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*
import org.jetbrains.anko.startActivity   // 추가할 것

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        
        resultButton.setOnClickListener {
            saveData(heightEditText.text.toString().toInt(), weightEditText.text.toString().toInt())

            // Anko 코딩된 부분
            startActivity<ResultActivity>(
                "weight" to weightEditText.text.toString(),
                "height" to heightEditText.text.toString()
            ) 
        }
    }

    ...
}

 

 

<< 결과 화면 ResultActivity >>

 

-- ResultActivity.kt onCreate()

 

import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_result.*

class ResultActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_result)

        val h = intent.getStringExtra("height").toInt()      // 인텐트에서 자료 빼오기
        val w = intent.getStringExtra("weight").toInt()
        val bmi = w / Math.pow(h / 100.0, 2.0)

        when {
            bmi >= 35 -> resultTextView.text = "고도 비만"
            bmi >= 30 -> resultTextView.text = "2단계 비만"
            bmi >= 25 -> resultTextView.text = "1단계 비만"
            bmi >= 23 -> resultTextView.text = "과체중"
            bmi >= 18.5 -> resultTextView.text = "정상"
            else -> resultTextView.text = "저체중"
        }

        when {
            bmi >= 23 -> imageView.setImageResource(R.drawable.ic_sentiment_very_dissatisfied_black_24dp)
            bmi >= 18.5 -> imageView.setImageResource(R.drawable.ic_sentiment_satisfied_black_24dp)
            else -> imageView.setImageResource(R.drawable.ic_sentiment_dissatisfied_black_24dp)
        }

        Toast.makeText(this, "BMI : $bmi", Toast.LENGTH_SHORT).show()  // Toast 메시지로 BMI 값 출력
                                                                    // Anko로는 toast("BMI : $bmi")
}

 

 

실행 결과

  [ 입력 화면 ]
[ 결과 화면 ]

 

 

Anko 라이브러리를 추가하며 실습해 봤던, 깃허브를 이용한 '라이브러리 의존성 추가'는 참 편리하고 효과적인 것 같습니다.

 

이 번 실습을 두 세 차례 차근 차근 따라해 보면, 흐름이 파악되실 거예요. 이렇게해서 첫 번째 안드로이드 앱을 만들어 봤습니다. 수고 하셨습니다~

 

 

<<< 이전 글 보기      다음 글 보기 >>>

 

 

 

 

본 블로그를 찾은 분들에게 오늘도 축복 있으시길...

[코틀린의 고급 문법 2]

고급 문법 마지막 시간입니다, 힘내자구요~~ 화이팅!

 

람다식

람다식이란 함수를 간결하게 표시한 형태입니다. 익명 클래스나 익명 함수를 간결하게 표현할 수 있어 편리합니다.

코드가 간결해져서 좋긴하지만, 남발할 경우 가독성이 떨어져 디버깅이 힘들어 질 수도 있습니다.

아래 세 가지 표현은 모두 같은 의미입니다. (코딩 간결화의 변천 과정이라고 생각해도 됨)

 

① 일반 함수
fun add(x: Int, y: Int) : Int { return  x + y }

② 문법적으로 허용된 생략을 한 일반 함수
fun add(x: Int, y: Int) = x + y

③ 람다식
var add = {x: Int, y: Int -> x + y }      // { } 부분이 람다식임.  '{ 인수들 -> 함수본문 }' 형태임

사용 예)
println(add(3+4))        // 결과: 7

 

 

 

SAM 변환

JAVA에서 메소드가 하나뿐인 인터페이스는 인터페이스를 구현하는 대신 함수로 구현할 수 있는데,
이것을 'SAM변환'이라고 합니다.  SAM (Simple Abstract Method ; 간단한 추상메소드)

코틀린 함수는 매개변수로 한 개의 추상메소드를 받는 형태를 취할 수 있는데,
이 때는 자바에서 작성된 인터페이스인 경우에 한해,
자바의 SAM변환과 유사하게 함수로 구현된 매개변수를 전달할 수 있습니다.

예를 들어 버튼의 클릭 이벤트를 구현할 때, View.OnClickListener 인터페이스를 구현하는데,
 (이 View.OnClickListener는 onClick() 이라는 추상메소드 한 개만을 가지고 있음)
이 인터페이스를 함수로, 더 나아가 익명메소드 형태로 구현해서 전달해 봅시다.

button.setOnClickListener(object : View.OnClickListener {    //OnClickListener는 OnClick메소드 하나만 포함
   override fun onClick(v: View?) {                        // 익명메소드 형태
       // 클릭시 처리 코드
   }
}
)

그리고 위 익명메소드는 람다식으로 표현할 수 있으므로,

button.setOnClickListener({ v: View? ->                // OnClickListener가 람다식화된 형태
      // 클릭시 처리 코드(람다 블럭)
})


코틀린에서 메소드 호출 시 제일 뒤의 매개변수가 람다식인 경우에는,
가독성을 위해 람다식을 ( ) 밖으로 빼서 작성할 수 있습니다.

button.setOnClickListener( ) { v: View? ->
     // 클릭시 처리 코드(람다 블럭)
}

그리고 람다식이 어떤 메소드의 유일한 매개변수인 경우에는 메소드의 ( ) 를 생략할 수 있습니다. 게다가 컴파일러가 자료형을 추론할 수 있다면 자료형을 생략할 수 있는 것이므로, 최종적으로 다음과 같이 되겠네요.

button.setOnClickListener{ v ->
     // 클릭시 처리 코드(람다 블럭)
}

※ 만약에 '클릭시 처리 코드'에서 v라는 매개변수를 사용하지 않는다면,
v를 _ 기호로 대치할 수도 있습니다.

button.setOnClickListener{ _ ->
       // 클릭시 처리 코드(람다 블럭)
}

※ 또 만약에 그리고 람다식에서 매개변수가 하나 뿐인 경우라면,
매개변수를 아예 생략할 수도 있습니다.

button.setOnClickListener{
    // 클릭시 처리코드(람다 블럭)
}

그리고 이 때 람다블럭내에서는 매개변수를 it로 접근할 수 있습니다.

예를들면,
button.setOnClickListener{
    it.visiblity = view.GONE             // 여기서 it는 View? 형 v를 의미함 ----- (K)
}


위에서 SAM변환을 이용한 익명메소드에서 람다식화, 그리고 코틀린의 편리한 생략 표현 기법들을 총동원해 코드를
간결화시켜봤고 코드들 모두 같은 의미입니다. 어쨌거나 가장 가독성이 뛰어난 형태는 (K)의 코드죠. 앞으로 종종 등장합니다.

 
주의해야 할 점은 코틀린에서의 SAM변환은 자바에서 작성한 인터페이스일 때에만 동작한다는 것~!.


 

확장 함수

코틀린에서는 .연산자를 이용해 이미 정의된 클래스에 메소드를 쉽게 추가할 수 있습니다.
(보통 Java나 C#에서는 final로 상속이 봉인되어 있어서 메소드를 추가하지 못하는 경우가 많은데 편리한 기능인 것 같네요)
(확장 함수 내부에서 해당 객체로의 접근은 this를 사용)

예) Int 클래스에 isEven()을 추가해 보자.
fun Int.isEven( ) = this % 2 == 0    // 'this % 2 == 0'의 결과인 부울 값이 함수의 결과 값이 됨

println(5.isEven())        // 결과: false


 

형 변환

val a = 10L

b = a.toInt()
c = a.toDouble()
d = a.toString()

e = Integer.parseInt(d)      // 문자열 형을 숫자로 변환


※ 일반 클래스 간 형 변환 (as 키워드)
open class Human()
class Man: Human()

val man = Man()
val human = man as Human     // Human 형으로 형 변환


형 체크

val st = "hello"
if (st is String) { println(st.toUpperCase() }



고차 함수

함수를 매개 변수로 전달하거나 함수 형으로 반환할 수 있습니다. 이렇게 사용되는 함수를 '고차 함수'하고 합니다.

fun add( x: Int, y: Int, callback: (sum: Int) -> Unit) {   // 두 개의 Int 매개변수와 한 개의 익명함수 매개변수
                                                 // 매개 변수로 쓰인 익명함수는 한 개의 Int 매개변수를 받고 리턴 값은 없음
    callback( x + y )
}

add(3,4, {println(it)})      // 결과: 7    함수를 { }로 감싸고, 이 함수 내부에서는 반환 값을 it로 접근한 예임



동반 객체

'팩토리 메소드'
코틀린에서는 클래스를 객체화 하는 것과는 별개로 메소드를 이용해 객체를 생성하는 코딩 패턴을 지원하는데, 이를 '팩토리 메소드'라고 합니다.
(나중에 다루게 될 프래그먼트 컴포넌트는 특수한 제약 때문에 팩토리 메소드로만 객체를 생성할 수 있음)

코틀린에서는 타 언어에서 정적인 메소드를 만들 때 사용하는 static 키워드 같은 게 없습니다. 그 대신 '동반 객체 (companion object)'라는 것을 통해 이를 구현합니다. (companio : 동반자, 동료, 친구)

다음은 newInstance() 정적 메소드를 사용해서 Fragment 객체를 생성하는 팩토리 패턴을 구현한 것입니다.
여기서 동반 객체 내부의 메소드는 Fragment 클래스와 아무 관계가 없는 정적인 존재입니다.

class Fragment {
   companion object {
      fun newInstance(): Fragment {    // shs: 함수의 반환 형이 Fragment 형이라... 
         println("생성됨")
      }
   }
}

val fragment = Fragment.newInstance()   // shs: 이거 정적 Fragment 안의 newInstance 멤버메소드를 액세스 하는 것과 비슷한데요...



<<< 코틀린 기본 라이브러리에서 유용한 함수들 >>>

let()

블럭에 자기 자신을 인수로 전달(it로 참조)하고 실행 결과를 반환합니다. 
'안전한 호출' 즉, str이 null이 아닐때만 호출 되도록 ?.연산자를 이용하면 더 좋습니다.

val result = str?.let {            //   결과는 Int형
    Integer.parseInt(it)
}
// fun <T, R> T.let(block: (T) -> R): R


with()

객체를 매개변수로 받고 블럭에 리시버 객체형으로 전달(this로 참조)해 준 후, 실행 결과를 반환합니다.
단, '안전한 호출'이 불가능하기 때문에 반드시 str이 null아닐 때에만 호출해야 합니다.

with(str) {
    println(toUpperCase())     // this.toUpperCase()에서 this.를 생략할 수도 있다.
}
// fun <T, R> with(receiver: T, block T.() -> R): R


apply()

블럭에 객체 자신이 리시버 객체형으로 전달되고 그 객체형으로 반환됩니다.
(주로 객체의 상태를 변경해서 반환할 때 사용함)

val result = car?.apply {
   car.setColor(Color.BLACK)
   car.setPrice(1000000)
}
// fun  T.apply(block: T.() -> Unit): T


run()

run() 함수는 익명 함수처럼 쓰는 법과 객체에서 호출하는 법 모두를 제공합니다.

i) 익명 함수처럼 쓸 때
블럭의 결과를 반환합니다.
블럭 안에 선언된 변수들은 모두 임시로 잠깐 사용하는 변수들인데, 임시 변수를 많이 사용하는 복잡한 계산에 유용하겠네요.

val avg = run{
   val kor = 90
   val eng = 80
   val math = 90

   (kor + eng + math) / 3.0f
}
// fun  run(block: () -> R): R


ii) 객체에서 호출 할 때
객체를 블럭의 리시버 객체로 전달하고 결과를 반환합니다.
안전한 호출이 가능하므로 with()보다 더 유용합니다~ 

str?.run {
   println(toUpperCase())
}
// fun <T, R> T.run(block: T.() -> R): R

 

 

 

 

 

 

<<< 이전 글 보기      다음 글 보기 >>>

 

 

 

 

본 블로그를 찾은 분들에게 오늘도 축복 있으시길...

[코틀린의 고급 문법 1]

 

특별한 상태 값 null

코틀린에서는, 모든 객체는 생성과 동시에 값을 갖도록 초기화 하는 것을 원칙으로 하며

null 사용을 허용하지 않습니다.

 

즉,

val a: String             // 불허

val a: String = null    // 불허

 

하지만, null을 꼭 사용하겠다면, ?를 사용해서 null을 허용하겠다고 명백히 해줘야 합니다.

val a: String? = null

 

주의) 위 변수 a 사용시 고려할 점

val b: String? = a      // 허용 (a나 b나 동류이므로)

val c: String = a        // Error (a는 부정확한 상태므로 불허)

val c: String = a!!      // a값이 null이 아님을 보증한다는 의미로 !!를 붙여쓰면 허용

 

늦은 초기화

가끔 초기화를 일부러 늦춰야 하는 경우에 사용합니다. 코틀린에서는 두 개의 키워드로 이를 지원합니다.

(앱이 시작될 때 일부 변수들을 늦게 초기화함으로써 연산을 분산시켜 실행 속도를 빠르게 하기도 함)

 

lateinit

var 타입의 늦은 초기화 (int, long, float, double과 같은 프리미티브 자료형에는 사용 불가)

lateinit var a : String      // 허용

 

by lazy

val 타입의 늦은 초기화 (프리미티브 자료형에도 사용 가능)

val a : String by lazy {

    "Hello"

}

 

실습)

val str : String by lazy {
    println("늦은 초기화!")
    "Hello"
}
println(str)       // 늦은 초기화!, Hello      (str 첫 사용시에만 '늦은 초기화!'가 출력됨)
                   //                         (즉, lazy { } 블럭 내부 코드가 한 번만 실행됨)
println(str)       // Hello

 

 

 

안전한 호출

.연산자 대신 ?. 연산자를 사용하면 변수 값이 null이 아닌 경우에만 메소드가 호출됩니다 (편리한 기능이네요!)

var nameUpperCase = if (str != null) str else null       // 이런 문장을
var nameUpperCase = str?.toUpperCase              // 로 간략하게 쓸 수 있다.

또 한 가지 편리한 기능이 있는데, 위에서 str이 null 일 때 nameUpperCase도 null 이 됩니다.
그런데 이 때 엘비스 연산자 ?: 를 사용하면 null 을 대체할 문자열을 대입할 수 있습니다.

var nameUpperCase = str?.toUpperCase ?: "초기화가 안됐어요"  // str이 null인 경우 문자열이 대입됨

 

 

 

컬렉션

데이터 집합을 만드는 자료 구조를 말합니다. (예: 리스트, 맵, ...)
원소들의 내용을 수정할 수 없는 타입과 수정할 수 있는 가변형(mutable) 타입이 있습니다.


<리스트>

원소 변경 불가
val colors1: List = listOf("초록", "주황", "빨강", "파랑", "하양")
val colors1 = listOf("초록", "주황", "빨강", "파랑", "하양")     // 물론 데이터 형을 생략할 수도 있습니다

원소 변경 가능
val colors2: MutableList = mustableListOf("초록", "주황", "빨강", "파랑", "하양")
val colors2 = mustableListOf("초록", "주황", "빨강", "파랑", "하양")

<colors2.add("검정")    // 원소 추가
colors2.removeAt(4)   // 원소 제거
color2[0] = "연두"     // 원소 변경



<맵>
키-값 쌍의 데이터 집합 (중복불가)

원소 변경 불가
val colors3: Map<String, Int> = mapOf("A" to 90, "B" to 80, "C" to 70, "D" to 60)
val colors3 = mapOf("A" to 90, "B" to 80, "C" to 70, "D" to 60)
println(colors3["B"])

원소 키 값 변경 가능
val colors3: MutableMap<String, Int> = mutableMapOf("A" to 90, "B" to 80, "C" to 70, "D" to 60)
val colors3 = mutableMapOf("A" to 90, "B" to 80, "C" to 70, "D" to 60)


사용 예)
for ((k, v) in colors3) {
   println("$k --- $v")
}



<집합>
원소 변경 불가
val cities: Set = setOf("Seoul", "Incheon", "CheongJu")
val cities = setOf("Seoul", "Incheon", "CheongJu")

원소 변경 가능
val cities: MutableSet = mutableSetOf("Seoul", "Incheon", "CheongJu")
val cities = mutableSetOf("Seoul", "Incheon", "CheongJu")


사용 예)
cities.add("Sokcho")
cities.remove("Incheon")

 

(계속...)

 

<<< 이전 글 보기      다음 글 보기 >>>

 

 

 

 

본 블로그를 찾은 분들에게 오늘도 축복 있으시길...

[코틀린의 기본 문법 2]

 

class (클래스)

class Person {  }                       // 빈 클래스 형태

 

class Person {                          // 생성자를 정의한 클래스 (*k1)
   constructor(name: String) {
      println(name)
   }
}

 

희한하게 클래스를 아래 처럼도 정의하는군요...

class Person (var name: String) {  }    // (생성자를 갖는) 빈 클래스 (참고: var 생략 가능)
                                        // 굳이 클래스명 옆에 이런 문법을 만들것 까지야...
                                        // 메소드 오버로드는 없나보군요...

 

class Person (name: String) {
   init {                    // init 블럭은 생성자와 함께 제일 먼저 실행됨 (위 *k1과 동일 결과)
      println(name)
   }
}

 

var person1 = Person("원빈")     // 개체 생성
person1.name = "한효주"           // 프로퍼티 쓰기
println(person1.name)            // 프로퍼티 읽기

 

 

접근 제한자

public  : (생략가능) 전체 공개
private : 현재 파일 내에서만 공개
internal : 같은 모듈 내에서만 공개 (모듈? 예를 들면, 한 프로젝트 내에 스마트폰용 모듈, 시계용 모듈, TV용 모듈,
            태블릿용 모듈 등등 여러 모듈들을 제작하는 경우가 있음. 모듈은 여러 개의 파일 조각으로 이뤄져 있음)
protected : 부모로 부터 상속받은 클래스에서만 공개

 

 

클래스 상속

※ 코틀린에서는 기본적으로 상속을 금지합니다. (추상 클래스 제외)
그러나 꼭 사용하고자 한다면 open 키워드를 사용할 수 있습니다.

 

빈 클래스 상속

open class Animal { }
class Dog : Animal( ) { }     // SHS: 반드시 상위 클래스의 생성자를 호출하는 형식임 

 

생성자를 갖는 클래스의 상속

open class Animal(val name: String) { }
class Dog(name: String) : Animal(name) { }

 

중첩 클래스

안쪽 클래스는 바깥 클래스A와 거의 독립적입니다.

안쪽과 바깥쪽 변수명이 같아도 됩니다. 단지 선언된 위치가 클래스A 안쪽일 뿐입니다.

class A {
    class B {          // 중첩 클래스
    }
}
var bb = A.B( )     // 클래스B의 위치를 명시하고 클래스B를 객체화 할 수 있음.

 

(중첩된) 내부 클래스

안쪽 클래스에 inner 키워드를 붙여주면 바깥 클래스 멤버에 접근할 수 있고,

이 때의 내부 클래스B는 클래스A의 멤버로 소속이 바뀝니다.

class A {
    var a=10
 
    inner class B {       // (중첩된) 내부 클래스 - 멤버가 된 클래스
        fun result( ) {
            a = 20        // 바깥 클래스 멤버에 접근 가능
        }
    }
}
var bb = A( ).B( )      // 클래스B는 클래스A의 멤버이므로 객체화된 바깥 클래스를 통해서만 객체화가 가능

 

 

추상 클래스

미구현 메소드가 포함된 클래스를 의미하죠. 키워드: abstract

클래스명과 미구현 메소드명 앞 모두에 키워드를 작성해야 합니다.

미구현 메소드가 단 한 개라도 포함되어 있다면 추상 클래스 입니다.

추상 클래스는 반드시 상속을 목적으로 제작하는 클래스입니다.

반드시 상속한 후, 미구현 메소드를 구현해야 합니다.

이것은 반드시 지켜야 하는 개발 팀원들 간의 약속입니다.

abstract class A {
    abstract fun func1( ) { }
    fun func2( ) { }
}
class B: A( ) {
    override fun func3( ) {
        println("Hello")
    }
}

 

 

인터페이스

추상 클래스와 거의 같지만, 일단 2가지 큰 차이점이 있습니다.

1. 추상 클래스는 하나만 상속 가능

   인터페이스는 복수 개를 상속 받을 수 있음 ('다중 상속')

2. 추상 클래스 내부에는 추상 메소드만 선언 가능 (abstract 키워드 필수)

   인터페이스 내부에서는 추상 메소드 + 일반 메소드 모두 사용가능 (abstract 키워드 불필요)

 

interface Runable {
    fun run( )                  // { } 코드가 없는 추상 메소드
    fun walk( ) = println("걷는다")   // 일반 메소드 포함 가능
}

  class Human : Runable {
   override fun run( ) { println("달린다") }
}

 

 

※ 상속과 인터페이스를 함께 사용해본 예)

class Animal { ... }
interface Runable { ... }
interface Eatable { ... }
class Dog : Animal( ), Runable( ), Eatable( ) { ... }   // 클래스 상속과 인터페이스 다중 상속

 

 

 

 

(계속...)

 

<<< 이전 글 보기      다음 글 보기 >>>

 

본 블로그를 찾은 분들에게 오늘도 축복 있으시길...

 

모든 언어가 그렇듯이 코틀린 언어에도 문법이 있습니다. 여기에서는 코틀린의 기본 문법들을 익혀 봅시다.

프로그래밍 언어들은 단 한 가지 언어라도 입문했었다면 사실상 다른 언어들은 거져 먹는 식입니다.

코틀린 역시 특별히 새로운 문법은 없으니까 아래 글을 한 번 읽어 보는 것만으로도 파악이 끝나실 거예요.

 

문법을 설명하기 전에 먼저 스튜디오 안에 있는 편리한 도구 두 가지를 소개해 드릴게요.

[편리한 도구 - REPL]

REPL (Read-eval-print loop)은 코틀린 명령어(='함수'='메서드' 모두 같은 의미로 받아들여도 됨)를 한 줄씩 입력해서 실행해 볼 수 있는 쉘입니다. (쉘: 인간의 명령과 기계의 동작 사이에서 소통을 도와주는 통역자 같은 프로그램) 

 

REPL을 실행해 보세요 --> 메인메뉴 Tools>Kotlin>Kotlin REPL 클릭하고, 아래 명령을 입력해 보세요.

println("안녕하세요, 코틀린")

   

   명령을 실행하도록 Ctrl+엔터 를 눌러주세요.

   println() 은 괄호 속의 문자열을 출력해 주는 메서드 입니다.

코틀린 REPL

 

[편리한 도구 - 스크래치]

여러 줄의 명령어들을 한꺼번에 실행시켜 볼 수 있는, REPL 보다 좀 더 진보된 쉘입니다.

 

스크래치를 실행해 보세요 --> 메인메뉴 File>New>Scratch File>Kotlin 클릭하고, 아래 소스를 입력해 보세요.

var a=10

println (a * 10)

 

   이들 명령을 실행하도록 창에서 '▶'아이콘을 눌러주세요.

   var 는 변수를 선언하는 명령어이고 a에 10이 대입된 후, 이어지는 문장에서 a*10이 계산되어 100이라는 결과가 출력되는데 재밌는 것은 각 줄마다 처리된 결과가 각 줄 우측에 각각 표시된다는 거죠. 스레기통 아이콘을 누르면 출력 결과가 지워집니다.

참고) 만약 각 행에 밑줄이 그어지면서 런타임 스크립트 오류가 표시되면, Ctrl+Shift+Alt+s를 누르시고 Dependencies>app 에서 Path를 업데이트 시켜주세요. 업데이트 링크가 보일 거예요.

(안보이면 수직 스크롤바를 내려보세요)

스크래치 화면

 

 

[코틀린 기본 문법 1]

기본적인 문법을 간략하게 정리해 보겠습니다.

프로그래밍을 조금이라도 했던 분들은 그냥 이해되시겠지만, 처음 프로그래밍을 접하시는 분들은 지금부터 아래 나열된 각 키워드들에 대해서는 각자 검색등을 통해 지식을 습득하셔야 합니다. 이들에 대한 설명은 두꺼운 서적 한 권 정도 되니까 여기에 세세한 내용들을 적을 수는 없어요. ㅠㅜ

 

수치 자료형 : Int, Long, Short, Byte, Float, Double
 문자 자료형 : Char, String
 기타 : Unit (아무것도 없는 상태를 가리킴. 타 언어의 void에 해당)

 

리터럴 (데이터형을 표현하는 방법입니다)

  1    : Int

  1L   : Long

  1.0   : Double

  1.0f  : Float

 

var는 변수 (값 수정 가능), val은 상수 (값 수정 불가)

var a: Int = 10           // 변수 a를 Int형으로 선언 후 10을 대입
var b: Long = 10L          // 변수 b를 Long형으로 선언 후 10을 대입
val pi: Float = 3.14f      // 상수 pi를 Float형으로 선언 후 3.14를 대입 (값 수정 불가)
val str: String = "안녕"   // 문자열 상수. 여러 줄의 문자열은 """로 감싸면 됨 (값 수정 불가)
val ch: Char = '굿'        // 문자 상수. (값 수정 불가)

 

참고) 다른 언어에서 처럼 변수타입 없이 다음과 같이 써도 되는데, 이런 경우에는 타입이 자동으로 설정됨

var a = 10

val str = "안녕"

 


배열은 var, val 어느 키워드로 선언하든 모두 값 수정이 가능

val arr: Array<Int> = arrayOf(1,2,3,4,5)   // Int형 1차원 배열
 val arr = arrayOf(1,2,3,4,5)               // 자료형을 생략해도 됨 (위와 동일)
    arr[0] = 2                              // 배열

 

++i    // 증감연산자 사용 가능. i++ 라고 해도 됨.

 

== // 문자열 비교
=== // 개체 비교

 

println("입력하신 문자열은 $str 입니다")    // " "내에서 문자열 변수 참조 (이건 마치 PHP 같네요)

 

 

<함수>

fun circleArea(r: Float) : Float {          // Float형 인자 r을 받고 Float형 반환 값을 갖는 함수
     return (r*r*pi)
}

fun circleArea(r: Float) : Pair<String, Float> {  // 두 가지 형 데이터를 반환하는 함수
     return Pair("원의 넓이 = ", r*r*pi)
}

fun circleArea(r: Float) : Unit {          // 반환 값은 Unit형 즉, 없음.
     println(r*r*pi)
 }

 

fun circleArea(r: Float) {     // 반환 값이 없을 때(=return문 없음) 반환타입을 아예 생략해도 됨
   println(r*r*pi)

 

 

※ 식의 결과를 함수의 결과 값으로 대입

fun f( ) : Unit = println("Hi")

fun f( ) = println("Hi")

 

 

<조건문>

if ( a == 10) { println("맞아요") }        // 조건문
 else { println("틀려요") }

val max = if (a>b) a else b

 

when (a) {           // switch 문을 코틀린에서 when 이라고 적음(이건 SQL 같은 모습이죠)
   1 -> println("1 입니다")
   2, 3 -> println("2 또는 3 입니다")
   4..7 -> println("4~7 사이 값입니다")      // ..연산자 사용도 가능하네요
   !8..10 -> println("8~10 사이 값이 아닙니다")
   else -> println("...")
}

 

val str = when (a % 2) {
   0 -> "짝수"
   1 -> "홀수"
}

 

fun isEven(n: Int) = when (n%2) {  // when 문 결과를 함수의 반환 값으로도 사용 가능
  0 -> "짝수"                       // 나잘난 프로그램 덕후나 쓸법한 문법...알아도 쓰지말자!
  1 -> "홀수"
  else -> "Exception Error"         // else 분기식은 생략할 수 없음
}
println(isEven(3))                 // 결과: "홀수"

 

val numbers : Array<Int> = arrayOf(1,2,3,4,10,11,-3,-2,-1,0)
for (i in numbers){               // 타 언어의 foreach문에 대응함
   println(i)                      // 결과: 1;2;3;4;10;11;-3;-2;-1;0
}

for (i in 1..10) {.....}           // 1,2,3,...,10
for (i in 1..10 step 2) {.....}    // 1,3,5,7,9    step은 양수만 가능
for (i in 10 downTo -2 step 2) {.....}  // 10,8,6,4,2,0,-2   10..-2라는 표현은 불가함

 

while (i < 10) {.....}       // while 문

 

do {.....} while(i<10)       // do while 문

 

몇 가지 문법을 살펴봤는데, 변수 선언 방법이나 클래스 상속 부분들이 C#과 같은 언어에 비해

덜 직관적인 것 같아 보이네요;; 뭐라고 표현해야 할까... 코틀린 문법이 마치 공돌이 덕후스러운

오버가 너무 많이 보인다고나 할까요^^;

제가 마소의 열성팬은 아니지만, 마소의 팬이 안될래야 안될 수가 없게 만드네요. ㅎㅎ

 

 

(계속...)

 

<<< 이전 글 보기      다음 글 보기 >>>

본 블로그를 찾은 분들에게 오늘도 축복 있으시길...

코틀린 (Kotlin) 이란?

본 포스트는 한빛미디어, '안드로이드 생존코딩'에 소개된 예제를 구현해 보면서 누구나 앱 개발 기술을 습득했으면 하는 바램으로 시작합니다. (본 포스트는 작성되는대로 계속해서 업데이트 할 예정입니다)

 

스마트폰의 OS하면 단연코 안드로이드 입니다. 스마트폰에서 80% 이상의 점유율을 보이고 있죠. 안드로이드가 이렇게 까지 광범위하게 사용될 수 있었던 이유는 안드로이드 OS의 저작권 정책이 오픈 소스라는 점일 것 입니다. 즉 누구에게나 무료로 공개되어 있기 때문에 누구나 자유롭게 OS 개선에 참여할 수 있고 그렇게 많은 지성들이 모여 안드로이드의 성능을 끌어 올린 것이죠.

 

스마트폰 앱을 만들려면 그 스마트폰에 사용되는 OS에 맞춰 프로그래밍을 해야 합니다. 그 때 사용하는 프로그래밍 언어는 자바입니다. 그리고 자바 언어를 좀 더 쉽고 간단화 시킨 최신의 언어가 코틀린 입니다. 그러니까 안드로이드로 구동되는 스마트폰 앱을 만들기 위해 '자바' 또는 '코틀린'을 사용할 수 있습니다. (이 언어 외에도 여러 다른 언어들도 있어요. 하지만 머리 복잡하지 않게, 그리고 여기서는 코틀린을 다루기로 했으니까 다른 언어는 일단 제쳐둡시다)

 

안드로이드 앱 개발 환경 구축

안드로이드 앱은 PC에서 개발합니다. 지금부터 PC에 개발용 프로그램 도구들을 설치함으로써 앱 개발 환경을 구축해 봅시다.
PC의 OS는 Windows 10 이나 리눅스 모두 가능한데, 여기에서는 Windows 10을 기준으로 설명하겠습니다.

첫 번째, https://developer.android.com/studio/ 에서 "Android Studio"라는 도구를 다운받고 설치하세요.

 

두 번째, 설치된 안드로이드 스튜디오를 실행하고 첫 화면에서 몇 가지 설정을 하세요.

    (안드로이드 스튜디오의 버전에 따라 화면 표시가 좀 다를 수도 있습니다만, 메뉴를 잘 찾아가며 따라하시면 되요)

            Configure > Setting 클릭!

    ① Editor > File Encodings

        Global Encoding , project Encoding , Default encoding for properties files 모두 UTF-8로 설정 후 Apply 클릭

    ② Editor > General > Auto Import

        'Add unambiguous imports on the fly'와 'Optimize imports on the fly'를 모두 체크한 후 Apply 클릭

 

( ※ shs:  안드로이드 스튜디오는 자동으로 스펠링 체크를 하도록 되어 있는데, 변수명 만들 때 좀 귀찮을 수도 있으므로 옵션을 제거하겠습니다. (setting > Editor > Code Style > inspections 우측 Spelling 항목 언체크)

안드로이드 스튜디오 초기 화면

 

세 번째, 'Start a new Android Studio project'를 클릭

   (초기에 프로젝트 종류 선택 화면이 나오면, 'Empty Activity'를 선택하고  Next)

   아래 사항들을 원하는대로 작성한 후 Finish를 클릭하면 앱 개발용 화면으로 진입하게 됩니다.

      Name : 원하는 앱 이름 작성 ('FirstApp'이라고 적어보자~)

      Pakage name : 만들어질 앱이 구글플레이에 공개될 때, 전세계적 고유의 앱 ID가 됨

                         (도메인 이름 형식으로 적어봤습니다. 자신만의 임의의 이름을 사용해도 됩니다. 'com.tistory.firstApp' )

      Save location : 만들어질 앱이 저장되는 장소 (제 경우는 'C:\_Lab\Android\FirstApp')

      Language : Kotlin

      Minimum API level : 최소 호환 레벨 --> 킷캣 (안드로이드 4.4)로 합시다~

    (위에 표시된 이외에도 여러 항목이 있을 수 있는데, 위와 같이 앱 이름과 API 레벨만 잘 정해주시면 됩니다)

  Finish 버튼을 누르면 안드로이드 스튜디오가 열리면서 프로그래밍 할 수 있는 에디터가 나타납니다.

    activity_main.xml과 MainActivity.kt 탭이 나타나고 코딩을 할 수 있게 되는데, 이에 대해서는 다음 포스트에서 다룹니다.

    이렇게 새로운 프로젝트를 준비한 것만으로도 기본 안드로이드 화면을 출력하는 프로그램 뼈대를

    모두 만든 것입니다.

 

네 번째, PC와 스마트폰 연결

  앱 개발 과정에서 앱을 실행할 때는 두 가지 방법으로 할 수 있습니다.

  ① 스마트폰에서 직접 실행 --> 실행 테스트 속도가 빨라요. ( <-- 이 방법으로 진행할게요)

  ② 에뮬레이터로 실행 --> PC 메모리를 많이 좀 많이 사용하며 실행 테스트 속도가 느려요. (스마트폰 없이 가능)

 

  ①의 방법을 사용하려면, 아래 과정을 따라 하세요. PC와 스마트폰을 USB 케이블로 연결해야 합니다.

   필요한 경우 PC에 USB 드라이버를 설치해야 할 수도 있습니다.

    (https://developer.android.com/studio/run/oem-usb.html 를 참고)

   스마트폰은 '개발자 모드>USB 디버깅'이 활성화 되어 있어야 합니다.

 

   스마트폰의 '개발자 모드' 전환 방법은 폰 제조사마다 약간 다릅니다. (제조사별 방법은 인터넷에 널려있음)

   제 폰은 LG 폰인데, LG 폰의 경우 '설정>휴대폰 정보>소프트웨어 정보'에서 빌드번호 항목을

   여러 번 연속으로 탭함으로써 개발자 모드로 전환할 수 있습니다.

   (개발자 모드로 전환되면 설정 메뉴들 중에 '개발자 옵션'이라는 항목이 추가되며, 개발자 모드를 끌 때는  개발자 옵션에서 '사용 안함'을 하면 됩니다)

    개발자 모드가 되었으면 개발자 옵션에서 'USB 디버깅'을 켜 주세요. (이렇게 설정해야 PC와 연결이 됨)

 

    (PC와 스마트폰을 USB케이블로 연결한 뒤, 안드로이드 스튜디오 화면 하단 'Logcat'을 클릭해보면 폰 이름이 표시되어 있고  PC와 스마트폰간의 통신 상태 로그가 표시될 것 입니다. 그러면 잘 연결된 것 입니다)

 

다섯 번째, 실행

   안드로이드 스튜디오에서 현재 열려있는 프로젝트를(이미 기본적인 프로그램 뼈대가 완성되어 있는 것이라고 했죠?)

   실행해 볼까요?  -----> 메인 메뉴에서 Run>Run>(App)>Run 또는 단축키 Shift+F10

 

   결과) 스마트폰에 빈 화면이 나타나고 'Hello World!'라고 표시될 것 입니다!

 

   

   축하합니다! ^^

   (아직 익숙하지 않은 과정들이지만, 앞으로 앱 개발을 따라하다 보면 많이 친숙해 질 것 입니다.

    앞으로 코틀린 언어를 이용해서 이 빈 화면에 여러가지 기능들을 추가해 봅시다)

 

   다음 포스트에 계속~

    다음 글 보기 >>> 

 

 

 

 

본 블로그를 찾은 분들에게 오늘도 축복 있으시길...

+ Recent posts