Focusing Hint EditText: A Picky requirement from UI Designer
No doubt about that Clubhouse is the most hottest social media recently. There is no special UI/UX which is never have seen anywhere else.
But I’m impressed by the ProgressBar. Even if it is not special something, it’s quite pretty and simple!
It could be a good example of Custom View. Because its view is simple and doesn’t have heavy animation.
class DotDrawable(private val radius: Float,
var paint: Paint) : Drawable() {
override fun draw(canvas: Canvas) {
canvas.drawCircle(bounds.exactCenterX(), bounds.exactCenterY(), radius, paint)
}
override fun setAlpha(alpha: Int) {}
override fun setColorFilter(colorFilter: ColorFilter?) {}
override fun getOpacity(): Int = PixelFormat.TRANSLUCENT
}
This is Dot Class which is created in ProgressBar we gonna make. It draw a Circle(dot) at center of bounds
which means something like drawing paper.
We going to set bounds
at ProgressBar Class.
Make sure that declare paint
as a public var
in Constructor! Not a private val
or public val
. We will check below why it should be a public var
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ClubhouseProgressBar">
<attr name="dotCount" format="integer"/>
<attr name="dotRadius" format="dimension"/>
<attr name="dotInterval" format="dimension"/>
<attr name="activeColor" format="color"/>
<attr name="inactiveColor" format="color"/>
<attr name="animationDuration" format="integer" />
</declare-styleable>
</resources>
Make this at res/values
folder. This is Custom Attributes will be used at Layout. After all the work is done, you can specify attributes like below.
<your.package.name.ClubhouseProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:dotCount="5"
app:dotRadius="20dp"
app:dotInterval="10dp"
app:inactiveColor="@color/purple_200"
app:activeColor="@color/purple_700"
app:animationDuration="500" />
init {
context.theme.obtainStyledAttributes(attrs, R.styleable.ChProgressBar, 0, 0).let {
try {
dotCount = it.getInt(R.styleable.ChProgressBar_dotCount, 3)
dotRadius = it.getDimension(R.styleable.ChProgressBar_dotRadius, 65f)
dotInterval = it.getDimension(R.styleable.ChProgressBar_dotInterval, 25f)
activePaint.color = it.getColor(R.styleable.ChProgressBar_activeColor, Color.BLACK)
inactivePaint.color = it.getColor(
R.styleable.ChProgressBar_inactiveColor,
Color.WHITE
)
animDuration = it.getInt(R.styleable.ChProgressBar_animationDuration, 200).toLong()
} finally {
it.recycle()
}
}
for (i in 0 until dotCount) {
dotList.add(DotDrawable(dotRadius, inactivePaint))
}
}
First of all, get Attributes that user set from Layout. And add Dot object into dotList
(mutableList).
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
adjustDotBounds()
}
private fun adjustDotBounds() {
val diameter = (dotRadius * 2).toInt()
val totalDotWidth = (diameter*dotCount + dotInterval*(dotCount-1)).toInt()
val diameterWithInterval = (diameter + dotInterval).toInt()
val paddingStart = (measuredWidth - totalDotWidth)/2f
var left = paddingStart.toInt()
val top = 0
var right = left + diameter
val bottom = measuredHeight
dotList.forEach {
it.setBounds(left, top, right, bottom)
left += diameterWithInterval
right += diameterWithInterval
}
}
Now it’s time to set bounds of each dot. totalDotWidth is the sum of every dot’s diameter and every dotInterval.
For example, if dotRadius
is 30, dotCount
is 5 and dotInterval
is 10, the totalDotWidth
is a 340.
paddingStart
is for no matter what width of this view user sets, locate at center like gravity center. We don’t have to consider the height, because
Clubhouse ProgressBar is only horizontal line.
Finally, set bounds of each dot by giving each coordinate. Dot will be drew center of this bounds.
override fun onAttachedToWindow() {
super.onAttachedToWindow()
createAnimation()
}
private fun createAnimation() {
val paintEvaluator = TypeEvaluator<Paint> { fraction, _, _ ->
when(fraction) {
1f -> inactivePaint
0f -> inactivePaint
else -> activePaint
}
}
dotList.forEach { dot ->
val animator = ObjectAnimator.ofObject(dot, "paint", paintEvaluator, inactivePaint)
animator.apply {
duration = animDuration
addUpdateListener { invalidate() }
}
animatorList.add(animator)
}
animatorSet.apply {
playSequentially(animatorList)
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
start()
}
})
start()
}
}
We need animation to change color of dots. So change paint
by ObjectAnimator.
To ObjectAnimator access to paint
property of Dot, we should have set as a public var
as mention above.
public var
makes getter and setter automatically.
If you work with Java, you should add setter method like below.
public void setPaint(Paint paint) {
this.paint = paint;
}
In TypeEvaluator
you can manipulate in fine detail animation. Observe fraction
at Log when animation is running, you can catch how fraction
works.
💫Do you want to see more specific full code? Visit my github repo
If there’s a mistake, always welcome your opinion!
Last month, Jetpack Compose Beta was released! see Beta Overview Even if it’s beta, looks cool for some super advantages. In this posting, I will focus on L...
No doubt about that Clubhouse is the most hottest social media recently. There is no special UI/UX which is never have seen anywhere else. But I’m impressed ...
Almost every Android developers have tried passing data and getting response between two activities. Before Result API released, we passed data on startActi...
Let’s say you already know about getter and setter of Java. The Accessors are like this:
No doubt about that Clubhouse is the most hottest social media recently. There is no special UI/UX which is never have seen anywhere else. But I’m impressed ...
Last month, Jetpack Compose Beta was released! see Beta Overview Even if it’s beta, looks cool for some super advantages. In this posting, I will focus on L...