[Android] 4(2). 화면에 그려지는 디자인 요소 위젯 - 2

7 minute read


화면에 그려지는 디자인 요소 위젯 - 2

이번 포스팅에서는 체크박스, 토글버튼, 스위치, 이미지뷰, 프로그래스바, 시크바, 레이팅바에 대해 알아보겠습니다.


위젯의 대표 메뉴


Common

텍스트, 버튼, 레이아웃 등에서 일반적으로 많이 사용되는 것들을 모아놓은 메뉴

Text

글자를 화면에 나타내거나 입력받을 수 있는 위젯을 모아놓은 메뉴

텍스트에는 글자를 보여주는 기능만 있는 텍스트뷰와 글자를 입력받을 수 있는 기능이 있는 에디트 텍스트 가 있습니다. 아이콘을 보면 텍스트뷰의 Ab 아이콘에는 언더바가 없고, 두번째부터는 Ab 아이콘에 언더바가 있습니다. Ab 언더바가 있는 것이 모두 에디트 텍스트입니다.

Button

사용자로부터 클릭 또는 터치 관련 이벤트를 받을 수 있는 위젯을 모아놓은 메뉴

대표적으로 버튼, 라디오버튼, 체크박스, 스위치 등이 있습니다.

터치란 손을 대는 순간 발생하는 것이고, 클릭은 특정 위치를 터치한 뒤 같은 위치에서 떼었을 때만 발생하는 것으로 다른 개념입니다.

Widget

위젯은 이미지, 웹 사이트, 별점 표시, 진행 상태 등의 정보를 화면에 그리는 위젯 모음입니다.



체크 박스


체크박스는 라디오버튼처럼 여러 개의 선택지가 있지만 하나가 아닌 여러 개를 한 번에 선택할 때 사용합니다.

체크박스는 모든 체크박스들에 대해 리스너들을 달아주어야 하지만 코드를 간결하게 할 수 있는 방법이 있습니다.

다음 예를 보시죠.

👍 예시

  1. 기본 레이아웃 안에 LinearLayout(horizontal)을 배치하고 리니어 레이아웃 안에 3개의 체크박스를 배치합니다. 그리고 각각의 id 속성은 checkApple, checkBanana, checkOrange를 입력하고 각각의 text 속성에는 사과, 바나나, 오렌지를 입력합니다.

  2. 리니어 레이아웃의 외곽선을 입력된 체크박스에 맞추기 위해 리니어 레이아웃의 컨스트레인트를 모두 연결하고 layout_width와 layout_height 속성을 wrap_content로 합니다.

  3. 체크 박스 각각에 대한 리스너는 앞 포스팅에서 라디오 그룹의 리스너를 만드는 과정과 동일합니다. 여기서는 조금 다르게 해보겠습니다.

  4. 우선 그래들 설정에 viewBinding을 추가하고 [Sync Now] 합니다.

  5. onCreate( ) 메서드 위에 listenter 프로퍼티를 하나 만듭니다.

     class MainActivity : AppCompatActivity() {
        
         val listener by lazy {
           CompoundButton.OnCheckChangeListner{buttonView, isChecked -> }              
        }
    

    컴파운드 버튼

    체크박스는 컴파운드 버튼을 상속받아서 만들어졌습니다. 그래서 CompoundButton 클래스의 리스너 설정 시 첫번째 파라미터의 이름이 buttonView이고 타입은 CompoundButton?인 것입니다.

  6. 체크박스 선택 여부를 알 수 있는 리스너 프로퍼티를 작성했습니다. 이제 isChecked 변수로 체크박스가 선택 상태인지, 선택해제 상태인지 구분하고 when 문을 이용해 어떤 체크박스가 선택되었는 지 알 수 있도록 합니다.

    ```kotlin class MainActivity : AppCompatActivity() {

     val binding by lazy {ActivityMainBinding.inflate(layoutInflater)}
     // 변경 코드
     val listener by lazy {
         CompoundButton.OnCheckedChangeListener{buttonView, isChecked ->
             if(isChecked) {
                 when (buttonView.id) {
                     R.id.checkApple -> Log.d("CheckBox", "사과가 선택되었습니다. ")
                     R.id.checkBanana -> Log.d("CheckBox", "바나나가 선택되었습니다. ")
                     R.id.checkOrange -> Log.d("CheckBox", "오렌지가 선택되었습니다. ")
                 }
             }
             else{
                 when (buttonView.id) {
                     R.id.checkApple -> Log.d("CheckBox", "사과가 선택해제 되었습니다. ")
                     R.id.checkBanana -> Log.d("CheckBox", "바나나가 선택해제 되었습니다. ")
                     R.id.checkOrange -> Log.d("CheckBox", "오렌지가 선택해제 되었습니다. ")
                 }
             }
         }
    
     }  ...  }
    
  7. 마지막으로 onCreate( ) 메서드 안에 각 체크박스와 리스너를 연결해주도록 합니다.

    ```kotlin override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(binding.root)

         binding.checkApple.setOnCheckedChangeListener(listener)
         binding.checkBanana.setOnCheckedChangeListener(listener)
         binding.checkOrange.setOnCheckedChangeListener(listener)
    
     }
    



