운동앱_ripple/layer-list/ProgressBar/CountDownTimer/TextToSpeech

소정·2024년 10월 30일
0

Kotlin

목록 보기
30/40

1. ripple

  • UI에 터치 효과 주는 애

  • drawable폴더에 리소스 파일로 작성

  • ripple: ripple 효과를 정의하는 최상위 요소. color 속성을 사용하여 터치 효과의 색상을 지정

  • item: ripple 효과가 적용될 기본 레이어를 정의. item 태그 내부의 drawable 속성으로 기본 배경을 지정할 수 있다. 터치하지 않았을 때 나타날 색상이나 배경을 지정

<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
    android:color="@color/lightGray"
    >
    <item android:id="@android:id/mask">
        <shape android:shape="oval">
            <solid android:color="@color/white"/>
        </shape>
    </item>

    <item android:drawable="@drawable/item_circular_color_accent_border"/>
</ripple>

적용 방법

<Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@drawable/ripple_effect"
    android:text="Ripple Effect" />


2. layer-list

  • 여러 개의 다른 Drawable을 겹쳐서 하나의 이미지를 만드는 데 사용
  • 배경, 테두리, 그림자 등을 한 번에 포함하는 복잡한 모양을 만들 때 유용
  • layer-list는 XML 파일, 여러 item 요소를 포함할 수 있다
  • 각 item은 하나의 레이어를 나타내며, 레이어의 순서대로 그려짐
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <shape
            android:innerRadiusRatio="2.7" //링의 내부 반지름, 링의 중심부터 내부 가장자리까지의 거리를 결정
            android:shape="ring" //원과는 달리 두께를 지정할 수 있는 형태
            android:thicknessRatio="50.0" //링의 두께
            android:useLevel="true" //level 값에 따라 링의 두께를 조절, 이 속성은 주로 ProgressBar 같은 UI 요소에서 레벨에 따라 그려지는 크기를 변경할 때 유용
            >
            <solid android:color="@color/colorAccent"/>
        </shape>
    </item>
</layer-list>

3. ProgressBar와 CountDownTimer

3-1) ProgressBar

  • 작업의 진행 상황을 사용자에게 시각적으로 표시하는 UI 컴포넌트

두 가지 유형
1. Indeterminate ProgressBar : 진행 시간이 예측되지 않을 때, 로딩 중이라는 시각적 효과 제공 ex) 회전하는 원형 로딩

<ProgressBar
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:indeterminate="true"
    android:visibility="visible"
    android:layout_gravity="center"/>
  1. Determinate ProgressBar : 진행 상태를 퍼센트로 표시할 수 있는 프로그레스바
<ProgressBar
    android:id="@+id/progressBar"
    style="@android:style/Widget.ProgressBar.Horizontal" //안드로이드에서 제공하고 있는 스타일
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:max="100"
    android:progress="0"/>

3-1-1) ProgressBar XML 속성

  • style: 프로그레스바의 스타일을 설정(ex. @android:style/Widget.ProgressBar.Horizontal)
  • max: 최대값 설정
  • progress: 현재 진행 상태의 초기값 설정
  • indeterminate: true로 설정하면 Indeterminate



3-2) CountDownTimer

  • ountDownTimer : 일정 시간 동안 카운트다운 기능을 제공하는 유틸리티. 특정 작업을 지연, 일정 간격으로 업데이트 예) 퀴즈 타이머
  • CountDownTimer는 짧은 시간 동안 간단한 타이머 기능을 제공하기 적합한 클래스

3-2-1) CountDownTimer 주요 메서드 (오버라이드 하여 사용)

  • onTick(long millisUntilFinished) : 타이머가 설정된 간격으로 호출되는 메서드, 남은 시간을 millisUntilFinished 인수로 받음
  • onFinish(): 타이머가 완료되었을 때 호출되는 메서드
  • start(): 타이머 시작
  • cancel(): 타이머 중지. 중지 후 다시 시작하려면 새로운 CountDownTimer 인스턴스를 생성해야됨

