Learning Objectives
- 說出 container、block、component 的層次,以及 bubble 四個 block 的順序
- 用 box 的 layout、flex、margin、spacing 排出需要的版面
- 用 Python 字典組出 Flex Message,以 FlexContainer.from_dict 送出,並寫出有意義的 alt_text
- 依資料用迴圈產生多張卡片,放進 carousel,並處理官方上限與沒有資料的情況
範本排不出來的版面
上一課的 Buttons、Carousel 範本格式是固定的:一個標題、一段文字、幾顆按鈕。想做一張左邊是欄位名稱、右邊是內容的課程資訊卡,範本就排不出來。
Flex Message 讓你用 JSON 自己排版。它分成三層:
| 層次 | 是什麼 | 這一課會用到 |
|---|---|---|
| Container(容器) | 整則訊息的最外層 | bubble(一張卡片)、carousel(多張卡片左右滑動) |
| Block(區塊) | 一張卡片裡的區域,由上到下 | header、hero、body、footer,都可以省略,每種最多一個 |
| Component(元件) | 區塊裡的東西 | box、text、button、image、separator |
header、body、footer 裡面放的一定是 box;hero 常放圖片。box 可以放其他元件,也可以再放 box,版面就是這樣一層層組出來的。
在 Python 裡,JSON 就是字典和串列。FlexContainer.from_dict(字典) 把它轉成 SDK 的物件,再放進 FlexMessage。
逐步看:一張卡片怎麼送出去
卡片只是一個字典常數 CARD。按播放,注意 on_text 裡只多了一行:把字典轉成物件,包成 FlexMessage。最後印出的是模擬平台實際收到的內容。
box 決定元件怎麼排
每個 box 都要設定 layout,也就是裡面的元件往哪個方向排:
layout |
排列方式 | 常見用途 |
|---|---|---|
vertical |
由上往下 | 整個 body、一段段內容 |
horizontal |
由左往右 | 「欄位名稱+內容」同一列 |
baseline |
由左往右,文字的基線對齊 | 字級不同的文字放同一列、小圖示加文字 |
baseline 裡只能放 text、icon 這類元件,不能放 button、image、separator,也不能再放 box。
幾個調整版面的屬性:
| 屬性 | 寫在哪裡 | 作用 |
|---|---|---|
flex |
子元件 | 佔幾份寬度或高度。官方預設值:horizontal 裡是 1(平均分配),vertical 裡是 0(依內容大小) |
spacing |
父層的 box | 所有子元件之間的間距 |
margin |
子元件 | 這個元件前面的間距,會蓋過父層的 spacing |
wrap |
text | 預設 false,文字太長會被截斷;要換行就設 True |
size、weight、color |
text | 字級(xs、sm、md、lg、xl…)、粗細(bold)、顏色(#RRGGBB) |
要畫分隔線用 separator。舊教材常見的 filler(佔空白的元件)官方已標示棄用,留白請改用 margin、spacing、flex。
逐步看:把重複的版面寫成函數
課程資訊卡有很多列長得一樣,每一列寫一次會很長。既然是字典,就可以用函數產生。這個範例沒有機器人,只看字典怎麼組、SDK 怎麼轉。
想一想:版面
一個 bubble 同時有四個 block,由上到下的順序是?
想讓「學分」和「3」排在同一列,左窄右寬。應該怎麼寫?
alt_text 的用途是什麼?
依資料產生多張卡片
實際的機器人很少寫死一張卡片。資料通常來自串列或資料庫,一筆資料變成一個 bubble,全部放進 carousel:
carousel = {'type': 'carousel', 'contents': [course_bubble(c) for c in courses]}
官方對 carousel 的限制,模擬平台都沒有檢查,要在真實環境注意:
| 限制 | 內容 |
|---|---|
| 張數 | 最多 12 個 bubble。資料比較多時要自己截斷或分頁 |
| 大小 | 一個 bubble 的 JSON 上限 30 KB,一個 carousel 上限 50 KB |
| 寬度 | 同一個 carousel 裡的 bubble 要一樣寬(size 相同) |
卡片上的按鈕通常用 postback 動作:把課程編號放在 data 裡,使用者看不到,機器人收到 PostbackEvent 後依編號查資料。這和第 7 課的快速回覆是同一套機制。
逐步看:迴圈產生卡片,按鈕帶回課程編號
注意 course_bubble 被呼叫了幾次,以及第二張卡片的按鈕 data 是怎麼來的。
動手做:14 門課的輪播
COURSES 有 14 門課,但 carousel 最多放 12 個 bubble。完成兩個函數:
course_bubble:body 放課程名稱(weight設bold)與時間兩段文字;footer 放一顆 postback 按鈕,label是「查看」,data是action=detail&id=課程編號on_text:只取前 12 門,alt_text寫成「共 14 門課,顯示前 12 門」,14 這個數字要用len算
模擬平台不會擋超過 12 個的 carousel,所以要靠你自己的程式處理。
想一想:資料與上限
這段程式會印出什麼?
COURSES = [{'name': 'A'}, {'name': 'B'}, {'name': 'C'}]
def bubble(c):
return {'type': 'bubble', 'body': {'type': 'box', 'layout': 'vertical',
'contents': [{'type': 'text', 'text': c['name']}]}}
carousel = {'type': 'carousel', 'contents': [bubble(c) for c in COURSES[:2]]}
print(len(carousel['contents']), carousel['contents'][-1]['body']['contents'][0]['text'])
查詢結果有 30 筆,機器人想用 carousel 列出。比較合適的做法是?
在官方工具確認實際樣子
模擬手機只畫出結構。實際的字型、留白、圖片比例,要用 LINE 官方的 Flex Message Simulator 確認(要登入 LINE Developers 帳號,官方教學)。
常見的工作方式:
- 在 Simulator 裡拉出想要的版面
- 複製它產生的 JSON,用
json.loads轉成字典 - 把會變動的部分(課程名稱、時間)換成變數,寫成像
course_bubble這樣的函數
官方也提醒,同一則 Flex Message 在不同的裝置、作業系統、LINE 版本、語言與字型下,看起來可能不同,所以重要的訊息要放在文字裡,不要只靠排版傳達。
重點整理
- Flex Message 分三層:container(
bubble、carousel)、block(header、hero、body、footer)、component(box、text、button、image、separator) box的layout決定方向;flex分配比例,spacing寫在父層,margin寫在子元件- 用字典組版面,
FlexContainer.from_dict轉成物件,alt_text必填 - 重複的版面寫成函數,依資料用迴圈產生;carousel 最多 12 個 bubble,沒有資料時改回文字
- 模擬手機只看結構,實際樣子用 Flex Message Simulator 確認
到目前為止,機器人每次都只看眼前這句話。下一課讓它記得使用者上一句說了什麼。