토글버튼, 스위치, 이미지뷰


토글버튼은 부모 클래스인 CompoundButton을 상속받아 사용하기 때문에 체크박스와 리스너와 구현이 동일합니다. 단지 모양만 조금 다릅니다.

스위치도 체크박스와 구현이 동일하며 체크박스, 토글버튼, 스위치는 모두 CompoundButton을 상속받아 사용하므로 하나의 사용법만 익히면 동일한 리스터로 컨트롤할 수 있습니다.

이미지뷰는 이미지버튼과 사용법은 유사하고 리스터를 달아서 click 이벤트도 받을 수 있지만 이미지를 보여주는 용도로만 사용하는 것이 좋습니다. src, background, scaleType과 같은 주요 속성 또한 이미지버튼과 동일합니다.



프로그래스 바


프로그레스바는 진행 상태를 나타내는 위젯입니다.

주로 두 가지로 사용하는데, 하나는 파일 다운로드와 같이 시간이 일정하지 않은 작업을 할 때 ‘현재 진행 중’ 임을 보여주는 용도로 쓰이고, 또 하나는 진행 중임과 동시에 얼마 정도 진행되었는지 진척도를 %로 보여주는 용도로 사용합니다.

👍 예시

  1. UI 편집기에서 리니어 레이아웃(vertical)을 가져다 놓고 컨스트레인트 모두 연결, layout width와 height은 wrap content, gravity는 center, id는 progressBar로 설정합니다.

  2. 팔레트의 위젯 카테고리에서 프로그래스바와 텍스트뷰를 하나씩 가져다 놓고 gravity 속성을 center로 설정합니다.

  3. 텍스트 뷰의 text 속성에 ‘Downloading…‘이라고 입력합니다.

  4. build.gradle 파일에서 viewBinding 설정을 하고, MainAcitivity.kt 에서 binding을 생성한 후 setContentView에 binding.root를 전달합니다.

    ```kotlin val binding by lazy{ ActivityMainBinding.inflate(layoutInflater)}

     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         setContentView(binding.root)
     }
    
  5. 클래스 안에 showProgress 메서드를 만들고 리니어 레이아웃을 숨겼다 보였다 할 수 있는 코드를 추가합니다.

         override fun onCreate(savedInstanceState: Bundle?) {
             super.onCreate(savedInstanceState)
             setContentView(binding.root)
         }
        
         fun showProgress(show: Boolean){
     //        if(show){
     //            binding.progressBar.visibility = View.VISIBLE
     //        }
     //        else{
     //            binding.progressBar.visibility = View.GONE
     //        }
             binding.progressBar.visibility = if (show) View.VISIBLE else View.GONE
         }
     }
    

    visibility 속성

    속성 값 설명
    VISIBLE 현재 보이는 상태
    INVISIBLE 현재 안 보이는 상태. 보이지는 않지만 공간은 차지하고 있음.
    GONE 현재 안 보이는 상태. 보이지도 않고 공간도 차지하지 않음.


  1. 앱이 실행되고 3초 후에 showProgress(false)를 호출하는 코드를 onCreate( ) 메서드 안에 작성합니다. 이 때 주의해야 할 점이 있습니다.

    • 메인 스레드에서 Thread.sleep( ) 메서드를 사용할 경우
         override fun onCreate(savedInstanceState: Bundle?) {
             super.onCreate(savedInstanceState)
             setContentView(binding.root)
        
             Thread.sleep(3000)
             showProgress(false)
        
         }
    

    위와 같은 경우 에뮬레이터 실행 시 3초 동안 화면에 아무것도 나타나지 않다가 3초 후에 텍스트뷰만 나타납니다.

    화면을 그려주는 메인 스레드

    안드로이드에는 메인 스레드(Main Thread)라는 개념이 있는데, 화면에 그림을 그려주는 것이 메인 스레드의 역할입니다. 화면을 그리는 것은 모두 메인 스레드에서 실행되어야 합니다. (다른 이름으로 UI Thread라고 불리기도 합니다. )

    onCreate( ) 메서드 안의 코드는 모두 메인 스레드에서 동작하기 때문에 Thread.sleep 메서드를 사용하면 화면을 그리는 것도 모두 멈춥니다.

    • 서브 스레드 에서 Thread.sleep( ) 메서드를 사용할 경우
         override fun onCreate(savedInstanceState: Bundle?) {
             super.onCreate(savedInstanceState)
             setContentView(binding.root)
        
             thread(start=true){
                 Thread.sleep(3000)
                 showProgress(false) // 이대로 실행하면 앱이 죽음
             }
         }
    

    위의 코드를 실행하면 3초간 프로그래스바가 동작하다가 앱이 죽습니다. 앞에서 말했듯이 그림을 그리는 코드, 즉 UI와 관련된 모든 코드는 메인 스레드에서 실행해야만 하기 때문에 앞의 코드에서 showProgress 메서드를 백그라운드에서 호출 시 앱이 강제 종료되는 것입니다.

    • 서브 스레드 내의 runUiThread 에서 Thread.sleep( ) 메서드를 사용할 경우
         override fun onCreate(savedInstanceState: Bundle?) {
             super.onCreate(savedInstanceState)
             setContentView(binding.root)
        
             thread(start=true){
                 Thread.sleep(3000)
                 runOnUiThread{
                     showProgress(false)
                 }
             }
         }
    

    따라서 위와 같이 showProgressbar 메서드만 메인 스레드에서 실행하도록 해야 합니다. runOnUiThread 스코프 안에서 코드를 실행하면 모두 메인 스레드에서 동작합니다.


  2. 이제 앱을 실행하면 3초간 프로그래스바가 동작하다가 없어집니다.



시크바


시크바는 볼륨을 조절하거나 뮤직 플레이어에서 재생 시간을 조절하는 용도로 많이 사용합니다.

다음 예시는 시크바를 드래그하면 시크바의 값이 텍스트뷰에 나타나는 예제입니다.

👍 예시

  1. 위젯 카테고리의 시크바(SeekBar)를 드래그해서 화면 가운데에 가져다놓고 컨스트레인트를 네 방향 모두 연결합니다. 혹시 시크바의 크기가 너무 작다면 크기를 늘려준 다음 컨스트레인트를 연결합니다. layout_width는 0dp, layout_height는 wrap_content로 입력하고 id는 seekBar로 입력합니다.

  2. 시크바 위에 텍스트뷰를 하나 가져다 놓고 컨스트레인트를 연결합니다. 텍스트뷰의 id 는 textView, text는 을 입력합니다.

  3. build.gradle 파일에서 viewBinding 설정을 하고, MainAcitivity.kt 에서 binding을 생성한 후 setContentView에 binding.root를 전달합니다.

  4. setContentView 아랫줄에 아래 코드를 추가합니다. 시크바는 사용할 수 있는 리스너가 하나만 있습니다.

    ```kotlin binding.seekBar.setOnSeekBarChangeListener()

  5. 앞에서 생성된 리스너의 괄호 안에 아래처럼 코드를 추가합니다. object: 다음에 입력하는 SeekBar는 Alt + Enter 로 먼저 import 해야 합니다.

    ```kotlin binding.seekBar.setOnSeekBarChangeListener(object: OnSeekBarChangeListener{

         })
    
  6. 리스너의 코드 블록 사이를 클릭한 채로 (중괄호 안에 마우스 포인터를 두고) Ctrl + I 키를 입력한 후 나타나는 [Implements Members] 팝업창에서 3개의 메서드를 모두 선택합니다. 메서드를 생성한 후 각 메서드의 TODO 행은 주석처리합니다.

    TODO 는 강제 오류를 발생하는 함수이기 때문에 삭제하지 않으면 예외가 발생하여 앱이 다운됩니다.

  7. onProgressChanged 메서드 안에 다음 코드를 추가합니다.

        
     override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
         binding.textView.text = "$progress"
     }
    

    OnProgressChanged 메서드의 파라미터

    • seekBar: 리스너가 동작하고 있는 시크바 위젯
    • progress: 현재 시크바의 현재 progress 값
    • fromUser: 사용자 터치 여부 (코드에서 시크바를 움직일 수도 있기 때문에 사용자의 터치에 의해 동작하는 것인지의 여부)


  1. 에뮬레이터를 실행하고 시크바를 드래그하면 상단의 숫자가 바뀌는 것을 확인할 수 있습니다.


시크바의 주요 속성은 다음과 같습니다.

  • max: 시크바의 최댓값을 설정합니다.
  • progress: 처음 시작하는 시크바의 값을 설정합니다. 기본 값은 0입니다.



레이팅바


레이팅바는 한글로는 별점바, 등급바 로 불리며 인터넷에서 흔히 볼 수 있는 별점을 매기는 위젯입니다.

이번에도 시크바와 비슷하게 레이팅바를 클릭하거나 드래그하면 현재 별점이 텍스트뷰에 표시되도록 하겠습니다.

👍 예시

  1. 위젯 카테고리의 레이팅바(ratingBar)를 드래그해서 화면 가운데에 가져다놓은 후 컨스트레인트를 네 방향 모두 연결하고 id는 seekBar로 입력합니다.

  2. 시크바 위에 텍스트뷰를 하나 가져다 놓고 컨스트레인트를 연결합니다. 텍스트뷰의 id 는 textView, text는 0.0 을 입력합니다.

  3. build.gradle 파일에서 viewBinding 설정을 하고, MainAcitivity.kt 에서 binding을 생성한 후 setContentView에 binding.root를 전달합니다.

  4. setContentView 아랫줄에 아래 코드를 추가합니다.

     binding.ratingBar.setOnRatingBarChangeListener{ ratingBar, rating, fromUser -> }
    
  5. 시크바와는 인터페이스 구조가 다르기 때문에 중괄호 안에 식을 바로 사용할 수 있습니다. 소스 코드를 다음과 같이 입력합니다.

    ```kotlin binding.ratingBar.setOnRatingBarChangeListener { ratingBar, rating, fromUser -> binding.textView.text = “$rating” }


  1. 에뮬레이터를 실행하고 드래그해보면 숫자가 바뀌는 것을 확인할 수 있습니다.


