首页 > 解决方案 > Kivy:如何通过向上或向下按钮保持选中的 RecycleView 列表项移动,以便进一步移动?

问题描述

下面的代码显示了可选项目的列表。选择一个项目后,可以使用适当的按钮在列表中向上或向下移动它。移动项目后,它不会保持选中状态,因此可以再次移动它。我不知道如何解决这个问题。

执行代码...

kivyrecycleview_movebuttons.kv 文件

<KivyRecycleView>:
    orientation: "vertical"
    recycleViewList: recycleView_list
    moveUpButton: moveUp_Button
    moveDownButton: moveDown_Button
    boxLayoutContainingRV: boxlayout_recycleview

    BoxLayout:
        pos_hint: {'top': 1}
        size_hint_y: 0.2
        height: "28dp"
        Button:
            id: moveUp_Button
            text: "^"
            size_hint_x: 1
            on_press: root.moveUpSelItem()
        Button:
            id: moveDown_Button
            text: "v"
            size_hint_x: 1
            on_press: root.moveDownSelItem()

    BoxLayout:
        id: boxlayout_recycleview
        size_hint_y: 0.8
        height: "60dp"
        RecycleView:
            id: recycleView_list
            scroll_y: 0 # forces scrolling to list bottom after adding an entry
            effect_cls: "ScrollEffect" # prevents overscrolling
            viewclass: 'SelectableLabel'

            SelectableRecycleBoxLayout:
                default_size: None, dp(15) # height of list line
                default_size_hint: 1, None
                size_hint_y: None
                height: self.minimum_height
                spacing: 0.5
                orientation: 'vertical'
                multiselect: False
                touch_multiselect: False

<SelectableLabel>:
    # Draw a background to indicate selection
    canvas.before:
        Color:
            rgba: (1, 0, 0, 1) if self.selected else (.0, 0.9, .1, .3)
        Rectangle:
            pos: self.pos
            size: self.size
        Color:
            rgba: (0, 0.9, .1, .3)
        Rectangle:
            pos: self.pos
            size: self.size

kivyrecycleview_movebuttons.py 文件

from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label

# RecycleView related imports
from kivy.properties import BooleanProperty
from kivy.uix.behaviors import FocusBehavior
from kivy.uix.recycleboxlayout import RecycleBoxLayout
from kivy.uix.recycleview.layout import LayoutSelectionBehavior
from kivy.uix.recycleview.views import RecycleDataViewBehavior

class SelectableRecycleBoxLayout(FocusBehavior, LayoutSelectionBehavior,
                                 RecycleBoxLayout):
    ''' Adds selection and focus behaviour to the view. '''

    # required to authorise unselecting a selected item
    touch_deselect_last = BooleanProperty(True)

class SelectableLabel(RecycleDataViewBehavior, Label):
    ''' Add selection support to the Label '''
    index = None
    selected = BooleanProperty(False)
    selectable = BooleanProperty(True)

    def refresh_view_attrs(self, rv, index, data):
        ''' Catch and handle the view changes '''
        self.index = index

        return super(SelectableLabel, self).refresh_view_attrs(
            rv, index, data)

    def on_touch_down(self, touch):
        ''' Add selection on touch down '''
        if super(SelectableLabel, self).on_touch_down(touch):
            return True
        if self.collide_point(*touch.pos) and self.selectable:
            return self.parent.select_with_touch(self.index, touch)

    def apply_selection(self, rv, index, is_selected):
        ''' Respond to the selection of items in the view. '''
        if not self.selected and not is_selected:
            # case when adding a new list item
            return
        elif self.selected and not is_selected:
            # toggling from selected to unselected
            self.selected = False
            rv.parent.parent.recycleViewCurrentSelIndex = -1

        else:
            # toggling from unselected to selected
            self.selected = not self.selected
            rv.parent.parent.recycleViewCurrentSelIndex = index

class KivyRecycleView(BoxLayout):
    recycleViewCurrentSelIndex = -1

    def __init__(self, **kwargs):
        super(KivyRecycleView, self).__init__(**kwargs)

        self.populateList()

    def populateList(self):
        for i in range(6):
            listEntry = {'text': 'line {}'.format(i)}
            self.recycleViewList.data.append(listEntry)

    def moveUpSelItem(self):
        oldIndex = self.recycleViewCurrentSelIndex
        newIndex = oldIndex - 1
        requestTotalNumber = len(self.recycleViewList.data)

        if newIndex < 0:
            # if first line request is moved up, it is moved at the end of the
            # request history list
            newIndex = requestTotalNumber - 1

        self.moveItemInList(list=self.recycleViewList.data, oldIndex=oldIndex, newIndex=newIndex)

    def moveDownSelItem(self):
        oldIndex = self.recycleViewCurrentSelIndex
        newIndex = oldIndex + 1
        requestTotalNumber = len(self.recycleViewList.data)

        if newIndex == requestTotalNumber:
            # if last line request is moved down, it is moved at the beginning of the
            # request history list
            newIndex = 0

        self.moveItemInList(list=self.recycleViewList.data, oldIndex=oldIndex, newIndex=newIndex)

    def moveItemInList(self, list, oldIndex, newIndex):
        list.insert(newIndex, list.pop(oldIndex))

