Elevation

Elevation is the relative distance between two surfaces along the z-axis.

https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/elevation-previous.png

There are 5 classes in KivyMD that can simulate shadow:

By default, KivyMD widgets use the elevation behavior implemented in classes FakeRectangularElevationBehavior and FakeCircularElevationBehavior for cast shadows. These classes use the old method of rendering shadows and it doesn’t look very aesthetically pleasing. Shadows are harsh, no softness:

The RectangularElevationBehavior, CircularElevationBehavior, RoundedRectangularElevationBehavior classes use the new shadow rendering algorithm, based on textures creation using the Pillow library. It looks very aesthetically pleasing and beautiful.

Warning

Remember that RectangularElevationBehavior, CircularElevationBehavior, RoundedRectangularElevationBehavior classes require a lot of resources from the device on which your application will run, so you should not use these classes on mobile devices.

from kivy.lang import Builder
from kivy.uix.widget import Widget

from kivymd.app import MDApp
from kivymd.uix.card import MDCard
from kivymd.uix.behaviors import RectangularElevationBehavior
from kivymd.uix.boxlayout import MDBoxLayout

KV = '''
<Box@MDBoxLayout>
    adaptive_size: True
    orientation: "vertical"
    spacing: "36dp"


<BaseShadowWidget>
    size_hint: None, None
    size: 100, 100
    md_bg_color: 0, 0, 1, 1
    elevation: 36
    pos_hint: {'center_x': .5}


MDFloatLayout:

    MDBoxLayout:
        adaptive_size: True
        pos_hint: {'center_x': .5, 'center_y': .5}
        spacing: "56dp"

        Box:

            MDLabel:
                text: "Deprecated shadow rendering"
                adaptive_size: True

            DeprecatedShadowWidget:

            MDLabel:
                text: "Doesn't require a lot of resources"
                adaptive_size: True

        Box:

            MDLabel:
                text: "New shadow rendering"
                adaptive_size: True

            NewShadowWidget:

            MDLabel:
                text: "It takes a lot of resources"
                adaptive_size: True
'''


class BaseShadowWidget(Widget):
    pass


class DeprecatedShadowWidget(MDCard, BaseShadowWidget):
    '''Deprecated shadow rendering. Doesn't require a lot of resources.'''


class NewShadowWidget(RectangularElevationBehavior, BaseShadowWidget, MDBoxLayout):
    '''New shadow rendering. It takes a lot of resources.'''


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


Example().run()
https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/elevation-differential.png

For example, let’s create an button with a rectangular elevation effect:

from kivy.lang import Builder
from kivy.uix.behaviors import ButtonBehavior

from kivymd.app import MDApp
from kivymd.uix.behaviors import (
    RectangularRippleBehavior,
    BackgroundColorBehavior,
    FakeRectangularElevationBehavior,
)

KV = '''
<RectangularElevationButton>:
    size_hint: None, None
    size: "250dp", "50dp"


MDScreen:

    # With elevation effect
    RectangularElevationButton:
        pos_hint: {"center_x": .5, "center_y": .6}
        elevation: 18

    # Without elevation effect
    RectangularElevationButton:
        pos_hint: {"center_x": .5, "center_y": .4}
'''


class RectangularElevationButton(
    RectangularRippleBehavior,
    FakeRectangularElevationBehavior,
    ButtonBehavior,
    BackgroundColorBehavior,
):
    md_bg_color = [0, 0, 1, 1]


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


Example().run()
https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/rectangular-elevation-effect.gif

Similarly, create a circular button:

from kivy.lang import Builder
from kivy.uix.behaviors import ButtonBehavior

from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.app import MDApp
from kivymd.uix.behaviors import (
    CircularRippleBehavior,
    FakeCircularElevationBehavior,
)

KV = '''
<CircularElevationButton>:
    size_hint: None, None
    size: "100dp", "100dp"
    radius: self.size[0] / 2
    md_bg_color: 0, 0, 1, 1

    MDIcon:
        icon: "hand-heart"
        halign: "center"
        valign: "center"
        size: root.size
        pos: root.pos
        font_size: root.size[0] * .6
        theme_text_color: "Custom"
        text_color: [1] * 4


MDScreen:

    CircularElevationButton:
        pos_hint: {"center_x": .5, "center_y": .6}
        elevation: 24
'''


