pythonkivykivymdkivy-recycleview

How to select only one button in kivy recycleview


I'm creating an mp3 player using kivy recycleview, the app has a lot of buttons in the playlist screen and whenever you click on a button, icon of that button change from 'play' to 'pause' and vice versa.

I would like to know how to make it be in a way that clicking another button changes all the other buttons icon to 'play' only that selected button should be with icon of 'pause'.

.py file:

from kivy.lang import Builder
from kivymd.app import MDApp
from kivy.core.window import Window
from kivy.properties import StringProperty, ObjectProperty
from kivymd.theming import ThemableBehavior
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.screen import MDScreen
from kivymd.uix.behaviors import RectangularRippleBehavior
from kivy.uix.behaviors import ButtonBehavior

from kivy.clock import Clock

Builder.load_file('playlist.kv')

KV = """
#:import FadeTransition kivy.uix.screenmanager.FadeTransition

ScreenManager:
    transition: FadeTransition()

    Playlist:
        name: "playlist screen"

"""

class Playlist(ThemableBehavior, MDScreen):
    rv = ObjectProperty()
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        Clock.schedule_once(self._finish_init)

    def music_list(self):
        return ['audio '+str(i) for i in range(1, 121)]

    def _finish_init(self, dt):
        self.set_list_musics()
        
    def set_list_musics(self):
        """Builds a list of audios for the screen Playlist."""
        print(self.ids)

        def add_music_item(num, sura, secText, icon):
            self.ids.rv.data.append(
                {
                    "viewclass": "MusicListItem",
                    "number": num,
                    "text": sura,
                    "secondary_text": secText,
                    "icon": icon,
                    "callback": lambda x:x})
    
        for i in range(len(self.music_list())):
            music = self.music_list()
            add_music_item(str(i+1), music[i], '00:00:00', 'play')
         
    
class MusicListItem(ThemableBehavior, RectangularRippleBehavior, ButtonBehavior, MDBoxLayout):
    text = StringProperty()
    secondary_text = StringProperty()
    number = StringProperty()
    icon = StringProperty()
    
    def on_release(self, *args):
        if self.icon == "play":
            self.icon = "pause"
        else:
            self.icon = "play"
    
    
class Mp3Player(MDApp):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
       
    def build(self):
        self.theme_cls.primary_palette = "Purple"
        self.theme_cls.theme_style = "Dark"
        return Builder.load_string(KV)


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

.kv file:

#: import gch kivy.utils.get_color_from_hex
#: import StiffScrollEffect kivymd.effects.stiffscroll.StiffScrollEffect

<Playlist>
    md_bg_color: gch("#5D1049")
        
    MDGridLayout:
        cols: 1
        
        MDToolbar:
            left_action_items: [["menu", lambda x: x]]
            right_action_items: [["magnify", lambda x: x]]
            elevation: 10
            md_bg_color: 75/255, 6/255, 54/255, 1
            title: 'Playlist'
            pos_hint: {'top':1}
                    
        MDBoxLayout:
            orientation: 'vertical'
           
            RecycleView:
                id: rv
                effect_cls: 'ScrollEffect'
                viewclass: 'MusicListItem'

                RecycleBoxLayout:
                    padding: dp(10)
                    default_size: None, dp(60)
                    default_size_hint: 1, None
                    size_hint_y: None
                    height: self.minimum_height
                    orientation: 'vertical'

     
<MusicListItem>
    size_hint_y: None
    padding: dp(14)
    height: dp(60)

    canvas:
        Color:
            rgba:
                self.theme_cls.divider_color
        Line:
            points: (root.x+dp(10), root.y, root.x+self.width-dp(10)-0, root.y)

    MDBoxLayout:
        orientation: "horizontal"
        pos_hint: {"center_x": .5, "center_y": .5}

        MDBoxLayout:
            orientation: 'horizontal'
            MDBoxLayout:
                orientation: 'vertical'
                size_hint_x: .2
              
                MDLabel:
                    text: root.number
                    font_style: "H6"
                    adaptive_height: True
                
                MDLabel:
                    size_hint_y: .3
                 
            MDBoxLayout:
                orientation: 'vertical'
             
                MDLabel:
                    text: root.text
                    font_style: "Subtitle2"
                    adaptive_height: True
    
                MDLabel:
                    text: root.secondary_text
                    font_style: "Caption"
                    theme_text_color: "Hint"
                    adaptive_height: True

            MDIconButton:
                icon: root.icon          
    

Thank you


Solution

  • So, as I've understood, you want to set an icon as 'pause' while all other as 'play'. One way of doing this could be like, you have to reload the RecyclView data each time the icon changes.

    1. Now to provide data with icon reference (i.e. 'play' or 'pause') I found the number property suitable, so I change it to NumericProperty. Thus number = NumericProperty().

    2. Also this requires some change in kv,

                    MDLabel:
                        text: str(int(root.number))
                        font_style: "H6"
                        adaptive_height: True
    
    1. To let Playlist know about the number reference,
        def set_list_musics(self, music_no = 0):
            """Builds a list of audios for the screen Playlist."""
            print(self.ids)
            self.ids.rv.data = [ ] # Since you are appending data and we need to reload everytime.
    
    1. Make required changes in data,
            for i in range(len(self.music_list())):
                new_icon = 'pause' if i+1 == music_no else 'play'
                music = self.music_list()
                add_music_item(str(i+1), music[i], '00:00:00', new_icon)
    
    1. Now the final part, trigger the change via the button,
        def on_release(self, *args):
            if self.icon == "play":
                self.icon = "pause"
                pl = self.parent.parent.parent.parent.parent # Accessing the Playlist according to your design pattern.
                pl.set_list_musics(self.number)
            else:
                self.icon = "play"
    

    Note that I made this change in 'pause' icon (i.e. in if self.icon == "play"), thus you can also freely toggle this icon. Placing it otherwise could not make it possible.

    Perhaps this could have been done more systematically with other design styles. I've found some issues with your design pattern. This (such as calling function in for loop repeatedly etc.) may make it a little bit slower as the data increases.