Skip to content

Commit 9224518

Browse files
committed
move dots to root layout
1 parent 2877748 commit 9224518

6 files changed

Lines changed: 139 additions & 130 deletions

File tree

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package com.transferwise.sequencelayout
22

33
import android.content.res.Resources
4+
import android.view.ViewGroup
45

5-
val Int.toDp: Int
6-
get() = (this * Resources.getSystem().displayMetrics.density).toInt()
6+
fun Int.toPx() = (this * Resources.getSystem().displayMetrics.density).toInt()
7+
8+
fun ViewGroup.children() = 0.until(childCount).map { getChildAt(it) }

sequencelayout/src/main/java/com/transferwise/sequencelayout/SequenceLayout.kt

Lines changed: 95 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@ import android.graphics.Rect
66
import android.support.annotation.ColorInt
77
import android.support.annotation.StyleRes
88
import android.util.AttributeSet
9-
import android.util.SparseArray
9+
import android.util.Log
10+
import android.view.Gravity
1011
import android.view.View
1112
import android.view.ViewGroup
1213
import android.view.ViewTreeObserver
1314
import android.view.animation.LinearInterpolator
1415
import android.widget.FrameLayout
1516
import kotlinx.android.synthetic.main.sequence_layout.view.*
16-
import kotlinx.android.synthetic.main.sequence_step.view.*
1717

