首页 > 代码库 > Kivy A to Z -- 如何实现焦点切换效果

Kivy A to Z -- 如何实现焦点切换效果

Kivy是面向触屏设备的,对键盘,遥控器等输入设备的处理比较弱,但是有时候我们又需要实现对按键的处理,如通过方向键切换焦点,这篇文章来讨论下如何去实现。


在看下面的代码之前,最好是对Kivy的UI系统有一个基本的了解。


按照惯例,我们先上代码,然后再对代码进行解释:


focustest.py


import kivy
kivy.require('1.8.0')

from kivy.app import App
from kivy.properties import StringProperty,BooleanProperty
from kivy.core.window import Window
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.graphics import Color, Ellipse, Line,Rectangle
from kivy.uix.boxlayout import BoxLayout


class MyButton(Button):
    focus = BooleanProperty(False)
    def __init__(self,**kwargs):
        super(MyButton,self).__init__(**kwargs)
#        with self.canvas.before:
        rect=(self.pos[0]+4,self.pos[1]+4,self.size[0]-8,self.size[1]-8)
        with self.canvas.after:
            self.edge_color = Color(0,0,0,0)
            self.edge = Line(rectangle=rect,width=4,joint='round')
            self.edge_center_color = Color(1,1,1,0) 
            self.edge_center = Line(rectangle=rect,width=1)

        # listen to size and position changes
        self.bind(pos=self.update_rect,size=self.update_rect)
        self.focus=False

    def update_rect(self,instance,value):
        rect=(self.pos[0]+4,self.pos[1]+4,self.size[0]-8,self.size[1]-8)
        self.edge.rectangle=rect
        self.edge_center.rectangle=rect

    def set_focus(self):
        self.focus = True
        self.parent.on_focus_changed(self)
    def on_focus(self,instance,focused):
        print '++++++++++++++on_focus:',focused
        if focused:
            self.edge_color.rgba = (0.4,0.4,1,1)
            self.edge_center_color.rgba=(1,1,1,1)
        else:
            self.edge_color.rgba = (0,0,0,0)
            self.edge_center_color.rgba=(0,0,0,0)

    def on_press(self):
        pass

    def on_release(self):
        pass

    def on_touch_down(self,touch):
        print '++++++++++++++++++on_touch_down:',touch
        if self.collide_point(*touch.pos):
            self.set_focus()
        super(MyButton,self).on_touch_down(touch)

class FocusTest(BoxLayout):
    def __init__(self,**kargs):
        super(FocusTest,self).__init__(**kargs)
        self.buttons = []
        btn1 = MyButton(text='1',on_press=self.on_press)
        btn1.size_hint=(0.5,0.5)
        self.add_widget(btn1)

        Window.bind(on_key_down=self.on_key_down)

        btn = MyButton(text='2',on_press=self.on_press)
        btn.size_hint=(0.2,0.2)
        btn.pos_hint={'top':0.8}
        self.add_widget(btn)

        btn = MyButton(text='3',on_press=self.on_press)
        btn.size_hint=(0.2,0.3)
        self.add_widget(btn)

        self.children[-1].set_focus()

        self.index = len(self.children) - 1

        with self.canvas.before:
            Color(0, 1, 0, 1)
            self.rect = Rectangle(size=self.size,pos=self.pos)
            Color(1, 1, 0, 1)
            self.border = Line(rectangle=self.pos+self.size,width=3)

        # listen to size and position changes
        self.bind(pos=FocusTest.update_rect, size=FocusTest.update_rect)
        for item in self.children:
            print item.text
        
    def on_focus_changed(self,focusitem):
        index = 0
        for item in self.children:
            if focusitem is item:
                self.index = index
            else:
                item.focus = False
            index += 1

    def on_key_down(self,window,key,scancode,a,b):
        print 'on_key_down:',key,scancode
        if key == 275: #left
            if self.index == 0:
                self.index = len(self.children) - 1
            else:
                self.index -= 1
        elif key == 276: #right
            if self.index == len(self.children) - 1:
                self.index = 0
            else:
                self.index += 1
        self.children[self.index].set_focus()

    def on_key_up(self,window,key,scancode):
        pass

    def on_press(self,control):
        print control
    def on_release(self,control):
        print control

    def update_rect(self,value):
        self.rect.pos = self.pos
        self.rect.size = self.size
        self.border.rectangle=self.pos+self.size

class MyApp(App):
    def build(self):
        return FocusTest()
    def on_pause(self):
        return True

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

运行这个例子,会创建三个Button,按左右方向键可以在三个Button之间切换焦点,如下图所示:



接下来看下这130行的代码做了什么:

先挑重点的来讲:

在FocusTest.__init__中,有一行很关键的代码:

        Window.bind(on_key_down=self.on_key_down)

这行代码就是用于在Window注册一个key down的消息监听,以便接收按键消息。

接下来看下on_key_down做了些什么事:

    def on_key_down(self,window,key,scancode,a,b):
        print 'on_key_down:',key,scancode
        if key == 275: #left
            if self.index == 0:
                self.index = len(self.children) - 1
            else:
                self.index -= 1
        elif key == 276: #right
            if self.index == len(self.children) - 1:
                self.index = 0
            else:
                self.index += 1
        self.children[self.index].set_focus()

这里实现了根据左右方向键切换焦点的功能,最关键的是最后一行代码:

self.children[self.index].set_focus()

我们来看看MyButton.set_focus做了什么事:

    def set_focus(self):
        self.focus = True
        self.parent.on_focus_changed(self)

self.focus是一个BooleanProperty,给它赋值将会触发:on_focus,有疑问可以看下下面的文章:

Kivy A to Z -- Kivy之Properties

on_focus方法就是根据MyButton是否获取到焦点来进行画不同的矩形框:

    def on_focus(self,instance,focused):
        print ‘++++++++++++++on_focus:‘,focused
        if focused:
            self.edge_color.rgba = (0.4,0.4,1,1)
            self.edge_center_color.rgba=(1,1,1,1)
        else:
            self.edge_color.rgba = (0,0,0,0)
            self.edge_center_color.rgba=(0,0,0,0)

最后看下self.parent.on_focus_changed(self)做了什么,parent即为FocusTest:

    def on_focus_changed(self,focusitem):
        index = 0
        for item in self.children:
            if focusitem is item:
                self.index = index
            else:
                item.focus = False
            index += 1

这里的代码就是将其它Button设置为非焦点状态。


最后再来看下MyButton.__init__里的:

self.bind(pos=self.update_rect,size=self.update_rect)

这行代码的作用就是监听Button大小和位置的改变,并调整画焦点时矩形框的大小。


OK,that‘s ALL