class KivyRecycleView_moveButtonsApp(App):
    def build(self): # implicitely looks for a kv file of name kivyrecycleview.kv which is
                     # class name without App, in lowercases

        return KivyRecycleView()

if __name__ == '__main__':
    dbApp = KivyRecycleView_moveButtonsApp()

    dbApp.run()

标签: python-3.xkivy

解决方案


感谢您的上述评论。这是一个可行的解决方案(在 Windows 10 和 Android 上测试)。与初始代码相比的修改由 # <<------ 注释突出!我确信存在更简单的解决方案......

from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label

# RecycleView related imports
from kivy.properties import BooleanProperty
from kivy.uix.behaviors import FocusBehavior
from kivy.uix.recycleboxlayout import RecycleBoxLayout
from kivy.uix.recycleview.layout import LayoutSelectionBehavior
from kivy.uix.recycleview.views import RecycleDataViewBehavior

class SelectableRecycleBoxLayout(FocusBehavior, LayoutSelectionBehavior,
                                 RecycleBoxLayout):
    ''' Adds selection and focus behaviour to the view. '''

    # required to authorise unselecting a selected item
    touch_deselect_last = BooleanProperty(True)

class SelectableLabel(RecycleDataViewBehavior, Label):
    ''' Add selection support to the Label '''
    index = None
    selected = BooleanProperty(False)
    selectable = BooleanProperty(True)

    def refresh_view_attrs(self, rv, index, data):
        ''' Catch and handle the view changes '''
        self.rv = rv                            #<<------
        self.index = index

        return super(SelectableLabel, self).refresh_view_attrs(
            rv, index, data)

    def on_touch_down(self, touch):
        ''' Add selection on touch down '''
        if super(SelectableLabel, self).on_touch_down(touch):
            return True
        if self.collide_point(*touch.pos) and self.selectable:
            if self.rv.parent.parent.recycleViewCurrentSelIndex == self.index: #<<------
                self.selected = False                                          #<<------
                self.rv.parent.parent.recycleViewCurrentSelIndex = -1          #<<------
            else:                                                              #<<------
                self.rv.parent.parent.recycleViewCurrentSelIndex = self.index  #<<------
            
            return self.parent.select_with_touch(self.index, touch)


    def apply_selection(self, rv, index, is_selected):
        ''' Respond to the selection of items in the view. '''
        if rv.parent.parent.recycleViewCurrentSelIndex == index: #<<------
            is_selected = True                                   #<<------
            self.selected = False                                #<<------
        else:                                                    #<<------
            is_selected = False                                  #<<------
            self.selected = True                                 #<<------
    
        if not self.selected and not is_selected:
            # case when adding a new list item
            return
        elif self.selected and not is_selected:
            # toggling from selected to unselected
            self.selected = False                   #<<------
        else:
            # toggling from unselected to selected
            self.selected = not self.selected       #<<------


class KivyRecycleView(BoxLayout):
    recycleViewCurrentSelIndex = -1

    def __init__(self, **kwargs):
        super(KivyRecycleView, self).__init__(**kwargs)

        # setting RecycleView list item height from config
        self.populateList()

    def populateList(self):
        for i in range(6):
            listEntry = {'text': 'line {}'.format(i)}
            self.recycleViewList.data.append(listEntry)

    def moveUpSelItem(self):
        oldIndex = self.recycleViewCurrentSelIndex
    
        if oldIndex == -1:                                  #<<------
            return
        
        newIndex = oldIndex - 1
        itemTotalNumber = len(self.recycleViewList.data)
        
        if newIndex < 0:
            # if first line request is moved up, it is moved at the end of the
            # request history list
            newIndex = itemTotalNumber - 1
        
        self.moveItemInList(list=self.recycleViewList.data, oldIndex=oldIndex, newIndex=newIndex)
        self.recycleViewCurrentSelIndex = newIndex          #<<------

    def moveDownSelItem(self):
        oldIndex = self.recycleViewCurrentSelIndex
    
        if oldIndex == -1:                                  #<<------
            return
        
        newIndex = oldIndex + 1
        itemTotalNumber = len(self.recycleViewList.data)
        
        if newIndex == itemTotalNumber:
            # if last line request is moved down, it is moved at the beginning of the
            # request history list
            newIndex = 0
        
        self.moveItemInList(list=self.recycleViewList.data, oldIndex=oldIndex, newIndex=newIndex)
        self.recycleViewCurrentSelIndex = newIndex          #<<------

    def moveItemInList(self, list, oldIndex, newIndex):
        list.insert(newIndex, list.pop(oldIndex))

class KivyRecycleView_moveButtonsApp(App):
    def build(self): # implicitely looks for a kv file of name kivyrecycleview.kv which is
                     # class name without App, in lowercases

        return KivyRecycleView()

if __name__ == '__main__':
    dbApp = KivyRecycleView_moveButtonsApp()

    dbApp.run()

推荐阅读