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" />
<?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>
두 가지 유형
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"/>
- 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 속성
3-2-1) CountDownTimer 주요 메서드 (오버라이드 하여 사용)
3-2-2) CountDownTimer 생성자
CountDownTimer(long millisInFuture, long countDownInterval)
더 정확한 타이머가 필요하다면 Handler(포그라운드에서 시행될 때) 또는 AlarmManager(주기적 작업이나 특정 시간에 반복되는 알람을 설정할 때 적합, BroadcastReceiver를 통해 수신) 사용하는 것이 좋음
<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
}
}
위치는 퍼미션 받는 곳에 같이 쓰면 됨
<queries>
<intent>
<action android:name="android.Intent.action.TTS_SERVICE"/>
</intent>
</queries>
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 객체, 식별자)
QUEUE_FLUSH vs QUEUE_ADD
QUEUE_FLUSH는 queue에 쌓지않고 바로 출력
QUEUE_ADD는 queue에 쌓아두고 1부터 다시 읽어줌추가 설정 번들 파람
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처럼 다른 오디오 스트림과 섞이지 않도록 조정)스피치한테 고유 식별자를 주어 해당 발화가 언제 시작하고 끝나는지, 혹은 오류가 발생했는지에 대해 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?) {
// 오류가 발생할 경우 처리
}
})