1818
/**
1919
* Vertical step tracker that contains {@link com.transferwise.sequencelayout.SequenceStep}s and animates to the first active step.
@@ -61,8 +61,10 @@ public class SequenceLayout(context: Context?, attrs: AttributeSet?, defStyleAtt
6161
start()
6262
}
6363

64-
@ColorInt private var progressBackgroundColor: Int = 0
65-
@ColorInt private var progressForegroundColor: Int = 0
64+
@ColorInt
65+
private var progressBackgroundColor: Int = 0
66+
@ColorInt
67+
private var progressForegroundColor: Int = 0
6668

6769
public fun start() {
6870
viewTreeObserver.addOnGlobalLayoutListener(this)
@@ -113,75 +115,108 @@ public class SequenceLayout(context: Context?, attrs: AttributeSet?, defStyleAtt
113115
val item = adapter.getItem(i)
114116
val view = SequenceStep(context)
115117
adapter.bindView(view, item)
116-
addView(view)
118+
stepsWrapper.addView(view)
117119
}
118120
start()
119121
}
120122

121123
private fun applyAttributes(attributes: TypedArray) {
122124
setupProgressForegroundColor(attributes)
123-
setupDotBackground(attributes)
125+
setupProgressBackgroundColor(attributes)
124126
}
125127

126128
private fun setupProgressForegroundColor(attributes: TypedArray) {
127129
setProgressForegroundColor(attributes.getColor(R.styleable.SequenceLayout_progressForegroundColor, 0))
128130
}
129131

130-
private fun setupDotBackground(attributes: TypedArray) {
132+
private fun setupProgressBackgroundColor(attributes: TypedArray) {
131133
setProgressBackgroundColor(attributes.getColor(R.styleable.SequenceLayout_progressBackgroundColor, 0))
132134
}
133135

134136
private fun setProgressBarHorizontalOffset() {
135-
val firstAnchor: View = stepsWrapper.getChildAt(0).anchor.findViewById(R.id.anchor)
136-
val firstDot: View = stepsWrapper.getChildAt(0).findViewById(R.id.dot)
137-
progressBarWrapper.translationX = firstAnchor.measuredWidth + (firstDot.measuredWidth - progressBarWrapper.measuredWidth) / 2f
137+
val firstAnchor: View = stepsWrapper.getChildAt(0).findViewById(R.id.anchor)
138+
progressBarWrapper.translationX = firstAnchor.measuredWidth + 4.toPx() - (progressBarWrapper.measuredWidth / 2f) //TODO dynamic dot size
138139
}
139140

140-
private fun animateToActive() {
141-
val stepsToAnimate = getStepOffsets()
142-
if (stepsToAnimate.size() > 0) {
143-
val lastChild = stepsWrapper.getChildAt(stepsWrapper.childCount - 1)
144-
val lastChildOffset = lastChild.top + lastChild.paddingTop
145-
progressBarForeground.visibility = VISIBLE
146-
progressBarForeground.pivotY = 0f
147-
progressBarForeground.scaleY = 0f
148-
progressBarForeground
149-
.animate()
150-
.setStartDelay(resources.getInteger(R.integer.sequence_step_duration).toLong())
151-
.scaleY(stepsToAnimate.keyAt(stepsToAnimate.size() - 1) / lastChildOffset.toFloat())
152-
.setInterpolator(LinearInterpolator())
153-
.setDuration((stepsToAnimate.size() - 1) * resources.getInteger(R.integer.sequence_step_duration).toLong())
154-
.setUpdateListener({
155-
val v = (progressBarForeground.scaleY * lastChildOffset)
156-
157-
for (i in 0..stepsToAnimate.size()) {
158-
if (stepsToAnimate.valueAt(i) != null) {
159-
val step = stepsToAnimate.valueAt(i) as SequenceStep
160-
if (stepsToAnimate.keyAt(i) <= v + step.paddingTop) {
161-
if (i == stepsToAnimate.size() - 1) {
162-
step.getDot().isActivated = true
163-
} else {
164-
step.getDot().isEnabled = true
165-
}
166-
stepsToAnimate.setValueAt(i, null)
167-
}
168-
}
169-
}
170-
})
171-
.start()
141+
private fun placeDots() {
142+
dotsWrapper.removeAllViews()
143+
var firstOffset = 0
144+
var lastOffset = 0
145+
146+
stepsWrapper.children().forEachIndexed { i, view ->
147+
val sequenceStep = view as SequenceStep
148+
val sequenceStepDot = SequenceStepDot(context)
149+
sequenceStepDot.setDotBackground(progressForegroundColor, progressBackgroundColor)
150+
sequenceStepDot.setPulseColor(progressForegroundColor)
151+
sequenceStepDot.clipChildren = false
152+
sequenceStepDot.clipToPadding = false
153+
val layoutParams = FrameLayout.LayoutParams(8.toPx(), 8.toPx()) //TODO dynamic dot size
154+
val totalDotOffset = getRelativeTop(sequenceStep, stepsWrapper) + sequenceStep.paddingTop + sequenceStep.getDotOffset() + 2.toPx() //TODO dynamic dot size
155+
layoutParams.topMargin = totalDotOffset
156+
layoutParams.gravity = Gravity.CENTER_HORIZONTAL
157+
dotsWrapper.addView(sequenceStepDot, layoutParams)
158+
if (i == 0) {
159+
firstOffset = totalDotOffset
160+
}
161+
lastOffset = totalDotOffset
172162
}
163+
164+
val layoutParams = progressBarBackground.layoutParams as MarginLayoutParams
165+
layoutParams.topMargin = firstOffset + 4.toPx() //TODO dynamic dot size
166+
layoutParams.height = lastOffset - firstOffset
167+
progressBarBackground.requestLayout()
168+
169+
val layoutParams2 = progressBarForeground.layoutParams as MarginLayoutParams
170+
layoutParams2.topMargin = firstOffset + 4.toPx() //TODO dynamic dot size
171+
layoutParams2.height = lastOffset - firstOffset
172+
progressBarForeground.requestLayout()
173173
}
174174

175-
private fun adaptProgressBarHeight() {
176-
val first = stepsWrapper.getChildAt(0) as SequenceStep
177-
val last = stepsWrapper.getChildAt(stepsWrapper.childCount - 1) as SequenceStep
178-
val height = (getRelativeTop(last.getDot(), this) + last.getDot().translationY) -
179-
(getRelativeTop(first.getDot(), this) + first.getDot().translationY)
175+
private fun animateToActive() {
176+
progressBarForeground.visibility = VISIBLE
177+
progressBarForeground.pivotY = 0f
178+
progressBarForeground.scaleY = 0f
179+
180+
val activeStepIndex = stepsWrapper.children().indexOfFirst { it is SequenceStep && it.isActive() }
181+
182+
if (activeStepIndex == -1) {
183+
return
184+
}
180185

181-
val layoutParams = progressBarWrapper.layoutParams as MarginLayoutParams
182-
layoutParams.height = height.toInt()
183-
layoutParams.topMargin = (getRelativeTop(first.getDot(), this) + (first.getDot().measuredHeight / 2) + first.getDot().translationY).toInt()
184-
progressBarWrapper.requestLayout()
186+
val activeDot = dotsWrapper.getChildAt(activeStepIndex)
187+
val activeDotTopMargin = (activeDot.layoutParams as LayoutParams).topMargin
188+
val progressBarForegroundTopMargin = (progressBarForeground.layoutParams as LayoutParams).topMargin
189+
val scaleEnd = (activeDotTopMargin + (activeDot.measuredHeight / 2) - progressBarForegroundTopMargin) /
190+
progressBarBackground.measuredHeight.toFloat()
191+
192+
progressBarForeground
193+
.animate()
194+
.setStartDelay(resources.getInteger(R.integer.sequence_step_duration).toLong())
195+
.scaleY(scaleEnd)
196+
.setInterpolator(LinearInterpolator())
197+
.setDuration(activeStepIndex * resources.getInteger(R.integer.sequence_step_duration).toLong())
198+
.setUpdateListener({
199+
val animatedOffset = progressBarForeground.scaleY * progressBarBackground.measuredHeight
200+
dotsWrapper
201+
.children()
202+
.forEachIndexed { i, view ->
203+
if (i > activeStepIndex) {
204+
return@forEachIndexed
205+
}
206+
val dot = view as SequenceStepDot
207+
val dotTopMargin = (dot.layoutParams as LayoutParams).topMargin -
208+
progressBarForegroundTopMargin -
209+
(dot.measuredHeight / 2)
210+
if (animatedOffset >= dotTopMargin) {
211+
if (i < activeStepIndex && !dot.isEnabled) {
212+
dot.isEnabled = true
213+
} else if (i == activeStepIndex && !dot.isActivated) {
214+
dot.isActivated = true
215+
}
216+
}
217+
}
218+
})
219+
.start()
185220
}
186221

187222
private fun getRelativeTop(child: View, parent: ViewGroup): Int {
@@ -191,28 +226,6 @@ public class SequenceLayout(context: Context?, attrs: AttributeSet?, defStyleAtt
191226
return offsetViewBounds.top
192227
}
193228

194-
private fun getStepOffsets(): SparseArray<View> {
195-
val childCount = stepsWrapper.childCount
196-
val stepOffsets = SparseArray<View>()
197-
var containsActiveStep = false
198-
for (i in 0 until childCount) {
199-
val step = stepsWrapper.getChildAt(i) as SequenceStep
200-
stepOffsets.append(step.top - progressBarForeground.top + step.getDot().top, step)
201-
if (step.isActive()) {
202-
containsActiveStep = true
203-
if (i == childCount - 1) {
204-
//remove bottom padding if active step is last step
205-
step.setPadding(step.paddingLeft, step.paddingTop, step.paddingRight, 0)
206-
}
207-
break
208-
}
209-
}
210-
if (!containsActiveStep) {
211-
stepOffsets.clear()
212-
}
213-
return stepOffsets
214-
}
215-
216229
override fun addView(child: View, index: Int, params: ViewGroup.LayoutParams) {
217230
if (child is SequenceStep) {
218231
if (child.isActive()) {
@@ -223,21 +236,21 @@ public class SequenceLayout(context: Context?, attrs: AttributeSet?, defStyleAtt
223236
resources.getDimensionPixelSize(R.dimen.sequence_active_step_padding_bottom)
224237
)
225238
}
226-
val dot = child.getDot()
227-
dot.setDotBackground(progressForegroundColor, progressBackgroundColor)
228-
dot.setPulseColor(progressForegroundColor)
229239
stepsWrapper.addView(child, params)
230240
return
231241
}
232242
super.addView(child, index, params)
233243
}
234244

235245
override fun onGlobalLayout() {
236-
if (stepsWrapper.childCount > 0) {
237-
adaptProgressBarHeight()
238-
setProgressBarHorizontalOffset()
239-
animateToActive()
240-
viewTreeObserver.removeOnGlobalLayoutListener(this)
241-
}
246+
Log.d("testcount", stepsWrapper.childCount.toString())
247+
handler.postDelayed({
248+
if (stepsWrapper.childCount > 0) {
249+
setProgressBarHorizontalOffset()
250+
placeDots()
251+
animateToActive()
252+
viewTreeObserver.removeOnGlobalLayoutListener(this)
253+
}
254+
}, 500)
242255
}
243256
}

sequencelayout/src/main/java/com/transferwise/sequencelayout/SequenceStep.kt

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import android.support.annotation.StyleRes
77
import android.support.v4.widget.TextViewCompat
88
import android.util.AttributeSet
99
import android.view.View
10-
import android.view.ViewTreeObserver
1110
import android.widget.TableRow
1211
import android.widget.TextView
1312
import kotlinx.android.synthetic.main.sequence_step.view.*
@@ -39,7 +38,7 @@ import kotlinx.android.synthetic.main.sequence_step.view.*
3938
* @see com.transferwise.sequencelayout.SequenceLayout
4039
*/
4140
public class SequenceStep(context: Context?, attrs: AttributeSet?)
42-
: TableRow(context, attrs), ViewTreeObserver.OnGlobalLayoutListener {
41+
: TableRow(context, attrs) {
4342

4443
public constructor(context: Context) : this(context, null)
4544

@@ -65,8 +64,6 @@ public class SequenceStep(context: Context?, attrs: AttributeSet?)
6564
setupSubtitleTextAppearance(attributes)
6665
setupActive(attributes)
6766

68-
viewTreeObserver.addOnGlobalLayoutListener(this)
69-
7067
onFinishInflate()
7168
attributes.recycle()
7269
}
@@ -89,6 +86,7 @@ public class SequenceStep(context: Context?, attrs: AttributeSet?)
8986
*/
9087
public fun setAnchorTextAppearance(@StyleRes resourceId: Int) {
9188
TextViewCompat.setTextAppearance(anchor, resourceId)
89+
verticallyCenter(anchor, title)
9290
}
9391

9492
/**
@@ -117,6 +115,7 @@ public class SequenceStep(context: Context?, attrs: AttributeSet?)
117115
*/
118116
public fun setTitleTextAppearance(@StyleRes resourceId: Int) {
119117
TextViewCompat.setTextAppearance(title, resourceId)
118+
verticallyCenter(anchor, title)
120119
}
121120

122121
/**
@@ -165,9 +164,8 @@ public class SequenceStep(context: Context?, attrs: AttributeSet?)
165164
this.isActive = isActive
166165
}
167166

168-
internal fun getDot(): SequenceStepDot {
169-
return dot
170-
}
167+
fun getDotOffset(): Int =
168+
(Math.max(getViewHeight(anchor), getViewHeight(title)) - 8.toPx()) / 2 //TODO dynamic dot height
171169

172170
private fun setupAnchor(attributes: TypedArray) {
173171
if (!attributes.hasValue(R.styleable.SequenceStep_anchor)) {
@@ -223,7 +221,8 @@ public class SequenceStep(context: Context?, attrs: AttributeSet?)
223221
}
224222
for (view in views) {
225223
val height = getViewHeight(view)
226-
view.translationY = (maxHeight - height).toFloat() / 2
224+
(view.layoutParams as MarginLayoutParams).topMargin = (maxHeight - height) / 2
225+
view.requestLayout()
227226
}
228227
}
229228

@@ -234,8 +233,4 @@ public class SequenceStep(context: Context?, attrs: AttributeSet?)
234233
view.measuredHeight
235234
}
236235

237-
override fun onGlobalLayout() {
238-
verticallyCenter(anchor, dot, title)
239-
viewTreeObserver.removeOnGlobalLayoutListener(this)
240-
}
241236
}

sequencelayout/src/main/java/com/transferwise/sequencelayout/SequenceStepDot.kt

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,14 @@ internal class SequenceStepDot(context: Context?, attrs: AttributeSet?, defStyle
4444
with(GradientDrawable()) {
4545
shape = OVAL
4646
setColor(color)
47-
setStroke(1.toDp, Color.TRANSPARENT)
47+
setStroke(1.toPx(), Color.TRANSPARENT)
4848
this
4949
})
5050
addState(intArrayOf(),
5151
with(GradientDrawable()) {
5252
shape = OVAL
5353
setColor(progressBackgroundColor)
54-
setStroke(1.toDp, Color.TRANSPARENT)
54+
setStroke(1.toPx(), Color.TRANSPARENT)
5555
this
5656
})
5757
dotView.background = this
@@ -66,6 +66,18 @@ internal class SequenceStepDot(context: Context?, attrs: AttributeSet?, defStyle
6666
}
6767
}
6868

69+
private fun setupAnimator() {
70+
pulseAnimator = AnimatorInflater.loadAnimator(context, R.animator.fading_pulse) as AnimatorSet
71+
pulseAnimator!!.setTarget(pulseView)
72+
pulseAnimator!!.addListener(object : AnimatorListenerAdapter() {
73+
override fun onAnimationEnd(animator: Animator) {
74+
if (isActivated) {
75+
animator.start()
76+
}
77+
}
78+
})
79+
}
80+
6981
private fun startAnimation() {
7082
if (pulseAnimator == null) {
7183
setupAnimator()
@@ -86,18 +98,6 @@ internal class SequenceStepDot(context: Context?, attrs: AttributeSet?, defStyle
8698
pulseView.visibility = GONE
8799
}
88100

89-
private fun setupAnimator() {
90-
pulseAnimator = AnimatorInflater.loadAnimator(context, R.animator.fading_pulse) as AnimatorSet
91-
pulseAnimator!!.setTarget(pulseView)
92-
pulseAnimator!!.addListener(object : AnimatorListenerAdapter() {
93-
override fun onAnimationEnd(animator: Animator) {
94-
if (isActivated) {
95-
animator.start()
96-
}
97-
}
98-
})
99-
}
100-
101101
override fun setActivated(activated: Boolean) {
102102
super.setActivated(activated)
103103
if (!activated) {

0 commit comments

Comments
 (0)