pythonkivycarouselkivy-recycleview

Kivy RecycleView for Carousel slide direction not working


Each kivy carousel slide consists of an image at the top (with a checkbox) and text below. The direction of the carousel is 'right' (one after the other horizontally). Image and text data are fed to the carousel with a recycleview.

The minimum code results with a) Each slide stacked vertically; b) the checkbox not displaying.

from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.recycleview import RecycleView
from kivy.properties import StringProperty, ListProperty, NumericProperty

Builder.load_string("""
<CarouselSlide>:
    BoxLayout:
        orientation: 'vertical'
        size_hint_y: 1
        AsyncImage:
            source: root.tile
            size_hint_y: 0.5
            CheckBox:
                id: cb
                root_ref: root.index
                on_press: app.on_checkbox_press(self, self.active, self.state, self.root_ref)
        TextInput:
            text: root.text
            size_hint_y: 0.5
            multiline: True

<CarouselScreen>:
    name: 'carousel_screen'
    Carousel:
        direction: 'right'
        ignore_perpendicular_swipes: True
        size: root.width, root.height
        RV:
            id: rv
            viewclass: 'CarouselSlide'
            RecycleBoxLayout:
                orientation: 'vertical'
                size_hint_y: None
                default_size_hint: 1, None
                height: self.minimum_height
                default_size: 1, root.height
""")

class CarouselScreen(Screen):
    pass

class CarouselSlide(Screen):
    tile = StringProperty('')
    text = StringProperty('')
    index = NumericProperty(-1)
    cb_state = StringProperty('normal')

    def __init__(self, **kwargs):
        super(CarouselSlide, self).__init__(**kwargs)
        self.bind(cb_state=self.set_cb_state)

    def set_cb_state(self, carouselslide, cb_state_value):
        self.ids.cb.state = cb_state_value

class RV(RecycleView):
    data = ListProperty('[]')

    def __init__(self, **kwargs):
        super(RV, self).__init__(**kwargs)
        self.get_slide_data()

    def get_slide_data(self):
        text="Ooh, a storm is threatening. My very life today. If I don't get some shelter. Ooh yeah I'm gonna fade away. War, children. It's just a shot away. It's just a shot away. War, children. It's just a shot away. It's just a shot away."
        self.data = [{'tile': 'The Rolling Stones', 'text': text, "index": i, "cb_state": 'normal'} for i in range(41)]

class ThisApp(App):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def build(self):
        self.sm = ScreenManager()
        self.sm.add_widget(CarouselScreen(name="carousel_screen"))
        return self.sm

    def on_checkbox_press(self, checkbox, active, state, root_ref):
        if active:
            print(active, state, root_ref)
        else:
            print(active, state, root_ref)

        new_state = checkbox.state
        checkbox.state = 'normal'

        rv = self.root.get_screen('carousel_screen').ids.rv
        rv.data[root_ref]['cb_state'] = new_state
        rv.refresh_from_data()

if __name__ == "__main__":
    ThisApp().run()

Solution

  • I believe the problem is that you are defining the CheckBox as a child of AsyncImage. The AsyncImage is not intended to be a container. Try un-indenting the CheckBox in your kv, and adding size/position attributes:

    <CarouselSlide>:
        BoxLayout:
            orientation: 'vertical'
            size_hint_y: 1
            AsyncImage:
                source: root.tile
                size_hint_y: 0.5
            CheckBox:
                id: cb
                size_hint: None, None
                size: 48, 48
                pos_hint: {'center_x':0.5}
                root_ref: root.index
                on_press: app.on_checkbox_press(self, self.active, self.state, self.root_ref)
            TextInput:
                text: root.text
                size_hint_y: 0.5
                multiline: True