'''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 synchronised 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()
