android motionLayout
UI
motionLayout
motionScene을 활용하여 화면을 구성할 수 있다. Fragment레이아웃에서 motionLayout을 연결을 해준다.
private fun initMotionLayoutEvent(fragmentPlayerBinding: FragmentPlayerBinding){
fragmentPlayerBinding.playerMotionLayout.setTransitionListener(object :MotionLayout.TransitionListener{
override fun onTransitionStarted(
motionLayout: MotionLayout?,
startId: Int,
endId: Int
) {
}
override fun onTransitionChange(
motionLayout: MotionLayout?,
startId: Int,
endId: Int,
progress: Float
) {
binding?.let {
(activity as MainActivity).also { mainActivity ->
mainActivity.findViewById<MotionLayout>(R.id.mainMotionLayout).progress = abs(progress)//절대값으로 프로그레스 값을 준다
}
}
}
override fun onTransitionCompleted(motionLayout: MotionLayout?, currentId: Int) {
}
override fun onTransitionTrigger(
motionLayout: MotionLayout?,
triggerId: Int,
positive: Boolean,
progress: Float
) {
}
})
}
motion레이아웃을 활용하게 되면 scene 파일이 생성되게 된다. 해당 xml에서 transition, constraionset에서 각 요소에 설정값을 주어 원하는 모션이 start, end가 되었을때 어떤 모양으로 설정될지 값을 줄 수 있다.
아래는 모션레이아웃을 활용했을때 커스텀설정을 통해 특정 화면부분에서만 제스처 했을때 동작되도록 설정하였다.
//전체 화면이 스와프되는 문제를 위해 커스텀 생성
class CustomMotionLayout(context: Context, attributeSet: AttributeSet? = null):MotionLayout(context,attributeSet) {
private var motionTouchStarted = false
private val mainContainerView by lazy {
findViewById<View>(R.id.mainContainerLayout)
}
private val hitRect = Rect()
init {
setTransitionListener(object : TransitionListener{
override fun onTransitionStarted(
motionLayout: MotionLayout?,
startId: Int,
endId: Int
) {
}
override fun onTransitionChange(
motionLayout: MotionLayout?,
startId: Int,
endId: Int,
progress: Float
) {
}
override fun onTransitionCompleted(motionLayout: MotionLayout?, currentId: Int) {
motionTouchStarted = false
}
override fun onTransitionTrigger(
motionLayout: MotionLayout?,
triggerId: Int,
positive: Boolean,
progress: Float
) {
}
})
}
override fun onTouchEvent(event: MotionEvent): Boolean {
when(event.actionMasked) {
//ACTION_UP , ACTION_CANCEL 은 터치를 안하는 액션
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
motionTouchStarted = false
return super.onTouchEvent(event) //원래값을 리턴 (사용 x)
}
}
if(!motionTouchStarted){
//getHitRect() = 상위 요소의 좌표에서 하위 요소의 적중 사각형(터치 가능한 영역)을 가져옵니다
mainContainerView.getHitRect(hitRect)
//rect.contains = Returns true if (x,y) is inside the rectangle
motionTouchStarted = hitRect.contains(event.x.toInt(), event.y.toInt())
}
return super.onTouchEvent(event) && motionTouchStarted
}
private val gestureListener by lazy {
object: GestureDetector.SimpleOnGestureListener(){
override fun onScroll(
e1: MotionEvent,
e2: MotionEvent,
distanceX: Float,
distanceY: Float
): Boolean {
mainContainerView.getHitRect(hitRect)
return hitRect.contains(e1.x.toInt(),e1.y.toInt())
}
}
}
private val gestureDetector by lazy {
/*
Detects various gestures and events using the supplied MotionEvents.
The OnGestureListener callback will notify users when a particular motion event has occurred.
This class should only be used with MotionEvents reported via touch
*/
GestureDetector(context, gestureListener)
}
override fun onInterceptTouchEvent(event: MotionEvent?): Boolean {
return gestureDetector.onTouchEvent(event)
}
}
ExoPlayer
ExoPlayer는 Android 프레임워크에 속하지 않고 Android SDK에서 별도로 배포되는 오픈소스 프로젝트입니다. ExoPlayer의 표준 오디오 및 동영상 구성요소는 Android 4.1(API 레벨 16)에서 출시된 Android MediaCodec API를 기반으로 합니다.
아래는 exoPlayer를 init하는 코드이다.
private fun initPlayer(fragmentPlayerBinding: FragmentPlayerBinding) {
context?.let {
player = SimpleExoPlayer.Builder(it).build()
}
fragmentPlayerBinding.playerView.player = player
binding?.let {
player?.addListener(object : Player.EventListener{
override fun onIsPlayingChanged(isPlaying: Boolean) {
super.onIsPlayingChanged(isPlaying)
if(isPlaying){
it.bottomPlayerControlButton.setImageResource(R.drawable.ic_baseline_pause_24)
}else{
it.bottomPlayerControlButton.setImageResource(R.drawable.ic_baseline_play_arrow_24)
}
}
})
}
}
실제 플레이하게끔 하는 코드
fun play(url: String, title: String){
context?.let {
val dataSourceFactory = DefaultDataSourceFactory(it)
val mediaSource = ProgressiveMediaSource.Factory(dataSourceFactory)
.createMediaSource(MediaItem.fromUri(Uri.parse(url)))
player?.setMediaSource(mediaSource)
player?.prepare()
player?.play()
}
binding?.let {
it.playerMotionLayout.transitionToEnd() //모션 레이아웃에서 설정은 END의 화면으로 화면 전환
it.bottomTitleTextView.text = title
}
}