레이팅 바의 주요 속성은 다음과 같습니다.

  • numStarts: 전체 표시되는 별의 개수
  • rating: 기본 별점. 처음 시작하는 별점값.
  • stepSize: 별을 드래그 했을 때 움직이는 최소 단위. 0.1로 설정하면 별 하나를 10단위로 쪼개서 표시.


리스터 다음에 오는 중괄호 vs 괄호

안드로이드는 리스너라는 개념으로 인터페이스를 제공하는데, 인터페이스 안에 개발자가 구현해야 하는 메서드의 목록이 미리 정의되어 었습니다.

아직까지 안드로이드는 기반 코드가 자바로 되어 있기 때문에 실제 리스너 인터페이스는 다음과 같이 대부분 자바 문법을 사용합니다.

  • 레이팅바(RatingBar)의 리스너
public interface OnRatingBarChangeListener{
  void onRatingChanged(RatingBar ratingBar, float rating, boolean fromUser);
}

위처럼 정의되어 있는 메서드의 개수가 1개인 경우 메서드 이름을 작성하지 않고 중괄호를 사용해서 처리할 수 있습니다. 이렇게 중괄호를 사용하여 코드를 축약한 형태를 ‘람다식’이라고 합니다.

  • 시크바(SeekBar)의 리스너
