---
# System prepended metadata

title: AI LINE Bot練功坊-L9 Message Type (3) Flex Message
tags: [Python, Line Bot]

---

AI LINE Bot練功坊-L9 Message Type (3) Flex Message
===
## 匯入函式
```python
from linebot.v3.messaging import (
    FlexMessage,
    FlexBubble,
    FlexImage,
    FlexMessage,
    FlexBox,
    FlexText,
    FlexIcon,
    FlexButton,
    FlexSeparator,
)
```

# Flex Message 彈性訊息
LINE BOT 可以使用 Flex Message ( 彈性樣板訊息 ) 發送客製化的選單訊息，Flex Message 使用網頁 CSS3 的 Flex 語法，可以設計較豐富且多樣性的按鈕版型
而且不像 Template 在電腦版就無法運作，Flex Message 支援 「電腦版」

## Flex Message 結構
Flex Message 是由三層結構組成
**Container 容器 -> Block 區塊 -> Component 組件**

內容及圖片參考 [官方網站](https://developers.line.biz/en/docs/messaging-api/flex-message-elements/)。

![image](https://hackmd.io/_uploads/Byy3CHpyA.png)

>Reference：https://developers.line.biz/en/docs/messaging-api/using-flex-messages/

### **Container 容器**
容器是 Flex Message 的最外層結構，有兩種類型可選
|                      1.Bubble                      |                     2.Carousel                     |
|:--------------------------------------------------:|:--------------------------------------------------:|
|          是包含一個訊息框的容器。           |       是包含多個 Bubble 的容器，可以橫向滑動       |
| ![image](https://hackmd.io/_uploads/BkpIvayg0.png) | ![image](https://hackmd.io/_uploads/rkPQwTyeA.png)
 |

### **Block 區塊**

Block區塊是組成 Bubble 的結構，分為四個部分
![image](https://hackmd.io/_uploads/rJNI3C1g0.png)



Header: 顯示標題的區塊
Hero: 顯示主要圖片的區塊
Body: 顯示主要內容的區塊
Footer: 顯示按鈕或補充訊息的區塊

### **Component 組件**
組件是 Flex Message 的最基本結構，類型很多如下
1. Box: 定義介面的佈局，裡面可以包含其它組件
2. Button: 按鈕
![image](https://hackmd.io/_uploads/SJIv2RklA.png)
3. Image: 圖片
![image](https://hackmd.io/_uploads/rkPu3AkeR.png)
4. Icon: 文字前的圖標
![image](https://hackmd.io/_uploads/ryYFhRkgC.png)
5. Text: 文字
![image](https://hackmd.io/_uploads/rJkqnC1gR.png)
6. Span: 單行文字，可以指定不同的顏色、大小、粗細
![image](https://hackmd.io/_uploads/HJ5a301e0.png)
7. Separator: 分隔線，可製作水平或垂直的分隔線
![image](https://hackmd.io/_uploads/S1yNa0JlR.png)
8. Filler: 可以在組件間插入空白區塊
![image](https://hackmd.io/_uploads/HJAkpRJgC.png)

>Reference：https://developers.line.biz/en/docs/messaging-api/flex-message-elements/

### **Component 屬性**
**Bubble**

* direction 內容排列方向
    * ltr 從左至右
    * rtl 從右至左
* size 調整 bubble 大小
    * giga
    * mega
    * kilo
    * hecto
    * deca
    * micro
    * nano
* Action Type
    * postback
    * uri
    * message
    * datetimepicker
---
**Header、Hero、Body、Footer**

* backgroundColor 背景顏色
* seperator 分隔線
    * True
    * False 
* seperatorColor 分隔線顏色
---
**Box**

* layout
    * vertical 垂直排列
    box, button, image, text, separator, and filler
    * horizontal 水平排列
    box, button, image, text, separator, and filler
    * baseline 水平排列
    icon, text, and filler
    ---

    #### baseline 與 horizontal 不同之處
    baseline 中的元件按同一基線垂直對齊，這意味著所有子元件都使用相同的基線，無論字型大小。 圖示元件的基線是圖示影象的底部。 
    ![baseline.7a7956f9](https://hackmd.io/_uploads/BJ-dsUHWA.png)
    baseline 中的元件不能套用 *gravity* 和 *offsetBottom* 屬性
* position
    * relative 相對配置
    可以設定父層元素為基準元素作絕對位移。
    * absolute 絕對配置
    以「自己原本顯示的位置為基準位置」來指定上下左右的偏移，且區塊原本的空間仍會保留不會消失。
* flex 如果元件的位置屬性設定為相對（relative），則元件的寬度和高度由元件的 flex 屬性決定。
    #### horizontal box 中的寬度分配
    假設一個 horizontal box 有兩個元件，一個將 flex 屬性設定為 2，另一個設定為 3。 然後將可用寬度（horizontal box 的寬度）以二比三的比例劃分，並分配給每個元件。
    
    ![flexSample1.aa211fd0](https://hackmd.io/_uploads/B1tQ3kIWA.png)
    
    ---

    #### Vertical box 中的高度分配
    假設一個 horizontal box 有兩個 vertical boxes，第一個 box 佔用五行的文字，第二個 box 根據其 flex 屬性的值，以 2 : 3 的比例分配給兩個文字元件。
    
    ![line](https://hackmd.io/_uploads/ryzVklI-C.png)
* spacing 元件之間的間距

    本示例 Flex Message 有一個 horizontal box，其中三個 vertical boxes 作為子元件，間隔均為（md）。
    
    ![1](https://hackmd.io/_uploads/ByJ0cOIbA.png)
* margin 元件之間的邊距 （邊距屬性優先於間距屬性）
    
    本示例 Flex Message 有一個 horizontal box，其中三個 vertical boxes 作為子元件，間隔均為（md），第三個 vertical box 邊距屬性為 xxl。

    ![Unknown](https://hackmd.io/_uploads/HJkvkKIZR.png)

* width
* height
* maxWidth 
* maxHeight 
* backgroundColor 
* borderWidth 
* borderColor
* cornerRadius 
* justifyContent 在父階層中設置幫助安排內容
    * flex-start 
    這將從起點開始對所有項目進行排列。如果是水平，則將基於 bubble 給出的方向： ltr (從左至右)或 rtl (從右至左)；反之若是垂直，則所有項目將從上至下進行排列。
    * center 
    將所有項目排列在中心點。
    * flex-end 
    這將從末端開始對所有項目進行排列。如果是水平，則將基於 bubble 給出的方向： ltr 從左至右)或 rtl (從右至左)；反之若是垂直，則所有項目將從下至上進行排列。
    * space-between 
    第一個與最後一個項目貼齊左右側的佈局，其餘項目平均分配到顯示畫面的中心。
    * space-around 
    將每個項目之間的距離均勻地分配。
    * space-evenly 
    平均劃分每個項目與訊息佈局的距離來進行排列。

    #### **layout: vertical** 
    ![截圖 2024-04-24 00.47.36](https://hackmd.io/_uploads/H1tSIwHWR.png)
    
    ---

    #### **layout: horizontal & direction: ltr**
    ![截圖 2024-04-24 00.47.21](https://hackmd.io/_uploads/Hk4U8wrZ0.png)
* alignItems 指定父階層中的項目更換排序方向
    * flex-start 
    如果父階層水平排列，則所有項目將自上而下；如果父母垂直排列，則所有項目將從一開始（取決於 Bubble 的方向）。
    * center 
    將所有項目排列在水平和垂直方向的中心點。
    * flex-end 
    如果將父階層水平排列，則所有項目將從下至上；並且如果將父接成垂直排列，則所有項目將從末端排列（取決於 Bubble 的方向）。
    #### **layout: vertical** 
    ![截圖 2024-04-24 00.50.18](https://hackmd.io/_uploads/SJglvvHbR.png)
    

    ---

    #### **layout: horizontal & direction: ltr**
    ![截圖 2024-04-24 00.50.08](https://hackmd.io/_uploads/rycewwHWR.png)
* Offset 偏移量
    * offsetTop 
    將元件從元件原始位置的頂部邊緣向下移動。
    * offsetBottom 
    將元件從元件原始位置的底部邊緣向上移動。
    * offsetStart
    將元件從文字開始的地方移開。 如果 bubble 的方向是 LTR，則向右移動。 如果是 RTL，則向左移動。
    * offsetEnd
    遠離文字結束的地方。 如果 bubble 的方向是 LTR，則向左移動。 如果是 RTL，則向右移動。
    
    ![offset1](https://hackmd.io/_uploads/Bk4OPK8bR.png)
    
    #### Offset when position is relative
    
    ![offset2](https://hackmd.io/_uploads/r1ScwYUZC.png)
    
    | Property | Value   |
    | -------- | ------  | 
    | position | relative|
    | offsetTop| 10px    |
    | offStart | 40px    |
    
    ---
    #### Offset when position is absolute
    
    ![offset3](https://hackmd.io/_uploads/HkFZ5Yvb0.png)

    | Property  | Value    |
    | --------- | -------- |
    | position  | absolute |
    | offsetTop | 10px     |
    | offBottom | 20px     |
    | offStart  | 40px     |
    | offEnd    | 80px     |
* Padding 元件內所有內容與元件自身的邊界
    * paddingAll
    * paddingTop
    * paddingBottom
    * paddingStart
    * paddingEnd
    

    ![padding](https://hackmd.io/_uploads/Bk-MWq8-0.png)

    | Property    | Value    |
    | ---------   | -------- |
    | paddingTop  | 20px     |
    | paddingAll  | 80px     |
    | paddingStart| 40px     |
    

* Background Type
    * linearGradient 線性漸變背景
        * angle
        * startColor
        * endColor
        * centerColor
        * centerPosition
        ```
        {
          "type": "bubble",
          "body": {
            "type": "box",
            "layout": "vertical",
            "contents": [],
            "background": {
              "type": "linearGradient",
              "angle": "0deg",
              "startColor": "#ff0000",
              "endColor": "#0000ff"
            },
            "height": "200px"
          }
        }
        ```
        #### Linear gradient at 45 degrees (Bottom-left to top-right)
        ![45d](https://hackmd.io/_uploads/BJLto9IWR.png)
        #### Linear gradient at 90 degrees (Left to right)
        ![90d](https://hackmd.io/_uploads/BJ_pi9Ib0.png)
        #### Linear gradient at 180 degrees (Top to bottom)
        ![180d](https://hackmd.io/_uploads/r1rCo98ZA.png)
* Action Type
---
**Button**
* flex
* position
* margin
* height
* style
    * link
    * primary
    * secondary
* color
* gravity 
    #### 使用 gravity 屬性，垂直對齊 text, image, 或 button
    ![gravity](https://hackmd.io/_uploads/rJ970c8WC.png)
* scaling
    如果縮放屬性設定為 true，您可以根據 LINE 應用程式的字型大小設定自動縮放字型大小和圖示大小。
* adjustMode
    如果您為按鈕和文字元件的 adjustMode 屬性 shrink-to-fit，文字的字型大小將自動縮小以適應。
    
![stf](https://hackmd.io/_uploads/rJ_E5KDW0.png)

* Offset
* Action
---
**Image**
* flex
* position
* url
* margin
* align
    #### 使用 align 屬性，水平對齊 text, 或 image
    ![ho](https://hackmd.io/_uploads/rJGLctPZA.png)
* gravity
* size 
* aspectRatio 圖片長寬比
* aspectMode
    * cover
    * fit
* backgroundColor 
* animated
     * True
     * False 
* Offset
* Action
---
**Icon**
* position
* url
* margin
* size
* scaling
* aspectRatio
* Offset
---
**Text**
* text
* flex
* margin
* size
* lineSpacing 行距
* color
* weight
    * regular
    * bold 
* style
    * normal
    * italic 
* decoration
    * none
    * underline 底線
    * line-through 刪除線
* position
* align
* gravity
* wrap 過長的文字自動換至下一行
    * True
    * False 
* scaling
* maxLines
* adjustMode
* Offset
* Action Type
---
**Span** 
* text
* size
* color
* weight
* style
* decoration
---
**Separator**
* margin
* color
---
**Filter**
* flex

>Reference：https://developers.line.biz/en/docs/messaging-api/flex-message-layout/

## Flex Message 設計(flex-simulator)
官方有推出輔助工具 [Flex-Simulator](https://developers.line.biz/flex-simulator/) ，可以直接在網頁上製作 Flex Message
需登入LINE Developers 帳戶
![image](https://hackmd.io/_uploads/Bk7bMtTJA.png)
設計工具主要分成三個區塊：
* 左邊的區塊：預覽畫面 ( 傳送到 LINE 的訊息長相 )
* 中間的區塊：Flex Message 的樹狀清單結構
* 右邊的區塊：點擊中間區塊清單裡的元件，出現對應可以修改的屬性或參數

![image](https://hackmd.io/_uploads/rJ5eFTylC.png =50%x)
LINE官方也有提供的一些免費模板，可以根據需求改寫裡面的參數，設計成理想中的訊息

### 製作 Flex 的步驟：
你可以點右上角的「Showcase」從官方提供的模板修改寫裡面的參數，改成想要得樣式，再匯出

如果想要從零開始做的話:
1. 點擊網頁右上角的「NEW」，選擇Container建立一個包含訊息框的 Bubble or Carousel類型
2. 點選想要修改的Block 區塊
3. 點擊中間清單的元件後，就能從上方的「+」號，加入額外的元件
![image](https://hackmd.io/_uploads/S1X-nT1lC.png)
如:設定標題文字及背景顏色、新增圖片(設定圖片連結與大小)、新增文字(設定字體大小、粗細和對齊方式)、新增平行線(新增分隔線，並設定邊距)、新增按鈕(設定連結及按鈕樣式、添加連結到網站、或撥打電話

### 測試 Flex Message
點擊右上方的 Send 按鈕後，會出現 Register destination 的按鈕，繼續點擊後會出現 QRCode，掃描 QRCode 加入好友就能測試
![image](https://hackmd.io/_uploads/ByGMuPtxR.png)
![image](https://hackmd.io/_uploads/rk39g0ygC.png =30%x)


### 用Python 傳 Flex Message
首先先將官方模擬器的資料複製，點選右上角的複製JSON內容，編寫完基本指令後在「contents」之處將複製的JSON內容貼上



## Flex Message 範例
![image](https://hackmd.io/_uploads/Bydu001e0.png =80%x)
首先，程式碼建立一個 URL，這個 URL 是用來指向一個圖片資源的

FlexBubble()是一個物件，其屬性包括 direction（排列方向）、hero（主要圖像）、body（內容區域）、footer（底部區域）
* contents 是用來容納各種 Flex 元件的容器
* hero：主圖像是一張圖片，其中包含 URL、尺寸、比例、顯示模式等屬性
* body：內容區域包含了標題、評分、詳細資訊等內容，使用了不同的 Flex 元件來組合顯示
* footer：底部區域包含了一個連結到電話的按鈕和一個連結到網站的按鈕，使用了 FlexButton 元件

最後，使用 line_bot_api.reply_message 函式回覆訊息，設定了回覆的內容為一個 FlexMessage，其中的內容就是前面建立的 FlexBubble 物件

**Python程式碼**
```python
url = request.url_root + '/static/Logo.jpg'
url = url.replace("http", "https")
app.logger.info("url=" + url)

bubble = FlexBubble(
    direction='ltr',
    hero=FlexImage(
        url=url,
        size='full',
        aspect_ratio='20:13',
        aspect_mode='cover',
        action=URIAction(uri='https://www.facebook.com/NTUEBIGDATAEDU', label='label')
    ),
    body=FlexBox(
        layout='vertical',
        contents=[
            # title
            FlexText(text='教育大數據', weight='bold', size='xl'),
            # review
            FlexBox(
                layout='baseline',
                margin='md',
                contents=[
                    FlexIcon(size='sm', url="https://scdn.line-apps.com/n/channel_devcenter/img/fx/review_gold_star_28.png"),
                    FlexIcon(size='sm', url="https://scdn.line-apps.com/n/channel_devcenter/img/fx/review_gold_star_28.png"),
                    FlexIcon(size='sm', url="https://scdn.line-apps.com/n/channel_devcenter/img/fx/review_gold_star_28.png"),
                    FlexIcon(size='sm', url="https://scdn.line-apps.com/n/channel_devcenter/img/fx/review_gold_star_28.png"),
                    FlexIcon(size='sm', url="https://scdn.line-apps.com/n/channel_devcenter/img/fx/review_gold_star_28.png"),
                    FlexText(text='5.0', size='sm', color='#999999', margin='md', flex=0)
                ]
            ),
            # info
            FlexBox(
                layout='vertical',
                margin='lg',
                spacing='sm',
                contents=[
                    FlexBox(
                        layout='baseline',
                        spacing='sm',
                        contents=[
                            FlexText(
                                text='Place',
                                color='#aaaaaa',
                                size='sm',
                                flex=1
                            ),
                            FlexText(
                                text='Da\'an District, Taipei ',
                                wrap=True,
                                color='#666666',
                                size='sm',
                                flex=5
                            )
                        ],
                    ),
                    FlexBox(
                        layout='baseline',
                        spacing='sm',
                        contents=[
                            FlexText(
                                text='Time',
                                color='#aaaaaa',
                                size='sm',
                                flex=1
                            ),
                            FlexText(
                                text="10:00 - 23:00",
                                wrap=True,
                                color='#666666',
                                size='sm',
                                flex=5,
                            ),
                        ],
                    ),
                ],
            )
        ],
    ),
    footer=FlexBox(
        layout='vertical',
        spacing='sm',
        contents=[
            # callAction
            FlexButton(
                style='link',
                height='sm',
                action=URIAction(label='CALL', uri='tel:000000'),
            ),
            # separator
            FlexSeparator(),
            # websiteAction
            FlexButton(
                style='link',
                height='sm',
                action=URIAction(label='WEBSITE', uri='https://www.facebook.com/NTUEBIGDATAEDU')
            )
        ]
    ),
)

line_bot_api.reply_message(
    ReplyMessageRequest(
        reply_token=event.reply_token,
        messages=[FlexMessage(alt_text="hello", contents=bubble)]
    )
)

```

>Reference：https://developers.line.biz/en/reference/messaging-api/#flex-message

Youtube 課程影片
---
<iframe width="560" height="315" src="https://www.youtube.com/embed/8p-uWpy-Uh4?si=AmQ7pvG7yvvs7dTq" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

{%hackmd @ntuebigdata/about %}

## 相關教材連結

<div style="display: flex; justify-content:space-between;">
  <div>
    <a class="btn btn-warning" href="https://hackmd.io/@ntuebigdata/message-type-2-template-message" style="color:white;width:300px;text-overflow:ellipsis;overflow:hidden">◀◀◀ L08 Message Type(2) Template Message ◀◀◀</a>
  </div>
  <div>
    <a class="btn btn-info" href="https://hackmd.io/@ntuebigdata/message-type-4-imagemap-message" style="color:white;width:300px;text-overflow:ellipsis;overflow:hidden">▶▶▶ L10 Message Type(4) Imagemap Message ▶▶▶</a>
  </div>
</div>