class CircularElevationButton(
    FakeCircularElevationBehavior,
    CircularRippleBehavior,
    ButtonBehavior,
    MDBoxLayout,
):
    pass



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


Example().run()
https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/circular-fake-elevation.png

Animating the elevation

from kivy.animation import Animation
from kivy.lang import Builder
from kivy.properties import ObjectProperty
from kivy.uix.behaviors import ButtonBehavior

from kivymd.app import MDApp
from kivymd.theming import ThemableBehavior
from kivymd.uix.behaviors import FakeRectangularElevationBehavior, RectangularRippleBehavior
from kivymd.uix.boxlayout import MDBoxLayout

KV = '''
MDFloatLayout:

    ElevatedWidget:
        pos_hint: {'center_x': .5, 'center_y': .5}
        size_hint: None, None
        size: 100, 100
        md_bg_color: 0, 0, 1, 1
'''


class ElevatedWidget(
    ThemableBehavior,
    FakeRectangularElevationBehavior,
    RectangularRippleBehavior,
    ButtonBehavior,
    MDBoxLayout,
):
    shadow_animation = ObjectProperty()

    def on_press(self, *args):
        if self.shadow_animation:
            Animation.cancel_all(self, "_elevation")
        self.shadow_animation = Animation(_elevation=self.elevation + 10, d=0.4)
        self.shadow_animation.start(self)

    def on_release(self, *args):
        if self.shadow_animation:
            Animation.cancel_all(self, "_elevation")
        self.shadow_animation = Animation(_elevation=self.elevation, d=0.1)
        self.shadow_animation.start(self)


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


Example().run()
https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/rectangular-elevation-animation-effect.gif

Lighting position

from kivy.lang import Builder

from kivymd.app import MDApp
from kivymd.uix.card import MDCard
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.behaviors import RectangularElevationBehavior

KV = '''
MDScreen:

    ShadowCard:
        pos_hint: {'center_x': .5, 'center_y': .5}
        size_hint: None, None
        size: 100, 100
        shadow_pos: -10 + slider.value, -10 + slider.value
        elevation: 24
        md_bg_color: 1, 1, 1, 1

    MDSlider:
        id: slider
        max: 20
        size_hint_x: .6
        pos_hint: {'center_x': .5, 'center_y': .3}
'''


class ShadowCard(RectangularElevationBehavior, MDBoxLayout):
    pass


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


Example().run()
https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/shadow-pos.gif

API - kivymd.uix.behaviors.elevation

class kivymd.uix.behaviors.elevation.CommonElevationBehavior(**kwargs)

Common base class for rectangular and circular elevation behavior.

elevation

Elevation of the widget.

Note

Although, this value does not represent the current elevation of the widget. _elevation can be used to animate the current elevation and come back using the elevation property directly.

For example:

from kivy.lang import Builder
from kivy.uix.behaviors import ButtonBehavior

from kivymd.app import MDApp
from kivymd.uix.behaviors import CircularElevationBehavior, CircularRippleBehavior
from kivymd.uix.boxlayout import MDBoxLayout

KV = '''
#:import Animation kivy.animation.Animation


<WidgetWithShadow>
    size_hint: [None, None]
    elevation: 6
    animation_: None
    md_bg_color: [1] * 4
    on_size:
        self.radius = [self.height / 2] * 4
    on_press:
        if self.animation_:                     self.animation_.cancel(self);                     self.animation_ = Animation(_elevation=self.elevation + 6, d=0.08);                     self.animation_.start(self)
    on_release:
        if self.animation_:                     self.animation_.cancel(self);                     self.animation_ = Animation(_elevation = self.elevation, d=0.08);                     self.animation_.start(self)

MDFloatLayout:

    WidgetWithShadow:
        size: [root.size[1] / 2] * 2
        pos_hint: {"center": [0.5, 0.5]}
'''


class WidgetWithShadow(
    CircularElevationBehavior,
    CircularRippleBehavior,
    ButtonBehavior,
    MDBoxLayout,
):
    def __init__(self, **kwargs):
        # always set the elevation before the super().__init__ call
        # self.elevation = 6
        super().__init__(**kwargs)

    def on_size(self, *args):
        self.radius = [self.size[0] / 2]


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


Example().run()
angle

