Skip to content

Instantly share code, notes, and snippets.

@n8ebel
Created September 19, 2017 02:35
Show Gist options
  • Save n8ebel/1adfa415a63c0647868cf6725e29d244 to your computer and use it in GitHub Desktop.
Save n8ebel/1adfa415a63c0647868cf6725e29d244 to your computer and use it in GitHub Desktop.
Animations with BindingAdapters

Binding adapters work well for isolating animation control logic that is dependent on viewmodel data

  • the adapters can be general for things like smoothly animating text transitions
  • or the adapters can be specific to a particular feature/animation
  • putting the animation code at the binding adapter level is nice because it keeps it out of the viewmodel, but also out of the activity/fragment which can start to become large on their with various other setup/config logic
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="viewModel"
type="com.n8ebel.databindingsandbox.main.MainViewModel" />
</data>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:backgroundColor="@{viewModel.itemBackgroundId}"
>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:padding="16dp"
android:gravity="center"
app:backgroundDrawable="@{viewModel.screenBackgroundId}"
>
<ImageView
android:layout_width="64dp"
android:layout_height="64dp"
android:scaleType="centerInside"
android:tint="@color/colorAccent"
android:layout_margin="8dp"
app:imageUrl="@{viewModel.imageUrl}"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
app:mainSubtitleText="@{viewModel.number}"
/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Click Me"
android:onClick="@{() -> viewModel.handleClick()}"
/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Toggle Header"
android:onClick="@{() -> viewModel.toggleHeader()}"
/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Changer Header Text"
android:onClick="@{() -> viewModel.changeHeaderText()}"
/>
</LinearLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="88dp"
android:background="@android:color/background_light"
android:elevation="8dp"
app:isHeaderVisible="@{viewModel.isHeaderVisible}"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="64dp"
android:layout_gravity="center_vertical"
app:animatedText="@{viewModel.messageResId}"
app:textColor="@{viewModel.messageColorId}"
android:textSize="28sp"
android:layout_margin="8dp"
/>
</FrameLayout>
</FrameLayout>
</layout>
@BindingAdapter("isHeaderVisible")
fun setHeaderVisibility(view: View, isVisible: Boolean) {
if (view.visibility == View.VISIBLE) {
if (isVisible) {
return
}
view.animate()
.alpha(0f)
.translationY(-1f * view.height)
.setDuration(ANIM_TIME_VERY_SHORT)
.setListener(object : AnimatorListenerAdapter(){
override fun onAnimationEnd(animation: Animator?) {
super.onAnimationEnd(animation)
view.visibility = View.GONE
}
})
.start()
} else {
if (!isVisible) {
return
}
view.alpha = 0f
view.visibility = View.VISIBLE
view.translationY = -1f * view.height
view.animate()
.alpha(1f)
.translationY(0f)
.setDuration(ANIM_TIME_VERY_SHORT)
.setListener(object : AnimatorListenerAdapter(){
override fun onAnimationEnd(animation: Animator?) {
super.onAnimationEnd(animation)
}
})
.start()
}
}
@BindingAdapter("animatedText")
fun setAnimatedText(textView: TextView, @StringRes msgId:Int) {
val text = textView.context.getString(msgId)
if (TextUtils.isEmpty(textView.text)) {
textView.alpha = MIN_ALPHA
textView.animate().alpha(MAX_ALPHA).setDuration(ANIM_TIME_SHORT).setListener(object: AnimatorListenerAdapter() {
override fun onAnimationStart(animation: Animator?) {
super.onAnimationStart(animation)
textView.text = text
}
}).start()
return
}
textView.animate().alpha(MIN_ALPHA).setDuration(ANIM_TIME_VERY_SHORT).setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
super.onAnimationEnd(animation)
textView.text = ""
if (TextUtils.isEmpty(text)) {
return
}
// animate in the new text
textView.animate().alpha(MAX_ALPHA).setDuration(ANIM_TIME_SHORT).setListener(object: AnimatorListenerAdapter() {
override fun onAnimationStart(animation: Animator?) {
super.onAnimationStart(animation)
textView.text = text
}
}).start()
}
}).start()
}
class MainViewModel(@StringRes msgId:Int, num:Int, @ColorRes msgColorId:Int, val listener: ViewModelListener) {
val messageResId = ObservableInt(msgId)
val number = ObservableInt(num)
val messageColorId = ObservableInt(msgColorId)
val screenBackgroundId = ObservableInt(R.drawable.background_gradient)
val itemBackgroundId = ObservableInt(R.color.colorPrimaryDark)
val imageUrl = ""
val isHeaderVisible = ObservableBoolean(false)
private var headerIndex = 0
val headerIds = listOf(R.string.header1, R.string.header2)
@BoundFunction
fun handleClick() {
listener.showMessage("You Clicked It!!")
}
@BoundFunction
fun toggleHeader() {
isHeaderVisible.set(!isHeaderVisible.get())
}
@BoundFunction
fun changeHeaderText() {
messageResId.set(headerIds[headerIndex.rem(headerIds.size)])
headerIndex++
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment