Skip to content

Instantly share code, notes, and snippets.

@joseprl89
Created June 23, 2019 16:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save joseprl89/1e131353e62cb40fa17f6e1ed53a89b3 to your computer and use it in GitHub Desktop.
Save joseprl89/1e131353e62cb40fa17f6e1ed53a89b3 to your computer and use it in GitHub Desktop.
5 star rating view Android
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"
tools:context=".MainActivity">
<com.example.myapplication.views.RatingView
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
package com.example.myapplication
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.MotionEvent
import android.view.View
import com.example.myapplication.views.StarView
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="horizontal"
tools:parentTag="android.widget.LinearLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.example.myapplication.views.StarView
android:id="@+id/star_one"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"/>
<com.example.myapplication.views.StarView
android:id="@+id/star_two"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"/>
<com.example.myapplication.views.StarView
android:id="@+id/star_three"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"/>
<com.example.myapplication.views.StarView
android:id="@+id/star_four"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"/>
<com.example.myapplication.views.StarView
android:id="@+id/star_five"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"/>
</merge>
package com.example.myapplication.views
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.LinearLayout
import com.example.myapplication.R
import kotlinx.android.synthetic.main.rating_view_content.view.*
class RatingView: LinearLayout {
var selection: Selection = Selection.none()
val starViews: List<StarView>
get() = listOf(star_one, star_two, star_three, star_four, star_five)
constructor(context: Context?) : super(context) {
init(context)
}
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {
init(context)
}
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
init(context)
}
private fun init(context: Context?) {
val inflater = LayoutInflater.from(context)
inflater.inflate(R.layout.rating_view_content, this)
for (i in 0 until starViews.count()) {
starViews[i].onStarClicked = onStarClickedForStar(i)
starViews[i].state = StarView.State.deselected
}
}
private fun onStarClickedForStar(i: Int): StarView.OnStarClicked {
return object:StarView.OnStarClicked{
override fun starClicked(state: StarView.State) {
when (state) {
StarView.State.deselected -> selectUpTo(i)
StarView.State.selected -> selectUpTo(i)
else -> return
}
}
}
}
private fun selectUpTo(starIndex: Int) {
if (selection.shouldDeselectAll(starIndex)) {
selection = Selection.none()
starViews.forEach { it.state = StarView.State.deselected }
} else {
selection = Selection.selected(starIndex)
starViews.forEachIndexed { index, starView ->
starView.state = if (index <= starIndex) StarView.State.selected else StarView.State.deselected
}
}
}
@Suppress("DataClassPrivateConstructor")
data class Selection private constructor(val value: Int?) {
val isSelected: Boolean
get() = value != null
fun shouldDeselectAll(selectedIndex: Int): Boolean {
return isSelected && value == 0 && selectedIndex == 0
}
companion object Factory {
fun none(): Selection {
return Selection(null)
}
fun selected(i: Int): Selection {
return Selection(i)
}
}
}
}
package com.example.myapplication.views
import android.content.Context
import android.graphics.drawable.Drawable
import android.util.AttributeSet
import android.view.animation.AccelerateDecelerateInterpolator
import android.view.animation.Animation
import android.view.animation.Interpolator
import android.view.animation.ScaleAnimation
import android.widget.ImageSwitcher
import android.widget.ImageView
import com.example.myapplication.R
class StarView : ImageSwitcher {
enum class State {
selected,
deselected,
disabled
}
interface OnStarClicked {
fun starClicked(state: State)
}
var onStarClicked: OnStarClicked = object:OnStarClicked {
override fun starClicked(state: State) {
}
}
val State.drawable: Drawable?
get() = when (this) {
State.selected -> context.getDrawable(R.drawable.star_selected)
State.deselected -> context.getDrawable(R.drawable.star_deselected)
State.disabled -> context.getDrawable(R.drawable.star_disabled)
}
val State.scale: Float
get() = when (this) {
State.selected -> 1.0f
State.deselected -> 0.9f
State.disabled -> 0f
}
var state: State = State.deselected
set(value) {
val previousState = field
field = value
if (previousState != value) {
setImageDrawable(value.drawable)
// Recover 1.0f scale as the animation scale builds up on it, and it
// has been set in the init function already
scaleX = 1.0f
scaleY = 1.0f
val anim = ScaleAnimation(
previousState.scale, value.scale,
previousState.scale, value.scale, // Start and end values for the Y axis scaling
Animation.RELATIVE_TO_SELF, 0.5f, // Pivot point of X scaling
Animation.RELATIVE_TO_SELF, 0.5f // Pivot point of Y scaling
)
anim.fillAfter = true // Needed to keep the result of the animation
anim.duration = 300
anim.interpolator = AccelerateDecelerateInterpolator()
startAnimation(anim)
}
}
constructor(context: Context?) : super(context) { init(context) }
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) { init(context) }
private fun init(context: Context?) {
state = State.deselected
setFactory {
ImageView(context).apply {
setImageDrawable(state.drawable)
}
}
setInAnimation(context, android.R.anim.fade_in)
setOutAnimation(context, android.R.anim.fade_out)
setOnClickListener {
onStarClicked.starClicked(state)
}
scaleX = state.scale
scaleY = state.scale
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment