squeezing precious milliseconds
Native Android animations
Expand/Collapse animation on Android
Expand/Collapse animation on Android
Animating values
val animator = ObjectAnimator.ofInt(0, 100)
Animating values
val animator = ObjectAnimator.ofInt(0, 100)
animator.duration = 1000
Animating values
val animator = ObjectAnimator.ofInt(0, 100)
animator.duration = 1000
animator.interpolator = FastOutSlowInInterpolator()
Animating values
val animator = ObjectAnimator.ofInt(0, 100)
animator.duration = 1000
animator.interpolator = FastOutSlowInInterpolator()
animator.addUpdateListener { Log.d("pasha", "${it.animatedValue}") }1
Animating values
val animator = ObjectAnimator.ofInt(0, 100)
animator.duration = 1000
animator.interpolator = FastOutSlowInInterpolator()
animator.addUpdateListener { Log.d("pasha", "${it.animatedValue}") }1
animator.start()
Animating values
val animator = ObjectAnimator.ofInt(0, 100)
animator.apply {
duration = 1000
interpolator = FastOutSlowInInterpolator()
addUpdateListener { Log.d("pasha", "${it.animatedValue}") }1
start()
}2
Animating values
Animating view properties
Animating view properties
Animating view properties
val animator = ObjectAnimator.ofFloat(1f, 1.5f)
animator.apply {
duration = 1000
interpolator = OvershootInterpolator()
addUpdateListener {
view.scaleX = it.animatedValue
view.scaleY = it.animatedValue
}1
start()
}2
Animating view properties
val animator = ObjectAnimator.ofFloat(1f, 1.5f)
animator.apply {
duration = 1000
interpolator = OvershootInterpolator()
addUpdateListener {
view.scaleX = it.animatedValue
view.scaleY = it.animatedValue
}1
start()
}2
Animating view properties
val animator = ObjectAnimator.ofFloat(1f, 1.5f)
animator.apply {
duration = 1000
interpolator = OvershootInterpolator()
addUpdateListener {
view.scaleX = it.animatedValue
view.scaleY = it.animatedValue
}1
start()
}2
Animating view properties
val animator = ObjectAnimator.ofFloat(1f, 1.5f)
animator.apply {
duration = 1000
interpolator = OvershootInterpolator()
addUpdateListener {
view.scaleX = it.animatedValue
view.scaleY = it.animatedValue
}1
start()
}2
Animating view properties
view.animate()
.scaleX(1.5f)
.scaleY(1.5f)
.setDuration(1000)
.setInterpolator(OvershootInterpolator())
.start()
Animating view properties
view.animate()
.scaleX(1.5f)
.scaleY(1.5f)
.setDuration(1000)
.setInterpolator(OvershootInterpolator())
.start()
Animating view properties
view.animate()
.scaleX(1.5f)
.scaleY(1.5f)
.setDuration(1000)
.setInterpolator(OvershootInterpolator())
.start()
Animating view properties
• scale
view.animate()
.scaleX(1.5f)
.scaleY(1.5f)
.setDuration(1000)
.setInterpolator(OvershootInterpolator())
.start()
Animating view properties
• scale
• alpha
view.animate()
.scaleX(1.5f)
.scaleY(1.5f)
.setDuration(1000)
.setInterpolator(OvershootInterpolator())
.start()
Animating view properties
• scale
• alpha
• translation
view.animate()
.scaleX(1.5f)
.scaleY(1.5f)
.setDuration(1000)
.setInterpolator(OvershootInterpolator())
.start()
Animating view properties
• scale
• alpha
• translation
• rotation
Changing view properties does
not trigger measure/layout pass
Expand/Collapse animation on Android
Expand/Collapse animation on Android
Expand/Collapse animation on Android
Animate LayoutParams!
private fun expand(view: View) {
}3
Animate LayoutParams!
private fun expand(view: View) {
val oldHeight = view.measuredHeight
val newHeight = oldHeight + 300
}3
Animate LayoutParams!
private fun expand(view: View) {
val oldHeight = view.measuredHeight
val newHeight = oldHeight + 300
ObjectAnimator.ofInt(oldHeight, newHeight).apply {
}2
}3
Animate LayoutParams!
private fun expand(view: View) {
val oldHeight = view.measuredHeight
val newHeight = oldHeight + 300
ObjectAnimator.ofInt(oldHeight, newHeight).apply {
duration = 1000
interpolator = FastOutSlowInInterpolator()
}2
}3
Animate LayoutParams!
private fun expand(view: View) {
val oldHeight = view.measuredHeight
val newHeight = oldHeight + 300
ObjectAnimator.ofInt(oldHeight, newHeight).apply {
duration = 1000
interpolator = FastOutSlowInInterpolator()
addUpdateListener {
view.layoutParams.height = it.animatedValue
}1
start()
}2
}3
Animate LayoutParams!
private fun expand(view: View) {
val oldHeight = view.measuredHeight
val newHeight = oldHeight + 300
ObjectAnimator.ofInt(oldHeight, newHeight).apply {
duration = 1000
interpolator = FastOutSlowInInterpolator()
addUpdateListener {
view.layoutParams.height = it.animatedValue
view.requestLayout()
}1
start()
}2
}3
Animate LayoutParams!
private fun expand(view: View) {
val oldHeight = view.measuredHeight
val newHeight = oldHeight + 300
ObjectAnimator.ofInt(oldHeight, newHeight).apply {
duration = 1000
interpolator = FastOutSlowInInterpolator()
addUpdateListener {
view.layoutParams.height = it.animatedValue
view.requestLayout()
}1
start()
}2
}3
Animate LayoutParams!
Expand/Collapse animation on Android
Expand/Collapse animation on Android
Expand/Collapse animation on Android
Expand/Collapse animation on Android
Expand/Collapse animation on Android
{16
ms
{16
ms
frames
Expand/Collapse animation on Android
Avoiding layouts
val oldHeight = textView.measuredHeight
Expand
val oldHeight = textView.measuredHeight
showFullText() // <-- request layout
Expand
val oldHeight = textView.measuredHeight
showFullText() // <-- request layout
Expand
val oldHeight = textView.measuredHeight
showFullText() // <-- request layout
textView.viewTreeObserver.addOnPreDrawListener {
}2
Expand
val oldHeight = textView.measuredHeight
showFullText() // <-- request layout
textView.viewTreeObserver.addOnPreDrawListener {
val sizeChange = textView.measuredHeight - oldHeight
}2
Expand
val oldHeight = textView.measuredHeight
showFullText() // <-- request layout
textView.viewTreeObserver.addOnPreDrawListener {
val sizeChange = textView.measuredHeight - oldHeight
}2
Expand
val oldHeight = textView.measuredHeight
showFullText() // <-- request layout
textView.viewTreeObserver.addOnPreDrawListener {
val sizeChange = textView.measuredHeight - oldHeight
footer.translationY -= sizeChange
}2
Expand
val oldHeight = textView.measuredHeight
showFullText() // <-- request layout
textView.viewTreeObserver.addOnPreDrawListener {
val sizeChange = textView.measuredHeight - oldHeight
footer.translationY -= sizeChange
}2
Expand
val oldHeight = textView.measuredHeight
showFullText() // <-- request layout
textView.viewTreeObserver.addOnPreDrawListener {
val sizeChange = textView.measuredHeight - oldHeight
footer.translationY -= sizeChange
val clipRect = Rect()
clipRect.right = root.measuredWidth
clipRect.bottom = root.measuredHeight - sizeChange
root.clipBounds = clipRect
}2
Expand
val oldHeight = textView.measuredHeight
showFullText() // <-- request layout
textView.viewTreeObserver.addOnPreDrawListener {
val sizeChange = textView.measuredHeight - oldHeight
footer.translationY -= sizeChange
val clipRect = Rect()
clipRect.right = root.measuredWidth
clipRect.bottom = root.measuredHeight - sizeChange
root.clipBounds = clipRect
}2
Expand
val oldHeight = textView.measuredHeight
showFullText() // <-- request layout
textView.viewTreeObserver.addOnPreDrawListener {
val sizeChange = textView.measuredHeight - oldHeight
footer.translationY -= sizeChange
val clipRect = Rect()
clipRect.right = root.measuredWidth
clipRect.bottom = root.measuredHeight - sizeChange
root.clipBounds = clipRect
ObjectAnimator
.ofFloat(-sizeChange, 0f)
.addUpdateListener {
footer.translationY = it.animatedValue
clipRect.bottom = root.measuredHeight + it.animatedValue
root.clipBounds = clipRect
}1
.start()
}2
Expand
val oldHeight = textView.measuredHeight
showFullText() // <-- request layout
textView.viewTreeObserver.addOnPreDrawListener {
val sizeChange = textView.measuredHeight - oldHeight
footer.translationY -= sizeChange
val clipRect = Rect()
clipRect.right = root.measuredWidth
clipRect.bottom = root.measuredHeight - sizeChange
root.clipBounds = clipRect
ObjectAnimator
.ofFloat(-sizeChange, 0f)
.addUpdateListener {
footer.translationY = it.animatedValue
clipRect.bottom = root.measuredHeight + it.animatedValue
root.clipBounds = clipRect
}1
.start()
}2
Expand
val oldHeight = textView.measuredHeight
showFullText() // <-- request layout
textView.viewTreeObserver.addOnPreDrawListener {
val sizeChange = textView.measuredHeight - oldHeight
footer.translationY -= sizeChange
val clipRect = Rect()
clipRect.right = root.measuredWidth
clipRect.bottom = root.measuredHeight - sizeChange
root.clipBounds = clipRect
ObjectAnimator
.ofFloat(-sizeChange, 0f)
.addUpdateListener {
footer.translationY = it.animatedValue
clipRect.bottom = root.measuredHeight + it.animatedValue
root.clipBounds = clipRect
}1
.start()
}2
Expand
Expand/Collapse animation on Android
Expand/Collapse animation on Android
val sizeChange = textView.measuredHeight - collapsedHeight
Collapse
val sizeChange = textView.measuredHeight - collapsedHeight
val clipRect = Rect(0, 0, root.measuredWidth, root.measuredHeight)
Collapse
val sizeChange = textView.measuredHeight - collapsedHeight
val clipRect = Rect(0, 0, root.measuredWidth, root.measuredHeight)
ObjectAnimator.ofInt(0, sizeChange).apply {
}3
Collapse
val sizeChange = textView.measuredHeight - collapsedHeight
val clipRect = Rect(0, 0, root.measuredWidth, root.measuredHeight)
ObjectAnimator.ofInt(0, sizeChange).apply {
duration = 1000
interpolator = FastOutSlowInInterpolator()
}3
Collapse
val sizeChange = textView.measuredHeight - collapsedHeight
val clipRect = Rect(0, 0, root.measuredWidth, root.measuredHeight)
ObjectAnimator.ofInt(0, sizeChange).apply {
duration = 1000
interpolator = FastOutSlowInInterpolator()
addUpdateListener {
clipRect.bottom = root.measuredHeight - animatedValue
footer.translationY -= animatedValue
}1
}3
Collapse
val sizeChange = textView.measuredHeight - collapsedHeight
val clipRect = Rect(0, 0, root.measuredWidth, root.measuredHeight)
ObjectAnimator.ofInt(0, sizeChange).apply {
duration = 1000
interpolator = FastOutSlowInInterpolator()
addUpdateListener {
clipRect.bottom = root.measuredHeight - animatedValue
footer.translationY -= animatedValue
}1
}3
Collapse
val sizeChange = textView.measuredHeight - collapsedHeight
val clipRect = Rect(0, 0, root.measuredWidth, root.measuredHeight)
ObjectAnimator.ofInt(0, sizeChange).apply {
duration = 1000
interpolator = FastOutSlowInInterpolator()
addUpdateListener {
clipRect.bottom = root.measuredHeight - animatedValue
footer.translationY -= animatedValue
}1
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
showShortText() // <-- request layout
}2
})
}3
Collapse
val sizeChange = textView.measuredHeight - collapsedHeight
val clipRect = Rect(0, 0, root.measuredWidth, root.measuredHeight)
ObjectAnimator.ofInt(0, sizeChange).apply {
duration = 1000
interpolator = FastOutSlowInInterpolator()
addUpdateListener {
clipRect.bottom = root.measuredHeight - animatedValue
footer.translationY -= animatedValue
}1
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
showShortText() // <-- request layout
}2
})
}3
Collapse
Expand/Collapse animation on Android
Expand/Collapse animation on Android
Things to watch out for
Things to watch out for
• background drawables
Things to watch out for
• background drawables
• animate drawable bounds
Things to watch out for
• background drawables
• animate drawable bounds
• animating complex content
Things to watch out for
• background drawables
• animate drawable bounds
• animating complex content
• animating within scrollable container
Things to watch out for
• background drawables
• animate drawable bounds
• animating complex content
• animating within scrollable container
Things to watch out for
• background drawables
• animate drawable bounds
• animating complex content
• animating within scrollable container
Things to watch out for
Animating LayoutParams is
*not* bad
Animating LayoutParams is *not* bad
• know exactly which parts of layout are affected by your animation
Animating LayoutParams is *not* bad
• know exactly which parts of layout are affected by your animation
• measure performance with "Debug GPU Overdraw"
Animating LayoutParams is *not* bad
• know exactly which parts of layout are affected by your animation
• measure performance with "Debug GPU Overdraw"
• there is more than 1 way to achieve what you need
Animating LayoutParams is *not* bad
Thank you!
Pasha Dudka
@paveldudka
www.trickyandroid.com

More Related Content

PDF
Gabriel Font (2021) Simulation of a TaxiBot Operation for First Person Training
PPTX
FAKE NEWS DETECTION PPT
PPTX
House price prediction
PPTX
Credit card fraud detection using machine learning Algorithms
PPTX
Virtual Reality and Augmented Reality in Education
PPTX
Virtual Reality (VR) & Augmented Reality (AR): Are You Ready?
PPTX
FAKE NEWS DETECTION (1).pptx
PPTX
project final ppt.pptx
Gabriel Font (2021) Simulation of a TaxiBot Operation for First Person Training
FAKE NEWS DETECTION PPT
House price prediction
Credit card fraud detection using machine learning Algorithms
Virtual Reality and Augmented Reality in Education
Virtual Reality (VR) & Augmented Reality (AR): Are You Ready?
FAKE NEWS DETECTION (1).pptx
project final ppt.pptx

What's hot (20)

PPTX
Predicting house price
DOCX
Unit 1 - SNA QUESTION BANK
PPTX
final presentation fake news detection.pptx
PPTX
Unit 1 Introducation
PPTX
IPL Scam
PDF
Object detection and Instance Segmentation
DOCX
Synopsis tic tac toe
PPTX
Housing price prediction
PDF
Credit Card Fraud Detection Tutorial
PDF
Supervised vs Unsupervised vs Reinforcement Learning | Edureka
PPTX
Deep learning presentation
PPTX
A Web-based Service for Image Tampering Detection
PPTX
CAR PRICE PREDICTION.pptx
PPTX
Predicting house prices_Regression
PPTX
Random Forest Algorithm - Random Forest Explained | Random Forest In Machine ...
DOCX
Project Report Format for Final Year Engineering Students
PPT
Authentication Protocols
PDF
House Price Prediction Using Machine Learning
PPT
Hidden markov model ppt
PPTX
Eucalyptus, Nimbus & OpenNebula
Predicting house price
Unit 1 - SNA QUESTION BANK
final presentation fake news detection.pptx
Unit 1 Introducation
IPL Scam
Object detection and Instance Segmentation
Synopsis tic tac toe
Housing price prediction
Credit Card Fraud Detection Tutorial
Supervised vs Unsupervised vs Reinforcement Learning | Edureka
Deep learning presentation
A Web-based Service for Image Tampering Detection
CAR PRICE PREDICTION.pptx
Predicting house prices_Regression
Random Forest Algorithm - Random Forest Explained | Random Forest In Machine ...
Project Report Format for Final Year Engineering Students
Authentication Protocols
House Price Prediction Using Machine Learning
Hidden markov model ppt
Eucalyptus, Nimbus & OpenNebula
Ad

Similar to Expand/Collapse animation on Android (20)

PDF
PDF
Functional programming techniques in real-world microservices
PDF
WPF L02-Graphics, Binding and Animation
PDF
Miracle of std lib
PDF
Create a java project that - Draw a circle with three random init.pdf
PDF
Managing parallelism using coroutines
PDF
Swift 함수 커링 사용하기
PDF
SVGo workshop
PDF
Fabric.js @ Falsy Values
PDF
Creating custom views
PDF
Макс Грибов — Использование SpriteKit в неигровых приложениях
PDF
The Ring programming language version 1.3 book - Part 46 of 88
PDF
The Ring programming language version 1.5.3 book - Part 70 of 184
PPTX
Kotlin Mullets
PDF
Scientific Computing with Python Webinar: Traits
PDF
Atomically { Delete Your Actors }
PDF
Fun with flutter animations - Divyanshu Bhargava, GoHighLevel
PPTX
Animations avec Compose : rendez vos apps chat-oyantes
PDF
The Ring programming language version 1.7 book - Part 57 of 196
PDF
Александр Зимин – Анимация как средство самовыражения
Functional programming techniques in real-world microservices
WPF L02-Graphics, Binding and Animation
Miracle of std lib
Create a java project that - Draw a circle with three random init.pdf
Managing parallelism using coroutines
Swift 함수 커링 사용하기
SVGo workshop
Fabric.js @ Falsy Values
Creating custom views
Макс Грибов — Использование SpriteKit в неигровых приложениях
The Ring programming language version 1.3 book - Part 46 of 88
The Ring programming language version 1.5.3 book - Part 70 of 184
Kotlin Mullets
Scientific Computing with Python Webinar: Traits
Atomically { Delete Your Actors }
Fun with flutter animations - Divyanshu Bhargava, GoHighLevel
Animations avec Compose : rendez vos apps chat-oyantes
The Ring programming language version 1.7 book - Part 57 of 196
Александр Зимин – Анимация как средство самовыражения
Ad

Recently uploaded (20)

PDF
_Nature and dynamics of communities and community development .pdf
PPTX
INDIGENOUS-LANGUAGES-AND-LITERATURE.pptx
PDF
Yusen Logistics Group Sustainability Report 2024.pdf
PDF
5_tips_to_become_a_Presentation_Jedi_@itseugenec.pdf
PPTX
PurpoaiveCommunication for students 02.pptx
PDF
Module 7 guard mounting of security pers
PDF
Presentation on cloud computing and ppt..
PPTX
Bob Difficult Questions 08 17 2025.pptx
PPTX
Kompem Part Untuk MK Komunikasi Pembangunan 5.pptx
PDF
MODULE 3 BASIC SECURITY DUTIES AND ROLES.pdf
PPTX
power point presentation ofDracena species.pptx
PPTX
3RD-Q 2022_EMPLOYEE RELATION - Copy.pptx
PPTX
Lesson-7-Gas. -Exchange_074636.pptx
PDF
public speaking for kids in India - LearnifyU
DOCX
CLASS XII bbbbbnjhcvfyfhfyfyhPROJECT.docx
PDF
6.-propertise of noble gases, uses and isolation in noble gases
PPTX
HOW TO HANDLE THE STAGE FOR ACADEMIA AND OTHERS.pptx
PPTX
CAPE CARIBBEAN STUDIES- Integration-1.pptx
DOCX
Action plan to easily understanding okey
PPTX
ANICK 6 BIRTHDAY....................................................
_Nature and dynamics of communities and community development .pdf
INDIGENOUS-LANGUAGES-AND-LITERATURE.pptx
Yusen Logistics Group Sustainability Report 2024.pdf
5_tips_to_become_a_Presentation_Jedi_@itseugenec.pdf
PurpoaiveCommunication for students 02.pptx
Module 7 guard mounting of security pers
Presentation on cloud computing and ppt..
Bob Difficult Questions 08 17 2025.pptx
Kompem Part Untuk MK Komunikasi Pembangunan 5.pptx
MODULE 3 BASIC SECURITY DUTIES AND ROLES.pdf
power point presentation ofDracena species.pptx
3RD-Q 2022_EMPLOYEE RELATION - Copy.pptx
Lesson-7-Gas. -Exchange_074636.pptx
public speaking for kids in India - LearnifyU
CLASS XII bbbbbnjhcvfyfhfyfyhPROJECT.docx
6.-propertise of noble gases, uses and isolation in noble gases
HOW TO HANDLE THE STAGE FOR ACADEMIA AND OTHERS.pptx
CAPE CARIBBEAN STUDIES- Integration-1.pptx
Action plan to easily understanding okey
ANICK 6 BIRTHDAY....................................................

