Skip to content

Commit

Permalink
move dots to root layout
Browse files Browse the repository at this point in the history
  • Loading branch information
saschoar committed Mar 25, 2018
1 parent 2877748 commit 9224518
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 130 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.transferwise.sequencelayout

import android.content.res.Resources
import android.view.ViewGroup

val Int.toDp: Int
get() = (this * Resources.getSystem().displayMetrics.density).toInt()
fun Int.toPx() = (this * Resources.getSystem().displayMetrics.density).toInt()

fun ViewGroup.children() = 0.until(childCount).map { getChildAt(it) }
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ import android.graphics.Rect
import android.support.annotation.ColorInt
import android.support.annotation.StyleRes
import android.util.AttributeSet
import android.util.SparseArray
import android.util.Log
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.view.ViewTreeObserver
import android.view.animation.LinearInterpolator
import android.widget.FrameLayout
import kotlinx.android.synthetic.main.sequence_layout.view.*
import kotlinx.android.synthetic.main.sequence_step.view.*

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

@ColorInt private var progressBackgroundColor: Int = 0
@ColorInt private var progressForegroundColor: Int = 0
@ColorInt
private var progressBackgroundColor: Int = 0
@ColorInt
private var progressForegroundColor: Int = 0

public fun start() {
viewTreeObserver.addOnGlobalLayoutListener(this)
Expand Down Expand Up @@ -113,75 +115,108 @@ public class SequenceLayout(context: Context?, attrs: AttributeSet?, defStyleAtt
val item = adapter.getItem(i)
val view = SequenceStep(context)
adapter.bindView(view, item)
addView(view)
stepsWrapper.addView(view)
}
start()
}

private fun applyAttributes(attributes: TypedArray) {
setupProgressForegroundColor(attributes)
setupDotBackground(attributes)
setupProgressBackgroundColor(attributes)
}

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

