Changing background of a Button to a different shape and Styles like shadow effect etc in kivy python

paarth batra picture paarth batra · Apr 3, 2014 · Viewed 10.8k times · Source

I learnt that in Qt we do have some ways to make a QpushButton with :

  1. Shadow effect
  2. making it Flat by making setFlat properties to True .
  3. chaning the cursor of a different type if someone hovers over it like pushButton_18.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))

I am looking to make Buttons behavior similar in kivy as well . Is it possible ? I learnt that we can change background Image changed to a Image with rounded shape Changing the background color of a Button in Kivy

but this is not that satisfying as the button still looks very normal . There s no shadow affect . Even when we click on the corners outside the button it is treated s if button is clicked .

I have gone through documentation of Kivy Button as well as Label and tried to change the behavior and look of a button . Can someone advice on how to make a button look better We can try to have :

  1. Shadowing affect
  2. Animation affect in background
  3. Rounded edges etc .
  4. Can we put animated gif Images as background for animation (I did try that but it wasnt animated anymore )

Below is a code which i just created for exploring more into buttons in Kivy :

__author__ = 'pbatra'
#Good info about background
#https://stackoverflow.com/questions/20181250/changing-the-background-color-of-a-button-    in-kivy/20181407#20181407

from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.properties import ObjectProperty
from kivy.uix.image import Image
from kivy.graphics import Color


gui = '''
<MenuScreen>:
    canvas.before:
        BorderImage:
            # BorderImage behaves like the CSS BorderImage
            border: 10, 10, 10, 10
            texture: self.background_image.texture
            pos: self.pos
            size: self.size
    GridLayout:
        size_hint: .1, .1
        pos_hint: {'center_x': .5, 'center_y': .5}
        rows: 1
        Button:
            text: 'Play'
            font_size: 20
            font_name: 'DroidSans'
            bold: True
            italic: True
            height: 10
            background_color: (0.5, 0.7, 0.5, 0.9)
            #Read more about them from documentation
            background_normal: './images/orange.png'
            background_down: './images/green.png'
            border: 30,30,30,30
            color: (1, .3, .8, .5)

        on_press: self.text = 'Oh yeah'

'''


class MenuScreen(Screen):
    background_image = ObjectProperty(
                                Image(
                                    source='../Examples/examples/widgets/sequenced_images/data/images/button_white_animated.zip',
                                    anim_delay=.5))
    Builder.load_string(gui)
    pass

# Create the screen manager
sm = ScreenManager()
sm.add_widget(MenuScreen(name='menu'))


class MyJB(App):
    def build(self):
        return sm

if __name__ == '__main__':
    MyJB().run() 

Answer

qua-non picture qua-non · Apr 4, 2014

Button in kivy starts with a ButtonBehavior which is combined with a Label adding properties like background_normal/down...for handling textures on the canvas.

Knowing this you can simply combine ButtonBehavior with any other widget you choose. Eg.

from kivy.base import runTouchApp
from kivy.lang import Builder

kv = '''
<ButImage@ButtonBehavior+AsyncImage>

FloatLayout:
    # we don't specify anything here so float layout takes the entire size of the window.
    ButImage:
        id: but
        # take 50% size of the FloatLayout
        size_hint: .5, .5
        # Make Button change it's opacity when pressed for visual indication
        opacity: 1 if self.state == 'normal' else .5
        source: 'http://www.victoriamorrow.com/sitebuildercontent/sitebuilderpictures/enter_button.gif'
        # Introduce Label incase you want text on top of the image
        Label:
            center: but.center
            # change text acc to but state
            text: "Normal" if but.state == 'normal' else 'down'
'''

if __name__ == '__main__':
    runTouchApp(Builder.load_string(kv))

Here we just set the ButtonBehavior to be combined with a AsyncImage which downloads the image from web for your background.

you should see something like thisscreenshot asyncimage button

Animation affect in background

This would be as simple as changing the source to animated gif or list of images inside a .zip.

from kivy.base import runTouchApp
from kivy.lang import Builder


kv = '''
<ButImage@ButtonBehavior+AsyncImage>

FloatLayout:
    ButImage:
        id: but
        size_hint: .5, .5
        opacity: 1 if self.state == 'normal' else .5
        allow_stretch: True
        keep_ratio: False
        source: 'http://media1.policymic.com/site/article-items/2095/1_gif.gif'
        Label:
            center: but.center
            text: "Normal" if but.state == 'normal' else 'down'


'''

if __name__ == '__main__':
    runTouchApp(Builder.load_string(kv))

Look at the sequence images example This was done before ButtonBehaviors were introduced so it even has a example of a AnimatedButton class using the older method which is essentially not needed any more.

Shadow Effect:

There are many ways to do this too.

You could either add a shadow to a widget/layout and have the button on top of this widget/layout take up less space than the shadow so as to account for touch on the shadows.

Or Create your own CustomButtonBehavior class derived from ButtonBehavior that overrides collidepoint method to only return True for custom collision. There is a example of using custom collision for widgets. You could even set the Image's keep_data property to True and later check pixel data for alpha to determine if you want to return true for collision.

Rounded edges etc.

Simply use a image with rounded edges kivy supports use of BorderImage Instruction which is equivalent to css borderimage in terms of functionality. Kivy's own button by default uses this. Try and experiment with border attribute of BorderImage.