正文開始之前先說幾句別的。上月20號發布的Moblin V2 Beta版中,Clutter的版本已經到了0.9,API與clutter 0.8相比有了些變化,兩者在API上已不兼容。鑒於pyclutter目前版本依然還是0.8,並且本系列博文更側重於概念,因此所用的API一律基於clutter 0.8。等clutter 1.0正式版本發布後,我們再來看API的變化。
之前講了一點有關Actor與Stage的內容,有了演員和舞台,接下來自然而然就要上台表演,也就是這裡所謂的動畫了。在Clutter的動畫中,有三個概念不得不提一下,他們是——Timeline,Alpha和Behaviour。
Timeline,即時間軸,也就是表演的時間段,這個概念我想大家應該都能明白,不多廢話了。Behaviour,即行為,也就是表演的內容,比如放大、縮小、旋轉、透明化等等均屬於行為的范疇。最後講講Alpha,這個詞挺難翻譯的,如果放到表演上來理解,Alpha所指的應該是表演方式。比如同樣是將某個Actor放大2倍,通過Alpha可以控制是采用線性放大,加速放大還是減速放大等效果。Alpha其實是一個與總幀數和當前幀相關的函數,它的返回值是一個介於0和ALPHA_MAX之間的數,每當一個新的幀產生時都會調用Alpha函數,通過Alpha函數的返回值來確定當前幀的變化。用戶可以自定義Alpha函數,也可以使用clutter提供的一些常用Alpha函數。
一般來說,基於clutter進行動畫編程,代碼結構通常是這樣的:
1. 創建時間軸ClutterTimeline
2. 創建ClutterAlpha
3. 創建ClutterBehaviour
4. 將Behaviour應用到Actor上
5. 啟動時間軸
目前Clutter提供的Behaviour包括有Bspline、Ellipse、Depth、Opacity、Path、Rotate和Scale。如果用戶覺得這些Behaviour無法滿足應用的需求,可以通過實現自定義Behaviour或者使用Timeline的new-frame事件來制作自定義的動畫效果。
回到以前使用的demo 程序——照片浏覽器上來,這次要添加的功能是相片的正中顯示。之前照片是以隨機方位和角度鋪在桌面上,現在通過雙擊照片,圖片將以動畫的形式移至窗口正中並轉至0度。單擊正中顯示照片將使圖片以動畫形式回到原先的位置。
代碼如下:
#!/usr/bin/python
import sys
import os
import random
import clutter
STAGE_WIDTH=1024
STAGE_HEIGHT=768
Dragging = False
DraggingPhoto = None
Center = False
pic_list = []
timeline = None
alpha = None
p_behavior = None
r_behavior = None
class Photo:
'''Photo class'''
border_width = 10
def __init__(self, path, stage):
self.stage = stage
self.path = path
self.x = 0
self.y = 0
self.degree = 0
self.drag_start_x = 0
self.drag_start_y = 0
self.pic = clutter.Texture()
self.pic.set_from_file(path)
self.width = self.pic.get_width()+2*Photo.border_width
self.height = self.pic.get_height()+2*Photo.border_width
self.frame = clutter.Rectangle()
self.frame.set_color(clutter.Color(0xff, 0xff, 0xff, 0xff))
self.frame.set_position(self.x, self.y)
self.frame.set_size(self.width, self.height)
self.group = clutter.Group()
self.group.add(self.frame)
self.group.add(self.pic)
self.pic.set_position(Photo.border_width, Photo.border_width)
self.stage.add(self.group)
self.group.set_reactive(True)
self.group.connect("button-press-event", self.on_button_press)
self.group.connect("button-release-event", self.on_button_release)
self.group.connect("motion-event", self.on_motion)
def set_random_position(self):
stage_width = self.stage.get_width()
stage_height = self.stage.get_height()
left = random.randint(0, stage_width)
top = random.randint(0, stage_height)
degree = random.randint(0, 360)
self.set_position(left, top, degree)
def set_position(self, x, y, degree):
self.x = x
self.y = y
self.degree = degree
self.group.set_position(x, y)
self.group.set_rotation(clutter.Z_AXIS, degree, self.width/2, self.height/2, 0)
def on_button_press(self, actor, event):
global Dragging, DraggingPhoto, Center, timeline, alpha, p_behavior, r_behavior
if event.button == 1 and event.click_count == 2 and Center == False:
self.group.raise_top()
for pic in pic_list:
pic.group.set_reactive(False)
self.group.set_reactive(True)
timeline = clutter.Timeline(30, 30)
alpha = clutter.Alpha(timeline, clutter.ramp_inc_func)
tx = int((STAGE_WIDTH-self.width)/2)
ty = int((STAGE_HEIGHT-self.height)/2)
knots=((self.x, self.y), (tx, ty),)
p_behavior = clutter.BehaviourPath(alpha, knots)
p_behavior.apply(self.group)
r_behavior = clutter.BehaviourRotate(clutter.Z_AXIS, self.degree, 0, alpha, True)
r_behavior.set_center(self.width/2, self.height/2, 0)
r_behavior.apply(self.group)
timeline.start()
Center = True
return True
if event.button == 1 and event.click_count == 1 and Center == True:
for pic in pic_list:
pic.group.set_reactive(True)
timeline = clutter.Timeline(30, 30)
alpha = clutter.Alpha(timeline, clutter.ramp_dec_func)
tx = int((STAGE_WIDTH-self.width)/2)
ty = int((STAGE_HEIGHT-self.height)/2)
knots=((self.x, self.y), (tx, ty),)
p_behavior = clutter.BehaviourPath(alpha, knots)
p_behavior.apply(self.group)
r_behavior = clutter.BehaviourRotate(clutter.Z_AXIS, self.degree, 0, alpha, True)
r_behavior.set_center(self.width/2, self.height/2, 0)
r_behavior.apply(self.group)
timeline.start()
Center = False
return True
if event.button == 1 and Dragging == False and Center == False:
Dragging = True
DraggingPhoto = self
self.drag_start_x = event.x
self.drag_start_y = event.y
self.group.raise_top()
return True
return False
def on_motion(self, actor, event):
global Dragging, DraggingPhoto
if event.modifier_state & clutter.BUTTON1_MASK and Dragging == True and DraggingPhoto == self:
dist_x = event.x - self.drag_start_x
dist_y = event.y - self.drag_start_y
self.group.move_by(dist_x, dist_y)
self.drag_start_x = event.x
self.drag_start_y = event.y
return True
return False
def on_button_release(self, actor, event):
global Dragging, DraggingPhoto
if event.button == 1:
Dragging = False
DraggingPhoto = None
return True
return False
def main(args):
if len(args) < 2:
print "The number of arguments is less than 2!"
return -1
path=args[1]
if not os.path.exists(path):
print path, "doesn't exist!"
return -1
stage = clutter.Stage()
stage.set_size(STAGE_WIDTH, STAGE_HEIGHT)
stage.set_color(clutter.Color(0x00, 0x00, 0x00, 0x00))
stage.connect("destroy", clutter.main_quit)
if not path.endswith(os.sep):
path+=os.sep
filelist = os.listdir(path)
for item in filelist:
pic=Photo(path+item, stage)
pic.set_random_position()
pic_list.append(pic)
stage.show_all()
clutter.main()
if __name__ == '__main__':
main(sys.argv)