private fun setupDotBackground(attributes: TypedArray) {
private fun setupProgressBackgroundColor(attributes: TypedArray) {
setProgressBackgroundColor(attributes.getColor(R.styleable.SequenceLayout_progressBackgroundColor, 0))
}

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

private fun animateToActive() {
val stepsToAnimate = getStepOffsets()
if (stepsToAnimate.size() > 0) {
val lastChild = stepsWrapper.getChildAt(stepsWrapper.childCount - 1)
val lastChildOffset = lastChild.top + lastChild.paddingTop
progressBarForeground.visibility = VISIBLE
progressBarForeground.pivotY = 0f
progressBarForeground.scaleY = 0f
progressBarForeground
.animate()
.setStartDelay(resources.getInteger(R.integer.sequence_step_duration).toLong())
.scaleY(stepsToAnimate.keyAt(stepsToAnimate.size() - 1) / lastChildOffset.toFloat())
.setInterpolator(LinearInterpolator())
.setDuration((stepsToAnimate.size() - 1) * resources.getInteger(R.integer.sequence_step_duration).toLong())
.setUpdateListener({
val v = (progressBarForeground.scaleY * lastChildOffset)

for (i in 0..stepsToAnimate.size()) {
if (stepsToAnimate.valueAt(i) != null) {
val step = stepsToAnimate.valueAt(i) as SequenceStep
if (stepsToAnimate.keyAt(i) <= v + step.paddingTop) {
if (i == stepsToAnimate.size() - 1) {
step.getDot().isActivated = true
} else {
step.getDot().isEnabled = true
}
stepsToAnimate.setValueAt(i, null)
}
}
}
})
.start()
private fun placeDots() {
dotsWrapper.removeAllViews()
var firstOffset = 0
var lastOffset = 0

stepsWrapper.children().forEachIndexed { i, view ->
val sequenceStep = view as SequenceStep
val sequenceStepDot = SequenceStepDot(context)
sequenceStepDot.setDotBackground(progressForegroundColor, progressBackgroundColor)
sequenceStepDot.setPulseColor(progressForegroundColor)
sequenceStepDot.clipChildren = false
sequenceStepDot.clipToPadding = false
val layoutParams = FrameLayout.LayoutParams(8.toPx(), 8.toPx()) //TODO dynamic dot size
val totalDotOffset = getRelativeTop(sequenceStep, stepsWrapper) + sequenceStep.paddingTop + sequenceStep.getDotOffset() + 2.toPx() //TODO dynamic dot size
layoutParams.topMargin = totalDotOffset
layoutParams.gravity = Gravity.CENTER_HORIZONTAL
dotsWrapper.addView(sequenceStepDot, layoutParams)
if (i == 0) {
firstOffset = totalDotOffset
}
lastOffset = totalDotOffset
}

val layoutParams = progressBarBackground.layoutParams as MarginLayoutParams
layoutParams.topMargin = firstOffset + 4.toPx() //TODO dynamic dot size
layoutParams.height = lastOffset - firstOffset
progressBarBackground.requestLayout()

val layoutParams2 = progressBarForeground.layoutParams as MarginLayoutParams
layoutParams2.topMargin = firstOffset + 4.toPx() //TODO dynamic dot size
layoutParams2.height = lastOffset - firstOffset
progressBarForeground.requestLayout()
}

private fun adaptProgressBarHeight() {
val first = stepsWrapper.getChildAt(0) as SequenceStep
val last = stepsWrapper.getChildAt(stepsWrapper.childCount - 1) as SequenceStep
val height = (getRelativeTop(last.getDot(), this) + last.getDot().translationY) -
(getRelativeTop(first.getDot(), this) + first.getDot().translationY)
private fun animateToActive() {
progressBarForeground.visibility = VISIBLE
progressBarForeground.pivotY = 0f
progressBarForeground.scaleY = 0f

val activeStepIndex = stepsWrapper.children().indexOfFirst { it is SequenceStep && it.isActive() }

if (activeStepIndex == -1) {
return
}

val layoutParams = progressBarWrapper.layoutParams as MarginLayoutParams
layoutParams.height = height.toInt()
layoutParams.topMargin = (getRelativeTop(first.getDot(), this) + (first.getDot().measuredHeight / 2) + first.getDot().translationY).toInt()
progressBarWrapper.requestLayout()
val activeDot = dotsWrapper.getChildAt(activeStepIndex)
val activeDotTopMargin = (activeDot.layoutParams as LayoutParams).topMargin
val progressBarForegroundTopMargin = (progressBarForeground.layoutParams as LayoutParams).topMargin
val scaleEnd = (activeDotTopMargin + (activeDot.measuredHeight / 2) - progressBarForegroundTopMargin) /
progressBarBackground.measuredHeight.toFloat()

progressBarForeground
.animate()
.setStartDelay(resources.getInteger(R.integer.sequence_step_duration).toLong())
.scaleY(scaleEnd)
.setInterpolator(LinearInterpolator())
.setDuration(activeStepIndex * resources.getInteger(R.integer.sequence_step_duration).toLong())
.setUpdateListener({
val animatedOffset = progressBarForeground.scaleY * progressBarBackground.measuredHeight
dotsWrapper
.children()
.forEachIndexed { i, view ->
if (i > activeStepIndex) {
return@forEachIndexed
}
val dot = view as SequenceStepDot
val dotTopMargin = (dot.layoutParams as LayoutParams).topMargin -
progressBarForegroundTopMargin -
(dot.measuredHeight / 2)
if (animatedOffset >= dotTopMargin) {
if (i < activeStepIndex && !dot.isEnabled) {
dot.isEnabled = true
} else if (i == activeStepIndex && !dot.isActivated) {
dot.isActivated = true
}
}
}
})
.start()
}

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

private fun getStepOffsets(): SparseArray<View> {
val childCount = stepsWrapper.childCount
val stepOffsets = SparseArray<View>()
var containsActiveStep = false
for (i in 0 until childCount) {
val step = stepsWrapper.getChildAt(i) as SequenceStep
stepOffsets.append(step.top - progressBarForeground.top + step.getDot().top, step)
if (step.isActive()) {
containsActiveStep = true
if (i == childCount - 1) {
//remove bottom padding if active step is last step
step.setPadding(step.paddingLeft, step.paddingTop, step.paddingRight, 0)
}
break
}
}
if (!containsActiveStep) {
stepOffsets.clear()
}
return stepOffsets
}

override fun addView(child: View, index: Int, params: ViewGroup.LayoutParams) {
if (child is SequenceStep) {
if (child.isActive()) {
Expand All @@ -223,21 +236,21 @@ public class SequenceLayout(context: Context?, attrs: AttributeSet?, defStyleAtt
resources.getDimensionPixelSize(R.dimen.sequence_active_step_padding_bottom)
)
}
val dot = child.getDot()
dot.setDotBackground(progressForegroundColor, progressBackgroundColor)
dot.setPulseColor(progressForegroundColor)
stepsWrapper.addView(child, params)
return
}
super.addView(child, index, params)
}

override fun onGlobalLayout() {
if (stepsWrapper.childCount > 0) {
adaptProgressBarHeight()
setProgressBarHorizontalOffset()
animateToActive()
viewTreeObserver.removeOnGlobalLayoutListener(this)
}
Log.d("testcount", stepsWrapper.childCount.toString())
handler.postDelayed({
if (stepsWrapper.childCount > 0) {
setProgressBarHorizontalOffset()
placeDots()
animateToActive()
viewTreeObserver.removeOnGlobalLayoutListener(this)
}
}, 500)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import android.support.annotation.StyleRes
import android.support.v4.widget.TextViewCompat
import android.util.AttributeSet
import android.view.View
import android.view.ViewTreeObserver
import android.widget.TableRow
import android.widget.TextView
import kotlinx.android.synthetic.main.sequence_step.view.*
Expand Down Expand Up @@ -39,7 +38,7 @@ import kotlinx.android.synthetic.main.sequence_step.view.*
* @see com.transferwise.sequencelayout.SequenceLayout
*/
public class SequenceStep(context: Context?, attrs: AttributeSet?)
: TableRow(context, attrs), ViewTreeObserver.OnGlobalLayoutListener {
: TableRow(context, attrs) {

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

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

viewTreeObserver.addOnGlobalLayoutListener(this)

onFinishInflate()
attributes.recycle()
}
Expand All @@ -89,6 +86,7 @@ public class SequenceStep(context: Context?, attrs: AttributeSet?)
*/
public fun setAnchorTextAppearance(@StyleRes resourceId: Int) {
TextViewCompat.setTextAppearance(anchor, resourceId)
verticallyCenter(anchor, title)
}

/**
Expand Down Expand Up @@ -117,6 +115,7 @@ public class SequenceStep(context: Context?, attrs: AttributeSet?)
*/
public fun setTitleTextAppearance(@StyleRes resourceId: Int) {
TextViewCompat.setTextAppearance(title, resourceId)
verticallyCenter(anchor, title)
}

/**
Expand Down Expand Up @@ -165,9 +164,8 @@ public class SequenceStep(context: Context?, attrs: AttributeSet?)
this.isActive = isActive
}

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

private fun setupAnchor(attributes: TypedArray) {
if (!attributes.hasValue(R.styleable.SequenceStep_anchor)) {
Expand Down Expand Up @@ -223,7 +221,8 @@ public class SequenceStep(context: Context?, attrs: AttributeSet?)
}
for (view in views) {
val height = getViewHeight(view)
view.translationY = (maxHeight - height).toFloat() / 2
(view.layoutParams as MarginLayoutParams).topMargin = (maxHeight - height) / 2
view.requestLayout()
}
}

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

override fun onGlobalLayout() {
verticallyCenter(anchor, dot, title)
viewTreeObserver.removeOnGlobalLayoutListener(this)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,14 @@ internal class SequenceStepDot(context: Context?, attrs: AttributeSet?, defStyle
with(GradientDrawable()) {
shape = OVAL
setColor(color)
setStroke(1.toDp, Color.TRANSPARENT)
setStroke(1.toPx(), Color.TRANSPARENT)
this
})
addState(intArrayOf(),
with(GradientDrawable()) {
shape = OVAL
setColor(progressBackgroundColor)
setStroke(1.toDp, Color.TRANSPARENT)
setStroke(1.toPx(), Color.TRANSPARENT)
this
})
dotView.background = this
Expand All @@ -66,6 +66,18 @@ internal class SequenceStepDot(context: Context?, attrs: AttributeSet?, defStyle
}
}

private fun setupAnimator() {
pulseAnimator = AnimatorInflater.loadAnimator(context, R.animator.fading_pulse) as AnimatorSet
pulseAnimator!!.setTarget(pulseView)
pulseAnimator!!.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animator: Animator) {
if (isActivated) {
animator.start()
}
}
})
}

private fun startAnimation() {
if (pulseAnimator == null) {
setupAnimator()
Expand All @@ -86,18 +98,6 @@ internal class SequenceStepDot(context: Context?, attrs: AttributeSet?, defStyle
pulseView.visibility = GONE
}

private fun setupAnimator() {
pulseAnimator = AnimatorInflater.loadAnimator(context, R.animator.fading_pulse) as AnimatorSet
pulseAnimator!!.setTarget(pulseView)
pulseAnimator!!.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animator: Animator) {
if (isActivated) {
animator.start()
}
}
})
}

override fun setActivated(activated: Boolean) {
super.setActivated(activated)
if (!activated) {
Expand Down
Loading

0 comments on commit 9224518

Please sign in to comment.