Hero#

New in version 1.0.0.

Use the MDHeroFrom widget to animate a widget from one screen to the next.

  • The hero refers to the widget that flies between screens.

  • Create a hero animation using KivyMD’s MDHeroFrom widget.

  • Fly the hero from one screen to another.

  • Animate the transformation of a hero’s shape from circular to rectangular while flying it from one screen to another.

  • The MDHeroFrom widget in KivyMD implements a style of animation commonly known as shared element transitions or shared element animations.

The widget that will move from screen A to screen B will be a hero. To move a widget from one screen to another using hero animation, you need to do the following:

  • On screen A, place the MDHeroFrom container.

  • Sets a tag (string) for the MDHeroFrom container.

  • Place a hero in the MDHeroFrom container.

  • On screen B, place the MDHeroTo container - our hero from screen **A **will fly into this container.

https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/hero-base.png

Warning

MDHeroFrom container cannot have more than one child widget.

Base example#

from kivy.lang import Builder

from kivymd.app import MDApp

KV = '''
MDScreenManager:

    MDScreen:
        name: "screen A"
        md_bg_color: "lightblue"

        MDHeroFrom:
            id: hero_from
            tag: "hero"
            size_hint: None, None
            size: "120dp", "120dp"
            pos_hint: {"top": .98}
            x: 24

            FitImage:
                source: "kivymd/images/logo/kivymd-icon-512.png"
                size_hint: None, None
                size: hero_from.size

        MDRaisedButton:
            text: "Move Hero To Screen B"
            pos_hint: {"center_x": .5}
            y: "36dp"
            on_release:
                root.current_heroes = ["hero"]
                root.current = "screen B"

    MDScreen:
        name: "screen B"
        hero_to: hero_to
        md_bg_color: "cadetblue"

        MDHeroTo:
            id: hero_to
            tag: "hero"
            size_hint: None, None
            size: "220dp", "220dp"
            pos_hint: {"center_x": .5, "center_y": .5}

        MDRaisedButton:
            text: "Move Hero To Screen A"
            pos_hint: {"center_x": .5}
            y: "36dp"
            on_release:
                root.current_heroes = ["hero"]
                root.current = "screen A"
'''


class Test(MDApp):
    def build(self):
        return Builder.load_string(KV)


Test().run()
https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/hero-usage.gif

Note that the child of the MDHeroFrom widget must have the size of the parent:

MDHeroFrom:
    id: hero_from
    tag: "hero"

    FitImage:
        size_hint: None, None
        size: hero_from.size

To enable hero animation before setting the name of the current screen for the screen manager, you must specify the name of the tag of the MDHeroFrom container in which the hero is located:

MDRaisedButton:
    text: "Move Hero To Screen B"
    on_release:
        root.current_heroes = ["hero"]
        root.current = "screen 2"

If you need to switch to a screen that does not contain heroes, set the current_hero attribute for the screen manager as “” (empty string):

MDRaisedButton:
    text: "Go To Another Screen"
    on_release:
        root.current_heroes = []
        root.current = "another screen"

Example#

from kivy.lang import Builder

from kivymd.app import MDApp

KV = '''
MDScreenManager:

    MDScreen:
        name: "screen A"
        md_bg_color: "lightblue"

        MDHeroFrom:
            id: hero_from
            tag: "hero"
            size_hint: None, None
            size: "120dp", "120dp"
            pos_hint: {"top": .98}
            x: 24

            FitImage:
                source: "kivymd/images/logo/kivymd-icon-512.png"
                size_hint: None, None
                size: hero_from.size

        MDRaisedButton:
            text: "Move Hero To Screen B"
            pos_hint: {"center_x": .5}
            y: "36dp"
            on_release:
                root.current_heroes = ["hero"]
                root.current = "screen B"

    MDScreen:
        name: "screen B"
        hero_to: hero_to
        md_bg_color: "cadetblue"

        MDHeroTo:
            id: hero_to
            tag: "hero"
            size_hint: None, None
            size: "220dp", "220dp"
            pos_hint: {"center_x": .5, "center_y": .5}

        MDRaisedButton:
            text: "Go To Screen C"
            pos_hint: {"center_x": .5}
            y: "52dp"
            on_release:
                root.current_heroes = []
                root.current = "screen C"

        MDRaisedButton:
            text: "Move Hero To Screen A"
            pos_hint: {"center_x": .5}
            y: "8dp"
            on_release:
                root.current_heroes = ["hero"]
                root.current = "screen A"

    MDScreen:
        name: "screen C"

        MDLabel:
            text: "Screen C"
            halign: "center"

        MDRaisedButton:
            text: "Back To Screen B"
            pos_hint: {"center_x": .5}
            y: "36dp"
            on_release:
                root.current = "screen B"
'''