3-2-2) CountDownTimer 생성자
CountDownTimer(long millisInFuture, long countDownInterval)

  • millisInFuture: 타이머 동작을 언제까지 해야되는지 명시(밀리초)
  • countDownInterval : onTick()이 호출되는 간격(밀리초)

더 정확한 타이머가 필요하다면 Handler(포그라운드에서 시행될 때) 또는 AlarmManager(주기적 작업이나 특정 시간에 반복되는 알람을 설정할 때 적합, BroadcastReceiver를 통해 수신) 사용하는 것이 좋음


3-3) 예제

화면

<FrameLayout
        android:id="@+id/flExerciseBar"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_marginTop="10dp"
        android:layout_marginBottom="16dp"
        android:background="@drawable/item_circular_color_accent_border"
        android:visibility="gone"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent">

        <ProgressBar
            android:id="@+id/exerciseBar"
            style="?android:attr/progressBarStyleHorizontal"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:layout_gravity="center"
            android:background="@drawable/circular_progress_grey"
            android:progressDrawable="@drawable/circular_progress_bar"
            android:indeterminate="false"
            android:progress="100"
            android:rotation="-90"/>
        <!-- 보통 progress bar가 오른쪽에서 시작되는데 시작점을 위로 하고 싶어서 -90해서 돌림-->

        <LinearLayout
            android:layout_width="60dp"
            android:layout_height="60dp"
            android:gravity="center"
            android:layout_gravity="center"
            android:background="@drawable/item_circular_color_accent_background">
            <TextView
                android:id="@+id/tvActiveTimer"
                android:textColor="@color/white"
                android:textSize="25sp"
                tools:text="30"
                android:textStyle="bold"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>
        </LinearLayout>

    </FrameLayout>

동작 코드

package com.airpass.myexerciseapp

import android.annotation.SuppressLint
import android.os.Bundle
import android.os.CountDownTimer
import android.view.View
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.airpass.myexerciseapp.databinding.ActivityExerciseBinding

class ExerciseActivity : AppCompatActivity() {

    private var binding : ActivityExerciseBinding? = null
    
    
    private var restTimer : CountDownTimer? = null
    private var restProgress = 0 // progressBar max 시간(maxProgress) - restProgress 해서 남은 시간 표시해주기 위한 변수
    private val maxProgress = 30 // progressBar max 지정용 변수
    private val _millisInFuture = 30000L // CountDownTimer가 언제까지 동작해야 되는지 기준 시간(밀리세컨드)

    @SuppressLint("RestrictedApi")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        binding = ActivityExerciseBinding.inflate(layoutInflater)
        setContentView(binding!!.root)
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
            val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
            insets
        }

        setSupportActionBar(binding!!.toolbarExercise)

        setupResrView()

    }
    
    
    private fun setupResrView() {
        if (restTimer != null) {
            restTimer?.cancel() // restTimer를 새로 만들어 버리기
            restProgress = 0
        }
        setRestProgressBar()
    }
    
    private fun setRestProgressBar() {
        binding?.progressBar?.max = maxProgress
        binding?.progressBar?.progress = restProgress

        //시간이 얼마나 지속될지 적기 1초마다 10초까지
        restTimer = object :CountDownTimer(_millisInFuture, 1000) {
            override fun onTick(millisUntilFinished: Long) {
                restProgress++
                binding?.progressBar?.progress = maxProgress - restProgress
                binding?.tvTimer?.text = (maxProgress - restProgress).toString() //남은 시간 표시
            }

            override fun onFinish() {
                Toast.makeText(this@ExerciseActivity, "타이머 끝", Toast.LENGTH_SHORT).show()
                setRestProgressBar()
            }
        }.start()
    }
    
    override fun onDestroy() {
        super.onDestroy()
        if (restTimer != null) {
            restTimer?.cancel()
            restProgress = 0
        }
        binding = null
    }
    
}