Expand/Collapse animation on Android

  • 5. val animator = ObjectAnimator.ofInt(0, 100) Animating values
  • 6. val animator = ObjectAnimator.ofInt(0, 100) animator.duration = 1000 Animating values
  • 7. val animator = ObjectAnimator.ofInt(0, 100) animator.duration = 1000 animator.interpolator = FastOutSlowInInterpolator() Animating values
  • 8. val animator = ObjectAnimator.ofInt(0, 100) animator.duration = 1000 animator.interpolator = FastOutSlowInInterpolator() animator.addUpdateListener { Log.d("pasha", "${it.animatedValue}") }1 Animating values
  • 9. val animator = ObjectAnimator.ofInt(0, 100) animator.duration = 1000 animator.interpolator = FastOutSlowInInterpolator() animator.addUpdateListener { Log.d("pasha", "${it.animatedValue}") }1 animator.start() Animating values
  • 10. val animator = ObjectAnimator.ofInt(0, 100) animator.apply { duration = 1000 interpolator = FastOutSlowInInterpolator() addUpdateListener { Log.d("pasha", "${it.animatedValue}") }1 start() }2 Animating values
  • 14. val animator = ObjectAnimator.ofFloat(1f, 1.5f) animator.apply { duration = 1000 interpolator = OvershootInterpolator() addUpdateListener { view.scaleX = it.animatedValue view.scaleY = it.animatedValue }1 start() }2 Animating view properties
  • 15. val animator = ObjectAnimator.ofFloat(1f, 1.5f) animator.apply { duration = 1000 interpolator = OvershootInterpolator() addUpdateListener { view.scaleX = it.animatedValue view.scaleY = it.animatedValue }1 start() }2 Animating view properties
  • 16. val animator = ObjectAnimator.ofFloat(1f, 1.5f) animator.apply { duration = 1000 interpolator = OvershootInterpolator() addUpdateListener { view.scaleX = it.animatedValue view.scaleY = it.animatedValue }1 start() }2 Animating view properties
  • 17. val animator = ObjectAnimator.ofFloat(1f, 1.5f) animator.apply { duration = 1000 interpolator = OvershootInterpolator() addUpdateListener { view.scaleX = it.animatedValue view.scaleY = it.animatedValue }1 start() }2 Animating view properties
  • 24. Changing view properties does not trigger measure/layout pass
  • 29. private fun expand(view: View) { }3 Animate LayoutParams!
  • 30. private fun expand(view: View) { val oldHeight = view.measuredHeight val newHeight = oldHeight + 300 }3 Animate LayoutParams!
  • 31. private fun expand(view: View) { val oldHeight = view.measuredHeight val newHeight = oldHeight + 300 ObjectAnimator.ofInt(oldHeight, newHeight).apply { }2 }3 Animate LayoutParams!
  • 32. private fun expand(view: View) { val oldHeight = view.measuredHeight val newHeight = oldHeight + 300 ObjectAnimator.ofInt(oldHeight, newHeight).apply { duration = 1000 interpolator = FastOutSlowInInterpolator() }2 }3 Animate LayoutParams!
  • 33. private fun expand(view: View) { val oldHeight = view.measuredHeight val newHeight = oldHeight + 300 ObjectAnimator.ofInt(oldHeight, newHeight).apply { duration = 1000 interpolator = FastOutSlowInInterpolator() addUpdateListener { view.layoutParams.height = it.animatedValue }1 start() }2 }3 Animate LayoutParams!
  • 34. private fun expand(view: View) { val oldHeight = view.measuredHeight val newHeight = oldHeight + 300 ObjectAnimator.ofInt(oldHeight, newHeight).apply { duration = 1000 interpolator = FastOutSlowInInterpolator() addUpdateListener { view.layoutParams.height = it.animatedValue view.requestLayout() }1 start() }2 }3 Animate LayoutParams!
  • 35. private fun expand(view: View) { val oldHeight = view.measuredHeight val newHeight = oldHeight + 300 ObjectAnimator.ofInt(oldHeight, newHeight).apply { duration = 1000 interpolator = FastOutSlowInInterpolator() addUpdateListener { view.layoutParams.height = it.animatedValue view.requestLayout() }1 start() }2 }3 Animate LayoutParams!
  • 45. val oldHeight = textView.measuredHeight Expand
  • 46. val oldHeight = textView.measuredHeight showFullText() // <-- request layout Expand
  • 47. val oldHeight = textView.measuredHeight showFullText() // <-- request layout Expand
  • 48. val oldHeight = textView.measuredHeight showFullText() // <-- request layout textView.viewTreeObserver.addOnPreDrawListener { }2 Expand
  • 49. val oldHeight = textView.measuredHeight showFullText() // <-- request layout textView.viewTreeObserver.addOnPreDrawListener { val sizeChange = textView.measuredHeight - oldHeight }2 Expand
  • 50. val oldHeight = textView.measuredHeight showFullText() // <-- request layout textView.viewTreeObserver.addOnPreDrawListener { val sizeChange = textView.measuredHeight - oldHeight }2 Expand
  • 51. val oldHeight = textView.measuredHeight showFullText() // <-- request layout textView.viewTreeObserver.addOnPreDrawListener { val sizeChange = textView.measuredHeight - oldHeight footer.translationY -= sizeChange }2 Expand
  • 52. val oldHeight = textView.measuredHeight showFullText() // <-- request layout textView.viewTreeObserver.addOnPreDrawListener { val sizeChange = textView.measuredHeight - oldHeight footer.translationY -= sizeChange }2 Expand
  • 53. val oldHeight = textView.measuredHeight showFullText() // <-- request layout textView.viewTreeObserver.addOnPreDrawListener { val sizeChange = textView.measuredHeight - oldHeight footer.translationY -= sizeChange val clipRect = Rect() clipRect.right = root.measuredWidth clipRect.bottom = root.measuredHeight - sizeChange root.clipBounds = clipRect }2 Expand
  • 54. val oldHeight = textView.measuredHeight showFullText() // <-- request layout textView.viewTreeObserver.addOnPreDrawListener { val sizeChange = textView.measuredHeight - oldHeight footer.translationY -= sizeChange val clipRect = Rect() clipRect.right = root.measuredWidth clipRect.bottom = root.measuredHeight - sizeChange root.clipBounds = clipRect }2 Expand
  • 55. val oldHeight = textView.measuredHeight showFullText() // <-- request layout textView.viewTreeObserver.addOnPreDrawListener { val sizeChange = textView.measuredHeight - oldHeight footer.translationY -= sizeChange val clipRect = Rect() clipRect.right = root.measuredWidth clipRect.bottom = root.measuredHeight - sizeChange root.clipBounds = clipRect ObjectAnimator .ofFloat(-sizeChange, 0f) .addUpdateListener { footer.translationY = it.animatedValue clipRect.bottom = root.measuredHeight + it.animatedValue root.clipBounds = clipRect }1 .start() }2 Expand
  • 56. val oldHeight = textView.measuredHeight showFullText() // <-- request layout textView.viewTreeObserver.addOnPreDrawListener { val sizeChange = textView.measuredHeight - oldHeight footer.translationY -= sizeChange val clipRect = Rect() clipRect.right = root.measuredWidth clipRect.bottom = root.measuredHeight - sizeChange root.clipBounds = clipRect ObjectAnimator .ofFloat(-sizeChange, 0f) .addUpdateListener { footer.translationY = it.animatedValue clipRect.bottom = root.measuredHeight + it.animatedValue root.clipBounds = clipRect }1 .start() }2 Expand
  • 57. val oldHeight = textView.measuredHeight showFullText() // <-- request layout textView.viewTreeObserver.addOnPreDrawListener { val sizeChange = textView.measuredHeight - oldHeight footer.translationY -= sizeChange val clipRect = Rect() clipRect.right = root.measuredWidth clipRect.bottom = root.measuredHeight - sizeChange root.clipBounds = clipRect ObjectAnimator .ofFloat(-sizeChange, 0f) .addUpdateListener { footer.translationY = it.animatedValue clipRect.bottom = root.measuredHeight + it.animatedValue root.clipBounds = clipRect }1 .start() }2 Expand
  • 60. val sizeChange = textView.measuredHeight - collapsedHeight Collapse
  • 61. val sizeChange = textView.measuredHeight - collapsedHeight val clipRect = Rect(0, 0, root.measuredWidth, root.measuredHeight) Collapse
  • 62. val sizeChange = textView.measuredHeight - collapsedHeight val clipRect = Rect(0, 0, root.measuredWidth, root.measuredHeight) ObjectAnimator.ofInt(0, sizeChange).apply { }3 Collapse
  • 63. val sizeChange = textView.measuredHeight - collapsedHeight val clipRect = Rect(0, 0, root.measuredWidth, root.measuredHeight) ObjectAnimator.ofInt(0, sizeChange).apply { duration = 1000 interpolator = FastOutSlowInInterpolator() }3 Collapse
  • 64. val sizeChange = textView.measuredHeight - collapsedHeight val clipRect = Rect(0, 0, root.measuredWidth, root.measuredHeight) ObjectAnimator.ofInt(0, sizeChange).apply { duration = 1000 interpolator = FastOutSlowInInterpolator() addUpdateListener { clipRect.bottom = root.measuredHeight - animatedValue footer.translationY -= animatedValue }1 }3 Collapse
  • 65. val sizeChange = textView.measuredHeight - collapsedHeight val clipRect = Rect(0, 0, root.measuredWidth, root.measuredHeight) ObjectAnimator.ofInt(0, sizeChange).apply { duration = 1000 interpolator = FastOutSlowInInterpolator() addUpdateListener { clipRect.bottom = root.measuredHeight - animatedValue footer.translationY -= animatedValue }1 }3 Collapse
  • 66. val sizeChange = textView.measuredHeight - collapsedHeight val clipRect = Rect(0, 0, root.measuredWidth, root.measuredHeight) ObjectAnimator.ofInt(0, sizeChange).apply { duration = 1000 interpolator = FastOutSlowInInterpolator() addUpdateListener { clipRect.bottom = root.measuredHeight - animatedValue footer.translationY -= animatedValue }1 addListener(object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator?) { showShortText() // <-- request layout }2 }) }3 Collapse
  • 67. val sizeChange = textView.measuredHeight - collapsedHeight val clipRect = Rect(0, 0, root.measuredWidth, root.measuredHeight) ObjectAnimator.ofInt(0, sizeChange).apply { duration = 1000 interpolator = FastOutSlowInInterpolator() addUpdateListener { clipRect.bottom = root.measuredHeight - animatedValue footer.translationY -= animatedValue }1 addListener(object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator?) { showShortText() // <-- request layout }2 }) }3 Collapse
  • 70. Things to watch out for
  • 71. Things to watch out for
  • 73. • background drawables • animate drawable bounds Things to watch out for
  • 74. • background drawables • animate drawable bounds • animating complex content Things to watch out for
  • 75. • background drawables • animate drawable bounds • animating complex content • animating within scrollable container Things to watch out for
  • 76. • background drawables • animate drawable bounds • animating complex content • animating within scrollable container Things to watch out for
  • 77. • background drawables • animate drawable bounds • animating complex content • animating within scrollable container Things to watch out for
  • 80. • know exactly which parts of layout are affected by your animation Animating LayoutParams is *not* bad
  • 81. • know exactly which parts of layout are affected by your animation • measure performance with "Debug GPU Overdraw" Animating LayoutParams is *not* bad
  • 82. • know exactly which parts of layout are affected by your animation • measure performance with "Debug GPU Overdraw" • there is more than 1 way to achieve what you need Animating LayoutParams is *not* bad