class Test(MDApp):
    def build(self):
        return Builder.load_string(KV)


Test().run()
https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/hero-switch-another-screen.gif

Events#

Two events are available for the hero:

  • on_transform_in - when the hero flies from screen A to screen B.

  • on_transform_out - when the hero back from screen B to screen A.

The on_transform_in, on_transform_out events relate to the MDHeroFrom container. For example, let’s change the radius and background color of the hero during the flight between the screens:

from kivy import utils
from kivy.animation import Animation
from kivy.lang import Builder
from kivy.utils import get_color_from_hex

from kivymd.app import MDApp
from kivymd.uix.hero import MDHeroFrom
from kivymd.uix.relativelayout import MDRelativeLayout

KV = '''
MDScreenManager:

    MDScreen:
        name: "screen A"
        md_bg_color: "lightblue"

        MyHero:
            id: hero_from
            tag: "hero"
            size_hint: None, None
            size: "120dp", "120dp"
            pos_hint: {"top": .98}
            x: 24

            MDRelativeLayout:
                size_hint: None, None
                size: hero_from.size
                md_bg_color: "blue"
                radius: [24, 12, 24, 12]

                FitImage:
                    source: "https://github.com/kivymd/internal/raw/main/logo/kivymd_logo_blue.png"

        MDRaisedButton:
            text: "Move Hero To Screen B"
            pos_hint: {"center_x": .5}
            y: "36dp"
            on_release:
                root.current_heroes = ["hero"]
                root.current = "screen B"

    MDScreen:
        name: "screen B"
        hero_to: hero_to
        md_bg_color: "cadetblue"

        MDHeroTo:
            id: hero_to
            tag: "hero"
            size_hint: None, None
            size: "220dp", "220dp"
            pos_hint: {"center_x": .5, "center_y": .5}

        MDRaisedButton:
            text: "Move Hero To Screen A"
            pos_hint: {"center_x": .5}
            y: "36dp"
            on_release:
                root.current_heroes = ["hero"]
                root.current = "screen A"
'''


class Test(MDApp):
    def build(self):
        return Builder.load_string(KV)


class MyHero(MDHeroFrom):
    def on_transform_in(
        self, instance_hero_widget: MDRelativeLayout, duration: float
    ):
        '''
        Called when the hero flies from screen **A** to screen **B**.

        :param instance_hero_widget: dhild widget of the `MDHeroFrom` class.
        :param duration of the transition animation between screens.
        '''

        Animation(
            radius=[12, 24, 12, 24],
            duration=duration,
            md_bg_color=(0, 1, 1, 1),
        ).start(instance_hero_widget)

    def on_transform_out(
        self, instance_hero_widget: MDRelativeLayout, duration: float
    ):
        '''Called when the hero back from screen **B** to screen **A**.'''

        Animation(
            radius=[24, 12, 24, 12],
            duration=duration,
            md_bg_color=get_color_from_hex(utils.hex_colormap["blue"]),
        ).start(instance_hero_widget)


Test().run()
https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/hero-events.gif

Usage with ScrollView#

from kivy.animation import Animation
from kivy.clock import Clock
from kivy.lang import Builder
from kivy.properties import StringProperty, ObjectProperty

from kivymd.app import MDApp
from kivymd.uix.hero import MDHeroFrom

KV = '''
<HeroItem>
    size_hint_y: None
    height: "200dp"
    radius: 24

    MDSmartTile:
        id: tile
        radius: 24
        box_radius: 0, 0, 24, 24
        box_color: 0, 0, 0, .5
        source: "kivymd/images/logo/kivymd-icon-512.png"
        size_hint: None, None
        size: root.size
        mipmap: True
        on_release: root.on_release()

        MDLabel:
            text: root.tag
            bold: True
            font_style: "H6"
            opposite_colors: True


MDScreenManager:

    MDScreen:
        name: "screen A"

        ScrollView:

            MDGridLayout:
                id: box
                cols: 2
                spacing: "12dp"
                padding: "12dp"
                adaptive_height: True

    MDScreen:
        name: "screen B"
        heroes_to: [hero_to]

        MDHeroTo:
            id: hero_to
            size_hint: 1, None
            height: "220dp"
            pos_hint: {"top": 1}

        MDRaisedButton:
            text: "Move Hero To Screen A"
            pos_hint: {"center_x": .5}
            y: "36dp"
            on_release:
                root.current_heroes = [hero_to.tag]
                root.current = "screen A"
'''