4. TextToSpeech

  • 텍스트를 음성으로 읽어줌

4-1) 안드로이드 버전11에서 TTS 사용하기 위해선 menifest에 queries 작성해줘야함

위치는 퍼미션 받는 곳에 같이 쓰면 됨

<queries>
        <intent>
            <action android:name="android.Intent.action.TTS_SERVICE"/>
        </intent>
    </queries>

4-2) 사용법


class ExerciseActivity : AppCompatActivity(), TextToSpeech.OnInitListener {
	// 1. 변수 선언
	private var tts:TextToSpeech? = null
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
            //2. tts 초기화
            //첫번째 this는 context, 두번째 this는 lister
            //리스너를 따로 작성하지않고 상속받아 처리함
            tts = TextToSpeech(this, this)
        }
        
        // 3. text to speech 초기화에 대한 text의 완료 신호용
    override fun onInit(status: Int) {
        if (status == TextToSpeech.SUCCESS) {
            val result = tts!!.setLanguage(Locale.US)

            if (result == TextToSpeech.LANG_MISSING_DATA
                || result ==TextToSpeech.LANG_NOT_SUPPORTED) {
                //언어 지원하지 않는 경우
                Toast.makeText(this, "언어 지원 x", Toast.LENGTH_SHORT).show()
            } else {
                setupRestView()
            }
        } else {
            Toast.makeText(this, "Initialization Failed", Toast.LENGTH_SHORT).show()
        }
    }

	//4. 텍스트 읽어주기용 메소드 따로 작성
    private fun speakOut(text:String) {
        tts?.speak(text, TextToSpeech.QUEUE_FLUSH, null, "")
//        tts?.speak(text, TextToSpeech.QUEUE_ADD, null, "")
    }
}

speak(text, TextToSpeech.QUEUE_FLUSH, null, "")
speak(출력할 텍스트 onInit 에 설정한 대로 읽어줌, 2.스피크 큐에 텍스트 추가되는 방식 설정, 3.추가 설정을 위한 Bundle 객체, 식별자)

  1. QUEUE_FLUSH vs QUEUE_ADD
    QUEUE_FLUSH는 queue에 쌓지않고 바로 출력
    QUEUE_ADD는 queue에 쌓아두고 1부터 다시 읽어줌

  2. 추가 설정 번들 파람
    null로 설정하는 경우 기본 설정
    TextToSpeech.Engine.KEY_PARAM_VOLUME : 볼륨 크기를 설정 (1.0은 기본값, 0.0은 음소거)
    TextToSpeech.Engine.KEY_PARAM_PAN: 스테레오 패닝을 설정 (-1.0은 왼쪽, 1.0은 오른쪽, 0.0은 중앙)
    TextToSpeech.Engine.KEY_PARAM_STREAM: 오디오 스트림 유형 설정 (AudioManager.STREAM_MUSIC처럼 다른 오디오 스트림과 섞이지 않도록 조정)

  3. 스피치한테 고유 식별자를 주어 해당 발화가 언제 시작하고 끝나는지, 혹은 오류가 발생했는지에 대해 TextToSpeech.OnUtteranceProgressListener를 통해 알림을 받을 수 있다.


스피치를 세심하게 조정하고 싶으면 리스너 설정하여 사용해야함

예시)

tts.setOnUtteranceProgressListener(object : UtteranceProgressListener() {
    override fun onStart(utteranceId: String?) {
        // 발화가 시작될 때 MediaPlayer 일시 정지
        if (mediaPlayer.isPlaying) {
            mediaPlayer.pause()
        }
    }

    override fun onDone(utteranceId: String?) {
        // 발화가 완료되면 MediaPlayer 다시 재생
        mediaPlayer.start()
    }

    override fun onError(utteranceId: String?) {
        // 오류가 발생할 경우 처리
    }
})

profile
보조기억장치

0개의 댓글