Touch Event 를 이용하여 이동이 자유로운 View 를 만들어보려고 합니다.
흔히 알고 있는 드래그 앤 드롭(Drag And Drop) 방식으로
화면에 벗어나지 않도록 좌표 값에 대한 예외 처리를 추가했습니다.
1. Touch시 이동 가능한 View 예제 결과
- 생성된 View는 Touch시 이동이 가능하다.
- View에서는 좌표 값을 표시한다.
- Close시 해당 View는 종료시킨다.
2. Drag And Drop View
아래 DragAndDropView 클래스 전문 입니다.
주요 내용을 요약해보자면..
- DragAndDropView 는 LinearLayout 를 상속받은 View 클래스 이다.
- TouchListener 인터페이스 구현을 통해 Touch 시 View 의 좌표 값을 갱신한다.
- DragAndDropViewListener 를 통해 Close 동작을 시킨다.
[DragAndDropView.kt]
class DragAndDropView(context: Context) : LinearLayout(context), View.OnTouchListener {
private val binding: DragAndDropViewBinding
private var statusBarHeight = 0
private var viewX = 0f
private var viewY = 0f
private var dragAndDropViewListener: DragAndDropViewListener? = null
init {
val layoutInflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
binding = DataBindingUtil.inflate(layoutInflater, R.layout.drag_and_drop_view, this, true)
// 상태바(Status Bar) 높이값 구하기
val resourceId = resources.getIdentifier("status_bar_height", "dimen", "android")
if (resourceId > 0) statusBarHeight = resources.getDimensionPixelSize(resourceId)
setOnTouchListener(this)
bringToFront() // View 최상단으로 올리기
binding.btnClose.setOnClickListener {
dragAndDropViewListener?.onClose()
}
}
interface DragAndDropViewListener {
fun onClose()
// TODO : You can customize listener.
}
fun setDragAndDropViewListener(listener: DragAndDropViewListener) {
dragAndDropViewListener = listener
}
override fun onTouch(view: View, event: MotionEvent): Boolean {
val parentWidth = (view.parent as ViewGroup).width
val parentHeight = (view.parent as ViewGroup).height
val width = parentWidth - view.width
val height = parentHeight - view.height
when (event.action) {
MotionEvent.ACTION_DOWN -> {
// Touch Event 가 발생한 View 내에서의 좌표값 저장
viewX = event.x
viewY = event.y + statusBarHeight
// (참조) Touch 시 y 좌표값이 틀어지는 현상이 있었는데 단말의 statusBar 만큼 차이를 주니까 해결됐다..
}
MotionEvent.ACTION_MOVE -> {
// 화면의 좌표값에서 View 내에서의 좌표값만큼 빼서 위치시킨다. (View 를 선택한 위치에 따른 Offset)
view.x = event.rawX - viewX
view.y = event.rawY - viewY
}
MotionEvent.ACTION_UP -> {
// 좌표값을 벗어났을 때 예외처리
if (view.x < 0) view.x = 0f
if (view.y < 0) view.y = 0f
if (view.x > width) view.x = width.toFloat()
if (view.y > height) view.y = height.toFloat()
}
}
binding.tvX.text = "x : ${view.x}"
binding.tvY.text = "y : ${view.y}"
return true
}
}
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout
android:layout_width="160dp"
android:layout_height="120dp"
android:background="@drawable/border"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="@drawable/border"
android:gravity="center"
android:orientation="vertical">
<TextView
android:id="@+id/tvX"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/tvY"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<Button
android:id="@+id/btnClose"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:layout_gravity="center"
android:text="Close"
android:textAllCaps="false" />
</LinearLayout>
</layout>
3. Touch Listenere 관련 부연설명
처음에 TouchListener 인터페이스를 구현할 때 어려움이 있었는데 몇가지 이해에 도움됐던 내용을 공유 드리려고 합니다.
Touch Event 에 x, y 좌표에 대한 정의 체크하기.
- event.x : View 내에서의 x 좌표
- event.y : View 내에서의 y 좌표
- event.rawX : 화면에서의 x 좌표
- event.rawY : 화면에서의 y 좌표
그림으로 좌표 체크하기 (화면에 벗어나지 못하도록 예외처리)
4. Add View
아래 MainActivity 클래스 전문 입니다.
addDragAndDropView 메서드를 통해 DragAndDropView 생성에서 Add View 까지 하는 동작을 확인하실 수 있습니다.
[MainActivity.kt]
class MainActivity : AppCompatActivity() {
lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.btnAddView.setOnClickListener {
addDragAndDropView()
}
}
private fun addDragAndDropView() {
val dragAndDropView = DragAndDropView(this)
dragAndDropView.setDragAndDropViewListener(object : DragAndDropView.DragAndDropViewListener {
override fun onClose() {
binding.mainFrameLayout.removeView(dragAndDropView)
}
})
val layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
binding.mainFrameLayout.addView(dragAndDropView, layoutParams)
}
}
[activity_main.xml]
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<FrameLayout
android:id="@+id/mainFrameLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/btnAddView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="Add View"
android:textAllCaps="false" />
</FrameLayout>
</layout>
[Reference]
출처 : https://bictoselfdev.blogspot.com/2023/02/drag-and-drop-view.html
'프로그래밍 > Android' 카테고리의 다른 글
[Android] Context와 메모리 누수 (0) | 2024.08.01 |
---|---|
[Android] Retrofit에 Header 추가하기 (0) | 2024.03.21 |
[Android] RecyclerView에 Header, Footer 추가 (0) | 2024.02.22 |
[Android] Library 생성 (0) | 2024.02.22 |
[Android] 앱의 설정화면구현(SwitchPreferenceCompat) (1) | 2023.12.26 |
댓글