class HeroItem(MDHeroFrom):
    text = StringProperty()
    manager = ObjectProperty()

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.ids.tile.ids.image.ripple_duration_in_fast = 0.05

    def on_transform_in(self, instance_hero_widget, duration):
        Animation(
            radius=[0, 0, 0, 0],
            box_radius=[0, 0, 0, 0],
            duration=duration,
        ).start(instance_hero_widget)

    def on_transform_out(self, instance_hero_widget, duration):
        Animation(
            radius=[24, 24, 24, 24],
            box_radius=[0, 0, 24, 24],
            duration=duration,
        ).start(instance_hero_widget)

    def on_release(self):
        def switch_screen(*args):
            self.manager.current_heroes = [self.tag]
            self.manager.ids.hero_to.tag = self.tag
            self.manager.current = "screen B"

        Clock.schedule_once(switch_screen, 0.2)


class Test(MDApp):
    def build(self):
        return Builder.load_string(KV)

    def on_start(self):
        for i in range(12):
            hero_item = HeroItem(
                text=f"Item {i + 1}", tag=f"Tag {i}", manager=self.root
            )
            if not i % 2:
                hero_item.md_bg_color = "lightgrey"
            self.root.ids.box.add_widget(hero_item)


Test().run()
https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/hero-usage-with-scrollview.gif

Using multiple heroes at the same time#

from kivy.lang import Builder

from kivymd.app import MDApp

KV = '''
MDScreenManager:

    MDScreen:
        name: "screen A"
        md_bg_color: "lightblue"

        MDHeroFrom:
            id: hero_kivymd
            tag: "kivymd"
            size_hint: None, None
            size: "200dp", "200dp"
            pos_hint: {"top": .98}
            x: 24

            FitImage:
                source: "kivymd/images/logo/kivymd-icon-512.png"
                size_hint: None, None
                size: hero_kivymd.size

        MDHeroFrom:
            id: hero_kivy
            tag: "kivy"
            size_hint: None, None
            size: "200dp", "200dp"
            pos_hint: {"top": .98}
            x: 324

            FitImage:
                source: "data/logo/kivy-icon-512.png"
                size_hint: None, None
                size: hero_kivy.size

        MDRaisedButton:
            text: "Move Hero To Screen B"
            pos_hint: {"center_x": .5}
            y: "36dp"
            on_release:
                root.current_heroes = ["kivymd", "kivy"]
                root.current = "screen B"

    MDScreen:
        name: "screen B"
        heroes_to: hero_to_kivymd, hero_to_kivy
        md_bg_color: "cadetblue"

        MDHeroTo:
            id: hero_to_kivy
            tag: "kivy"
            size_hint: None, None
            pos_hint: {"center_x": .5, "center_y": .5}

        MDHeroTo:
            id: hero_to_kivymd
            tag: "kivymd"
            size_hint: None, None
            pos_hint: {"right": 1, "top": 1}

        MDRaisedButton:
            text: "Move Hero To Screen A"
            pos_hint: {"center_x": .5}
            y: "36dp"
            on_release:
                root.current_heroes = ["kivy", "kivymd"]
                root.current = "screen A"
'''


class Test(MDApp):
    def build(self):
        return Builder.load_string(KV)


Test().run()
https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/hero-multiple-heroes.gif

API - kivymd.uix.hero#

class kivymd.uix.hero.MDHeroFrom(**kwargs)#

The container from which the hero begins his flight.

For more information, see in the MDBoxLayout class documentation.

Events:
on_transform_in

when the hero flies from screen A to screen B.

on_transform_out

Called when the hero back from screen B to screen A.

tag#

Tag ID for heroes.

tag is an StringProperty and defaults to ‘’.

on_transform_in(self, *args)#

Called when the hero flies from screen A to screen B.

on_transform_out(self, *args)#

Called when the hero back from screen B to screen A.

class kivymd.uix.hero.MDHeroTo(*args, **kwargs)#

The container in which the hero comes.

For more information, see in the MDBoxLayout class documentation.

tag#

Tag ID for heroes.

tag is an StringProperty and defaults to ‘’.