Home Android: Nested Coordinator Layout (중첩 CoordinatorLayout)
Post
Cancel

Android: Nested Coordinator Layout (중첩 CoordinatorLayout)

들어가며

본 글에서는 중첩된 CoordinatorLayout을 사용하는 경우 ChildCoordinator의 unconsumed scroll event를 ParentCoordinator로 전파하는 방법에대하여 정리

Nested ScrollEvent 전파

짧게 정리하자면 NestedScrollingChild에서 발생한 scroll event NestedScrollingParent로 전파되며 이때 전달되는 consumed, unconsumed x,y값을 활용하여 nested scroll을 구현한다. 이때 NestedScrollingParent, Child interface가 사용되며 compatibility를 위해 Parent, Parent2, Child1, Child2, Child3등의 interface가 존재한다.

예시

coordinator

흔히 볼수있는 CoordinatorLayout 구조이며 위 구조에서 RecyclerView(NestedScrollingChild)의 스크롤 이벤트 발생시 CoordinatorLayout(NestedScrollingParent)으로 이벤트가 전파되며 CoordinatorLayout에서는 포함된 Chlid View의 Behavior에 해당 이벤트를 전파한다.

1
2
3
4
5
6
7
8
9
10
11
//in CoordinatorLayout.onStartNestedScroll
final LayoutParams lp = (LayoutParams) view.getLayoutParams();
final Behavior viewBehavior = lp.getBehavior();
if (viewBehavior != null) {
    final boolean accepted = viewBehavior.onStartNestedScroll(this, view, child,
            target, axes, type);
    handled |= accepted;
    lp.setNestedScrollAccepted(type, accepted);
} else {
    lp.setNestedScrollAccepted(type, false);
}

NestedCoordinatorLayout 문제

nested coordinator

만약 위처럼 CoordinatorLayout이 중첩된 Layout을 구현한다면 inner Coordinator의 스크롤이 발생해도 outer Coordinator에는 스크롤이 전파되지 않는 문제. 위 예시에서는 이해를 돕기위해 AppBar, BottomNavigation으로 명시했으나 CoordinatorLayout.Behavior을 구현한 많은 컴포넌트가 포함되는 경우가 일반적이다.

해결방법