Angle of rotation in degrees of the current shadow. This value is shared across different widgets.

Note

This value will affect both, hard and soft shadows. Each shadow has his own origin point that’s computed every time the elevation changes.

Warning

Do not add PushMatrix inside the canvas before and add PopMatrix in the next layer, this will cause visual errors, because the stack used will clip the push and pop matrix already inside the canvas.before canvas layer.

Incorrect:

<TiltedWidget>
    canvas.before:
        PushMatrix
        [...]
    canvas:
        PopMatrix

Correct:

<TiltedWidget>
    canvas.before:
        PushMatrix
        [...]
        PopMatrix

angle is an NumericProperty and defaults to 0.

radius

Radius of the corners of the shadow. This values represents each corner of the shadow, starting from top-left corner and going clockwise.

radius = [
    "top-left",
    "top-right",
    "bottom-right",
    "bottom-left",
]

This value can be expanded thus allowing this settings to be valid:

widget.radius=[0]  # Translates to [0, 0, 0, 0]
widget.radius=[10, 3]  # Translates to [10, 3, 10, 3]
widget.radius=[7.0, 8.7, 1.5, 3.0]  # Translates to [7, 8, 1, 3]

Note

This value will affect both, hard and soft shadows. This value only affects RoundedRectangularElevationBehavior for now, but can be stored and used by custom shadow draw functions.

radius is an VariableListProperty and defaults to [0, 0, 0, 0].

shadow_pos

Custom shadow origin point. If this property is set, _shadow_pos will be ommited.

This property allows users to fake light source.

shadow_pos is an ListProperty and defaults to [0, 0].

Note

this value overwrite the _shadow_pos processing.

shadow_group

Widget’s shadow group. By default every widget with a shadow is saved inside the memory __shadow_groups as a weakref. This means that you can have multiple light sources, one for every shadow group.

To fake a light source use force_shadow_pos.

shadow_group is an StringProperty and defaults to “global”.

soft_shadow_size

Size of the soft shadow texture over the canvas.

soft_shadow_size is an ListProperty and defaults to [0, 0].

Note

This property is automatically processed.

soft_shadow_pos

Position of the hard shadow texture over the canvas.

soft_shadow_pos is an ListProperty and defaults to [0, 0].

Note

This property is automatically processed.

soft_shadow_cl

Color of the soft shadow.

soft_shadow_cl is an ListProperty and defaults to [0, 0, 0, 0.15].

hard_shadow_texture

Texture of the hard shadow texture for the canvas.

hard_shadow_texture is an Image and defaults to None.

Note

This property is automatically processed when elevation is changed.

hard_shadow_size

Size of the hard shadow texture over the canvas.

hard_shadow_size is an ListProperty and defaults to [0, 0].

Note

This property is automatically processed when elevation is changed.

hard_shadow_pos

Position of the hard shadow texture over the canvas.

hard_shadow_pos is an ListProperty and defaults to [0, 0].

Note

This property is automatically processed when elevation is changed.

hard_shadow_cl

Color of the hard shadow.

Note

hard_shadow_cl is an ListProperty and defaults to [0, 0, 0, 0.15].

hard_shadow_offset

This value sets a special offset to the shadow canvas, this offset allows a correct draw of the canvas size. allowing the effect to correctly blur the image in the given space.

hard_shadow_offset is an BoundedNumericProperty and defaults to 2.

soft_shadow_offset

This value sets a special offset to the shadow canvas, this offset allows a correct draw of the canvas size. allowing the effect to correctly blur the image in the given space.

soft_shadow_offset is an BoundedNumericProperty and defaults to 4.

draw_shadow

This property controls the draw call of the context.

This property is automatically set to __draw_shadow__ inside the super().__init__ call. unless the property is different of None.

To set a different drawing instruction function, set this property before the super(),__init__ call inside the __init__ definition of the new class.

You can use the source for this classes as example of how to draw over with the context:

Real time shadows:
  1. RectangularElevationBehavior

  2. CircularElevationBehavior

  3. RoundedRectangularElevationBehavior

  4. ObservableShadow

