110 lines
3.6 KiB
Python
110 lines
3.6 KiB
Python
|
'''How to use Animation with RecycleView items?
|
||
|
|
||
|
In case you really want to use the Animation class with RecycleView, you'll
|
||
|
likely encounter an issue, as widgets are moved around, they are used to
|
||
|
represent different items, so an animation on a specific item is going to
|
||
|
affect others, and this will lead to really confusing results.
|
||
|
|
||
|
This example works around that by creating a "proxy" widget for the animation,
|
||
|
and, by putting it in the data, allowing the displayed widget to mimic the
|
||
|
animation. As the item always refers to its proxy, whichever widget is used to
|
||
|
display the item will keep in sync with the animation.
|
||
|
|
||
|
'''
|
||
|
from copy import copy
|
||
|
|
||
|
from kivy.app import App
|
||
|
from kivy.clock import triggered
|
||
|
from kivy.lang import Builder
|
||
|
from kivy.uix.widget import Widget
|
||
|
from kivy.animation import Animation
|
||
|
from kivy.uix.button import Button
|
||
|
from kivy.properties import (
|
||
|
ObjectProperty, ListProperty
|
||
|
)
|
||
|
|
||
|
|
||
|
KV = '''
|
||
|
<Item>:
|
||
|
index: None
|
||
|
animation_proxy: None
|
||
|
on_release: app.animate_item(self.index)
|
||
|
|
||
|
|
||
|
RecycleView:
|
||
|
data: app.data
|
||
|
viewclass: 'Item'
|
||
|
RecycleBoxLayout:
|
||
|
orientation: 'vertical'
|
||
|
size_hint: 1, None
|
||
|
height: self.minimum_height
|
||
|
default_size_hint: 1, None
|
||
|
default_size: 0, dp(40)
|
||
|
'''
|
||
|
|
||
|
|
||
|
class Item(Button):
|
||
|
animation_proxy = ObjectProperty(allownone=True)
|
||
|
_animation_proxy = None
|
||
|
|
||
|
def update_opacity(self, proxy, opacity):
|
||
|
# sync one animated property to the value in the proxy
|
||
|
self.opacity = opacity
|
||
|
|
||
|
def on_animation_proxy(self, *args):
|
||
|
"""When we create an animation proxy for an item, we need to bind to
|
||
|
the animated property to update our own.
|
||
|
"""
|
||
|
if self._animation_proxy:
|
||
|
self._animation_proxy.unbind(opacity=self.update_opacity)
|
||
|
|
||
|
self._animation_proxy = self.animation_proxy
|
||
|
if self.animation_proxy:
|
||
|
# when we are assigned an animation_proxy, sync our properties to
|
||
|
# the animated version.
|
||
|
self.opacity = self.animation_proxy.opacity
|
||
|
self.animation_proxy.bind(opacity=self.update_opacity)
|
||
|
else:
|
||
|
# if we lose our animation proxy, we need to reset the animated
|
||
|
# property to their default values.
|
||
|
self.opacity = 1
|
||
|
|
||
|
|
||
|
class Application(App):
|
||
|
data = ListProperty()
|
||
|
|
||
|
def build(self):
|
||
|
self.data = [
|
||
|
{'index': i, 'text': 'hello {}'.format(i), 'animation_proxy': None}
|
||
|
for i in range(1000)
|
||
|
]
|
||
|
return Builder.load_string(KV)
|
||
|
|
||
|
# the triggered decorator allows delaying the animation until after the
|
||
|
# blue effect on the button is removed, to avoid a flash as widgets gets
|
||
|
# reordered when that happens
|
||
|
@triggered(timeout=0.05)
|
||
|
def animate_item(self, index):
|
||
|
# the animation we actually want to do on the item, note that any
|
||
|
# property animated here needs to be synchronized from the proxy to the
|
||
|
# animated widget (in on_animation_proxy and using methods for each
|
||
|
# animation)
|
||
|
proxy = Widget(opacity=1)
|
||
|
item = copy(self.data[index])
|
||
|
animation = (
|
||
|
Animation(opacity=0, d=.1, t='out_quad')
|
||
|
+ Animation(opacity=1, d=5, t='out_quad')
|
||
|
)
|
||
|
animation.bind(on_complete=lambda *x: self.reset_animation(item))
|
||
|
item['animation_proxy'] = proxy
|
||
|
self.data[index] = item
|
||
|
animation.start(proxy)
|
||
|
|
||
|
def reset_animation(self, item):
|
||
|
# animation is complete, widget should be garbage collected
|
||
|
item['animation_proxy'] = None
|
||
|
|
||
|
|
||
|
if __name__ == "__main__":
|
||
|
Application().run()
|