public interface OnSeekBarChangedListener{
  void onSeekBarChanged(SeekBar seekBar, int progress, boolean fromUser);
  
  void onStartTrackingTouch(SeekBar seekBar);
  
  void onStopTrackingTouch(SeekBar seekBar);
}

메서드가 2개 이상이면 괄호를 사용하고 인터페이스에 정의되어 있는 메서드를 모두 구현해야만 정상적으로 동작합니다. 그래서 시크바의 리스너는 괄호를 사용해야 하고, 괄호 안에 오브젝트 형태로 모든 메서드를 구현하는 것입니다. 메서드가 1개인 리스터에 괄호를 사용하는 것은 코드가 길어질 뿐 정상 동작합니다.


정리


  • 리스너 정리

    위젯 리스너 예시 설명
    텍스트뷰 X  
    에디트텍스트 binding.editText.addTextChangedListener{ } it으로 참조 (중괄호)
    버튼, 이미지버튼 binding.imageButton.setOnClickListener{ } it으로 참조 (중괄호)
    라디오버튼(그룹) binding.radioGroup.setOnCheckedChangeListener{ group, checkedId ->
    when(checkedId){
    R.id.radioButton -> …
    }
    라디오 그룹에 리스너 연결
    체크박스, 토글버튼
    스위치, 이미지뷰
    binding 아래에 리스너를 작성
    val listener by lazy {
    CompoundButton.OnCheckedChangeListener{buttonView, isChecked ->
    if(isChecked) {
    when (buttonView.id) {
    R.id.checkBox -> …
    }
    }
    else{
    when (buttonView.id) {
    when (buttonView.id) {
    R.id.checkBox -> …
    }
    }
    }

    }

    oncreate( ) 메서드 안에서 리스너 연결
    binding.checkBox.setOnCheckedChangeListner(listner)
    각각에 연결해야 하지만
    리스너를 따로 작성해서
    코드를 간략하게 함
    프로그래스바 onCreate( ) 메서드 안에 서브 스레드 호출
    thread(start=true){
    Thread.sleep(3000)
    runOnUiThread{
    showProgress(false)
    }
    }

    class MainActivity 안에 함수 정의
    fun showProgress(show: Boolean){
    binding.progressBar.visibility = if (show) View.VISIBLE else View.GONE
    }
    리스너가 아닌 함수 구현
    서브 스레드와
    runOnUiThread( ) 사용
    시크바 binding.seekBar.setOnSeekBarChangeListener(object: OnSeekBarChangeListener{
    override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
    // TODO(“Not yet implemented”)
    }

    override fun onStartTrackingTouch(seekBar: SeekBar?) {
    // TODO(“Not yet implemented”)
    }

    override fun onStopTrackingTouch(seekBar: SeekBar?) {
    // TODO(“Not yet implemented”)
    }
    })
    구현해야 하는 함수가
    2개 이상이기 때문에
    괄호 안에 중괄호 사용
    레이팅바 binding.ratingBar.setOnRatingBarChangeListener{ ratingBar, rating, fromUser -> } it으로 참조 (중괄호)

Categories:

Updated:

Leave a comment