앞서 정리해본 내용을 토대로 생각해보면, CoordinatorLayout은 NestedScrollingParent이므로 내부의 Behaviour에는 스크롤 이벤트가 정상적으로 전파되나 부모 CoordinatorLayout에는 전파되지 않는다. 여기서 자식 CoordinatorLayout에서 NestedScrollingChild를 구현하면 부모 CoordinatorLayout에게 이벤트를 전파 할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
class NestedCoordinatorLayout @JvmOverloads constructor(
  context: Context, attrs: AttributeSet? = null
) : CoordinatorLayout(context, attrs), NestedScrollingChild3 {

  private val helper = NestedScrollingChildHelper(this)

  init {
    isNestedScrollingEnabled = true
  }

  override fun isNestedScrollingEnabled(): Boolean = helper.isNestedScrollingEnabled

  override fun setNestedScrollingEnabled(enabled: Boolean) {
    helper.isNestedScrollingEnabled = enabled
  }

  override fun hasNestedScrollingParent(type: Int): Boolean =
    helper.hasNestedScrollingParent(type)

  override fun hasNestedScrollingParent(): Boolean = helper.hasNestedScrollingParent()

  override fun onStartNestedScroll(child: View, target: View, axes: Int, type: Int): Boolean {
    val superResult = super.onStartNestedScroll(child, target, axes, type)
    return startNestedScroll(axes, type) || superResult
  }

  override fun onStartNestedScroll(child: View, target: View, axes: Int): Boolean {
    val superResult = super.onStartNestedScroll(child, target, axes)
    return startNestedScroll(axes) || superResult
  }

  override fun onNestedPreScroll(target: View, dx: Int, dy: Int, consumed: IntArray, type: Int) {
    val superConsumed = intArrayOf(0, 0)
    super.onNestedPreScroll(target, dx, dy, superConsumed, type)
    val thisConsumed = intArrayOf(0, 0)
    dispatchNestedPreScroll(dx, dy, consumed, null, type)
    consumed[0] = superConsumed[0] + thisConsumed[0]
    consumed[1] = superConsumed[1] + thisConsumed[1]
  }

  override fun onNestedPreScroll(target: View, dx: Int, dy: Int, consumed: IntArray) {
    val superConsumed = intArrayOf(0, 0)
    super.onNestedPreScroll(target, dx, dy, superConsumed)
    val thisConsumed = intArrayOf(0, 0)
    dispatchNestedPreScroll(dx, dy, consumed, null)
    consumed[0] = superConsumed[0] + thisConsumed[0]
    consumed[1] = superConsumed[1] + thisConsumed[1]
  }

  override fun onNestedScroll(
    target: View,
    dxConsumed: Int,
    dyConsumed: Int,
    dxUnconsumed: Int,
    dyUnconsumed: Int,
    type: Int,
    consumed: IntArray
  ) {
    dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, null, type)
    super.onNestedScroll(
      target,
      dxConsumed,
      dyConsumed,
      dxUnconsumed,
      dyUnconsumed,
      type,
      consumed
    )
  }

  override fun onNestedScroll(
    target: View, dxConsumed: Int, dyConsumed: Int, dxUnconsumed: Int,
    dyUnconsumed: Int, type: Int
  ) {
    super.onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type)
    dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, null, type)
  }

  override fun onNestedScroll(
    target: View,
    dxConsumed: Int,
    dyConsumed: Int,
    dxUnconsumed: Int,
    dyUnconsumed: Int
  ) {
    super.onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed)
    dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, null)
  }

  override fun onStopNestedScroll(target: View, type: Int) {
    super.onStopNestedScroll(target, type)
    stopNestedScroll(type)
  }

  override fun onStopNestedScroll(target: View) {
    super.onStopNestedScroll(target)
    stopNestedScroll()
  }

  override fun onNestedPreFling(target: View, velocityX: Float, velocityY: Float): Boolean {
    val superResult = super.onNestedPreFling(target, velocityX, velocityY)
    return dispatchNestedPreFling(velocityX, velocityY) || superResult
  }

  override fun onNestedFling(
    target: View,
    velocityX: Float,
    velocityY: Float,
    consumed: Boolean
  ): Boolean {
    val superResult = super.onNestedFling(target, velocityX, velocityY, consumed)
    return dispatchNestedFling(velocityX, velocityY, consumed) || superResult
  }

  override fun startNestedScroll(axes: Int, type: Int): Boolean =
    helper.startNestedScroll(axes, type)

  override fun startNestedScroll(axes: Int): Boolean = helper.startNestedScroll(axes)

  override fun stopNestedScroll(type: Int) {
    helper.stopNestedScroll(type)
  }

  override fun stopNestedScroll() {
    helper.stopNestedScroll()
  }

  override fun dispatchNestedScroll(
    dxConsumed: Int,
    dyConsumed: Int,
    dxUnconsumed: Int,
    dyUnconsumed: Int,
    offsetInWindow: IntArray?,
    type: Int,
    consumed: IntArray
  ) {
    helper.dispatchNestedScroll(
      dxConsumed,
      dyConsumed,
      dxUnconsumed,
      dyUnconsumed,
      offsetInWindow,
      type,
      consumed
    )
  }

  override fun dispatchNestedScroll(
    dxConsumed: Int, dyConsumed: Int, dxUnconsumed: Int, dyUnconsumed: Int,
    offsetInWindow: IntArray?, type: Int
  ): Boolean = helper.dispatchNestedScroll(
    dxConsumed,
    dyConsumed,
    dxUnconsumed,
    dyUnconsumed,
    offsetInWindow,
    type
  )

  override fun dispatchNestedScroll(
    dxConsumed: Int, dyConsumed: Int, dxUnconsumed: Int,
    dyUnconsumed: Int, offsetInWindow: IntArray?
  ): Boolean = helper.dispatchNestedScroll(
    dxConsumed,
    dyConsumed,
    dxUnconsumed,
    dyUnconsumed,
    offsetInWindow
  )

  override fun dispatchNestedPreScroll(
    dx: Int, dy: Int, consumed: IntArray?,
    offsetInWindow: IntArray?, type: Int
  ): Boolean = helper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, type)

  override fun dispatchNestedPreScroll(
    dx: Int,
    dy: Int,
    consumed: IntArray?,
    offsetInWindow: IntArray?
  ): Boolean =
    helper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow)

  override fun dispatchNestedPreFling(velocityX: Float, velocityY: Float): Boolean =
    helper.dispatchNestedPreFling(velocityX, velocityY)

  override fun dispatchNestedFling(
    velocityX: Float,
    velocityY: Float,
    consumed: Boolean
  ): Boolean =
    helper.dispatchNestedFling(velocityX, velocityY, consumed)

}
This post is licensed under CC BY 4.0 by the author.

Spring: Security 인가설정 - Basic

Android: ComposeView inside CoordinatorLayout (ComposeView CoordinatorLayout에서 사용하기)