Elevation#
See also Elevation is the relative distance between two surfaces along the z-axis. There are 5 classes in KivyMD that can simulate shadow: By default, KivyMD widgets use the elevation behavior implemented in classes
The Warning Remember that For example, let’s create an button with a rectangular elevation effect: Similarly, create a circular button:
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: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.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()
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()
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()
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()
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()
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 theelevation
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()
elevation
is anBoundedNumericProperty
and defaults to 0.
- 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 anNumericProperty
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 anVariableListProperty
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 anListProperty
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 anStringProperty
and defaults to “global”.
- soft_shadow_size#
Size of the soft shadow texture over the canvas.
soft_shadow_size
is anListProperty
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 anListProperty
and defaults to [0, 0].Note
This property is automatically processed.
- soft_shadow_cl#
Color of the soft shadow.
soft_shadow_cl
is anListProperty
and defaults to [0, 0, 0, 0.15].
- hard_shadow_texture#
Texture of the hard shadow texture for the canvas.
hard_shadow_texture
is anImage
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 anListProperty
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 anListProperty
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 anListProperty
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 anBoundedNumericProperty
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 anBoundedNumericProperty
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:
- Fake shadows (d`ont use this property):
draw_shadow
is anObjectProperty
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.