Fake shadows (d`ont use this property):
  1. FakeRectangularElevationBehavior

  2. FakeCircularElevationBehavior

draw_shadow is an ObjectProperty and defaults to None.

Note

If this property is left to None the CommonElevationBehavior will set to a function that will raise a NotImplementedError inside super().__init__.

Follow the next example to set a new draw instruction for the class inside __init__:

class RoundedRectangularElevationBehavior(CommonElevationBehavior):
    '''
    Shadow class for the RoundedRectangular shadow behavior.
    Controls the size and position of the shadow.
    '''

    def __init__(self, **kwargs):
        self._draw_shadow = WeakMethod(self.__draw_shadow__)
        super().__init__(**kwargs)

    def __draw_shadow__(self, origin, end, context=None):
        context.draw(...)

Context is a Pillow ImageDraw class. For more information check the [Pillow official documentation](https://github.com/python-pillow/Pillow/).

on_shadow_group(self, instance, value)

This function controls the shadow group of the widget. Do not use Directly to change the group. instead, use the shadow_group property.

force_shadow_pos(self, shadow_pos)

This property forces the shadow position in every widget inside the widget. The argument shadow_pos is expected as a <class ‘list’> or <class ‘tuple’>.

update_group_property(self, property_name, value)

This functions allows to change properties of every widget inside the shadow group.

shadow_preset(self, *args)

This function is meant to set the default configuration of the elevation.

After a new instance is created, the elevation property will be launched and thus this function will update the elevation if the KV lang have not done it already.

Works similar to an __after_init__ call inside a widget.

on_elevation(self, instance, value)

Elevation event that sets the current elevation value to _elevation.

on_disabled(self, instance, value)

This function hides the shadow when the widget is disabled. It sets the shadow to 0.

on__shadow_pos(self, ins, val)

Updates the shadow with the computed value.

Call this function every time you need to force a shadow update.

on_shadow_pos(self, ins, val)

Updates the shadow with the fixed value.

Call this function every time you need to force a shadow update.

class kivymd.uix.behaviors.elevation.RectangularElevationBehavior(**kwargs)

Base class for a rectangular elevation behavior.

class kivymd.uix.behaviors.elevation.CircularElevationBehavior(**kwargs)

Base class for a circular elevation behavior.

class kivymd.uix.behaviors.elevation.RoundedRectangularElevationBehavior(**kwargs)

Base class for rounded rectangular elevation behavior.

class kivymd.uix.behaviors.elevation.ObservableShadow(**kwargs)

ObservableShadow is real time shadow render that it’s intended to only render a partial shadow of widgets based upon on the window observable area, this is meant to improve the performance of bigger widgets.

Warning

This is an empty class, the name has been reserved for future use. if you include this clas in your object, you wil get a NotImplementedError.

class kivymd.uix.behaviors.elevation.FakeRectangularElevationBehavior(**kwargs)

FakeRectangularElevationBehavio`r is a shadow mockup for widgets. Improves performance using cached images inside `kivymd.images dir

This class cast a fake Rectangular shadow behaind the widget.

You can either use this behavior to overwrite the elevation of a prefab widget, or use it directly inside a new widget class definition.

Use this class as follows for new widgets:

class NewWidget(
    ThemableBehavior,
    FakeCircularElevationBehavior,
    SpecificBackgroundColorBehavior,
    # here you add the other front end classes for the widget front_end,
):
    [...]

With this method each class can draw it’s content in the canvas in the correct order, avoiding some visual errors.

FakeCircularElevationBehavior will load prefabricated textures to optimize loading times.

Note

About rounded corners: be careful, since this behavior is a mockup and will not draw any rounded corners.

class kivymd.uix.behaviors.elevation.FakeCircularElevationBehavior(**kwargs)

FakeCircularElevationBehavior is a shadow mockup for widgets. Improves performance using cached images inside kivymd.images dir

This class cast a fake elliptic shadow behaind the widget.

You can either use this behavior to overwrite the elevation of a prefab widget, or use it directly inside a new widget class definition.

Use this class as follows for new widgets:

class NewWidget(
    ThemableBehavior,
    FakeCircularElevationBehavior,
    SpecificBackgroundColorBehavior,
    # here you add the other front end classes for the widget front_end,
):
    [...]

With this method each class can draw it’s content in the canvas in the correct order, avoiding some visual errors.

FakeCircularElevationBehavior will load prefabricated textures to optimize loading times.

Note

About rounded corners: be careful, since this behavior is a mockup and will not draw any rounded corners. only perfect ellipses.