오늘은 화면 스크롤 시 배경이미지를 점점 투명하게 만드는 것을 해볼것이다. 이번에 앱을 만들때 필요했는데 Kotlin 자료가 많이 없어서 Java로 작성된 블로그를 참고했다. 우선 애니메이션으로 할 수 있겠지만 여기서는 스크롤이벤트를 이용할 것이다.
나는 ScrollView를 커스텀할것이다. 우선 아래 레이아웃을 보면 위에 사라지게 만들 top_image가 있고 그 아래에 "안녕하세요."를 입력받은 TextView가 있다. TextView 아래에는 스크롤을 하기위해 높이가 있는 것으로 채운것이므로 신경쓰지않아도 된다.
자, 그럼 이제 구현해보자!
우선, 제일 중요한 ScrollView를 커스텀해보자. 스크롤이 변경될때 이벤트 리스너를 추가하여 Activity나 Fragment단에서 처리할 수 있게 했다.
package com.crystal.customs
import android.content.Context
import android.util.AttributeSet
import android.util.Log
import android.widget.ScrollView
class CustomScrollView: ScrollView {
var SCROLL_UP = 0
var SCROLL_DOWN = 1
private var onScrollListener: OnScrollListener? = null
constructor(context: Context) : this(context, null, 0)
constructor(context: Context, attr: AttributeSet?) : this(context, attr, 0)
constructor(context: Context, attr: AttributeSet?, defStyleAttr: Int) : super(
context,
attr,
defStyleAttr
)
override fun onScrollChanged(l: Int, t: Int, oldl: Int, oldt: Int) {
super.onScrollChanged(l, t, oldl, oldt)
val direction = if (oldt > t) SCROLL_DOWN else SCROLL_UP
onScrollListener?.onScroll(direction, t.toFloat())
}
fun setOnScrollListener(listener: OnScrollListener) {
onScrollListener = listener
}
interface OnScrollListener {
fun onScroll(direction: Int, scrollY: Float)
}
}
fragment의 layout이다. 보면 layout 바로 아래의 ConstraintLayout의 ImageView가 우리의 배경화면이다.그리고 배경화면과 우리가 만든 스크롤뷰의 부분이 겹치게 둔다. 이건 취향껏 하자.
<?xml version="1.0" encoding="utf-8"?>
<layout >
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".fragments.HomeFragment">
<ImageView
android:id="@+id/top_image"
android:layout_width="match_parent"
android:layout_height="300dp"
android:contentDescription="@string/description_home_image"
android:scaleType="fitXY"
android:src="@drawable/night_sky"
app:layout_constraintTop_toTopOf="parent"
tools:layout_editor_absoluteX="0dp" />
<com.crystal.customs.CustomScrollView
android:id="@+id/scroll_view"
android:layout_width="match_parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_height="0dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:padding="10dp"
android:layout_marginTop="300dp"
android:layout_height="wrap_content">
<TextView
android:id="@+id/greetings_text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="22sp"
android:text="안녕하세요."
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<ImageView
android:id="@+id/imageview1"
android:layout_width="match_parent"
android:layout_height="300dp"
app:layout_constraintTop_toBottomOf="@+id/greetings_text_view" />
<ImageView
android:id="@+id/imageview2"
android:layout_width="match_parent"
android:layout_height="300dp"
app:layout_constraintTop_toBottomOf="@+id/imageview1"
tools:layout_editor_absoluteX="0dp" />
<ImageView
android:id="@+id/imageview3"
android:layout_width="match_parent"
android:layout_height="300dp"
app:layout_constraintTop_toBottomOf="@+id/imageview2" />
<ImageView
android:id="@+id/imageview4"
android:layout_width="match_parent"
android:layout_height="300dp"
app:layout_constraintTop_toBottomOf="@+id/imageview3" />
<ImageView
android:id="@+id/imageview5"
android:layout_width="match_parent"
android:layout_height="300dp"
app:layout_constraintTop_toBottomOf="@+id/imageview4" />
<ImageView
android:id="@+id/imageview6"
android:layout_width="match_parent"
android:layout_height="300dp"
app:layout_constraintTop_toBottomOf="@+id/imageview5" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.crystal.customs.CustomScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
package com.crystal.fragments
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import com.crystal.R
import com.crystal.customs.CustomScrollView
import com.crystal.databinding.FragmentHomeBinding
private const val TAG = "HomeFragment"
class HomeFragment : Fragment() {
private lateinit var binding: FragmentHomeBinding
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_home, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupEvents()
scrollEvent()
}
private fun setupEvents() {
}
private fun scrollEvent() {
binding.scrollView.overScrollMode = View.OVER_SCROLL_NEVER
// ScrollView에서 받는 이벤트 처리
// 1: 완전 불투명
// 스크롤 위치에 따라 alpha 값이 변경되므로, 방향은 상관이 없다.
binding.scrollView.setOnScrollListener(object : CustomScrollView.OnScrollListener {
override fun onScroll(direction: Int, scrollY: Float) {
// statusBar 높이 구하기
var statusBarHeight = 0
val resId = resources.getIdentifier("status_bar_height", "dimen", "android")
if (resId > 0) {
statusBarHeight = resources.getDimensionPixelSize(resId)
}
// top_image 높이 구하기, 나는 끝까지 안올리고 100% 불투명도 만들기위해 statusbar 높이를 뺐다.
val backgroundImgHeight = binding.topImage.height - statusBarHeight
val alpha = ((backgroundImgHeight - scrollY) / backgroundImgHeight)
binding.topImage.alpha = alpha
}
})
}
}
난 위에 일부분을 배경이미지로 뒀지만 화면 전체를 배경이미지로 두고 전체 스크롤 시 최상단에서 최하단까지 투명도를 원하는 사람도 있을거라 생각한다. 그럼 내가 참고한 아래 링크로 가면 된다. 나도 저기 링크를 커스텀했다!