Office-PowerPoint-MCP-Server

Office-PowerPoint-MCP-Server 是一款基于 MCP(Model Context Protocol)协议的 PowerPoint 操作服务器,内部依赖 python-pptx 库,能够通过标准化的工具接口完成创建、编辑、保存、填充、排版等各类演示文稿自动化操作。它支持对 .pptx 文件的完整 Round-trip,能够在保持原始结构与样式的基础上,灵活插入文本、图像、表格和图表等元素。

核心功能一览

  • 完整 Round-trip:无论是打开还是保存,都能保留原始文档的所有元素与属性。
  • 新增幻灯片:支持根据布局索引添加更多幻灯片,轻松扩展演示文稿结构。
  • 占位符文本填充:可批量或单条替换文本占位符,生成要点式、列表式幻灯片。
  • 图片管理:在幻灯片任意位置以指定大小插入图片,支持本地文件与 Base64 数据流。
  • 文本框操作:添加文本框,设置字体大小、加粗等样式属性,实现个性排版。
  • 表格与图表:支持插入表格并格式化单元格,添加柱状图、折线图、饼图等多种图表。
  • 自动图形:提供多种预定义形状(多边形、流程图节点等),助你绘制流程与架构图。
  • 文档属性管理:可读取与修改文档的标题、主题等核心属性,满足元数据需求。

安装与部署

前置条件

Python 版本:需 Python 3.10 及以上。

包管理器:系统已安装 pip,并能访问 PyPI 或本地私有源。

通过 Smithery 一键安装

1
npx -y @smithery/cli install @GongRzhe/Office-PowerPoint-MCP-Server --client claude

此方法适合已在使用 Smithery 平台的团队,一行命令即可完成依赖下载、环境配置与服务集成。

脚本式安装(推荐)

项目附带了 setup_mcp.py 脚本,可自动化完成全流程:

1
python setup_mcp.py

脚本功能包括:

  1. 检测 Python 版本和 pip 可用性;

  2. 询问安装选项(PyPI 安装或本地开发模式);

  3. 安装所需依赖;

  4. 生成 MCP 协议配置文件;

  5. 指导 Claude Desktop 或其他客户端完成集成。

手动安装步骤

对于喜欢手动掌控每一步的开发者,可按以下流程执行:

  1. 克隆仓库
1
git clone https://github.com/GongRzhe/Office-PowerPoint-MCP-Server.gitcd Office-PowerPoint-MCP-Server
  1. 安装依赖
1
pip install -r requirements.txt
  1. 赋予执行权限
1
chmod +x ppt_mcp_server.py

完成以上步骤后,即可启动服务器并调用各项工具。

MCP 协议简介与配置示例

MCP(Model Context Protocol)是一种轻量级 JSON-RPC 风格协议,旨在规范化客户端与服务器之间的工具调用。配置文件示例如下。

本地 Python 服务配置

1
2
3
4
5
6
7
8
9
10
11
12
13
{  
    "mcpServers": {
        "ppt": {
            "command": "python",
            "args": [
                "/path/to/ppt_mcp_server.py"
            ],
            "env": {

            }
        }
    }
}

该配置指定使用 Python 命令启动 ppt_mcp_server.py,无需额外环境变量。

UVX 无需本地安装方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{  
    "mcpServers": {
        "ppt": {
            "command": "uvx",
            "args": [
                "--from",
                "office-powerpoint-mcp-server",
                "ppt_mcp_server"
            ],
            "env": {

            }
        }
    }
}

UVX 客户端会从云端拉取对应工具,无需在本地安装源码,适合免维护场景。

常用工具与接口说明

以下小节将按功能模块细致说明各工具及参数。

文档创建与打开

create_presentation

功能:生成一个空白演示文稿并返回 presentation_id

参数:无。

返回:{"presentation_id": "xxx"}

open_presentation

功能:打开已有 .pptx 文件。

参数:{"path": "/full/path/to/file.pptx"}

返回:{"presentation_id": "xxx"}

save_presentation

功能:将内存中的演示文稿保存到指定路径。

参数:{"presentation_id":"xxx", "path":"/dest.pptx"}

返回:操作结果状态。

幻灯片操作

add_slide

功能:根据布局索引添加新幻灯片。

参数:

  • presentation_id:文档 ID
  • layout_index:布局编号(如 0 为封面、1 为标题+内容等)
  • title(可选):幻灯片标题

返回:新增幻灯片的 slide_id

get_slide_info

功能:查询幻灯片详情,包括占位符、形状列表等。

参数:{"presentation_id":"xxx"}

返回:幻灯片元数据数组。

文本与占位符填充

populate_placeholder

功能:将指定占位符填充为文本。

参数:

  • slide_id
  • placeholder_index
  • text

示例:填充文章要点列表。

add_bullet_points

功能:在文本框内以项目符号形式添加多行文本。

参数:

  • slide_id
  • text_list:字符串数组

适用场景:制作分点陈述或总结页。

add_textbox

功能:自定义位置与大小添加文本框。

参数:

  • lefttopwidthheight(单位:EMU)
  • textfont_sizebold 等样式属性。

图表与表格

add_table

功能:插入表格并返回表格对象 ID。

参数:

  • colsrows
  • lefttopwidthheight

可配合 format_table_cell 对单元格进行格式化。

add_chart

功能:添加数据可视化图表。

参数:

  • chart_type:如 "bar""line""pie"
  • data:二维数组或指定格式
  • position:图表所在坐标与尺寸

支持图例、数据标签、坐标轴设置等。

图片与形状

add_image / add_image_from_base64

功能:插入图片文件或 Base64 编码图像。

参数:path 或 base64_data,以及位置尺寸属性。

add_shape

功能:插入流程图形状、多边形等自动图形。

参数:shape_typelefttopwidthheight

可进一步设置颜色、线型、文本等。

文档属性管理

get_presentation_info

功能:获取标题、主题、作者等属性。

set_core_properties

功能:修改文档元数据,如标题(title)、主题(subject)。

参数:JSON 对象键值对。

实战示例详解

下面通过完整示例,演示如何在代码中调用 MCP 工具,实现从创建文档到填充内容、插入图表、导出文件的全流程。

新建演示文稿并添加标题页

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 调用 create_presentation
result = use_mcp_tool(
server_name="ppt",
tool_name="create_presentation",
arguments={}
)
presentation_id = result["presentation_id"]

# 添加封面幻灯片(布局 0 通常为封面)
slide = use_mcp_tool(
server_name="ppt",
tool_name="add_slide",
arguments={
"presentation_id": presentation_id,
"layout_index": 0,
"title": "年度总结报告"
}
)
slide_id = slide["slide_id"]

# 填充封面副标题
use_mcp_tool(
server_name="ppt",
tool_name="populate_placeholder",
arguments={
"slide_id": slide_id,
"placeholder_index": 1,
"text": "2025 年度业绩概览"
}
)

批量填充数据与图表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 添加数据概览幻灯片(布局 1)
slide_data = use_mcp_tool(
server_name="ppt",
tool_name="add_slide",
arguments={
"presentation_id": presentation_id,
"layout_index": 1,
"title": "销售数据一览"
}
)
slide_id_data = slide_data["slide_id"]

# 在内容区添加柱状图
chart = use_mcp_tool(
server_name="ppt",
tool_name="add_chart",
arguments={
"slide_id": slide_id_data,
"chart_type": "bar",
"data": [
["月份", "销售额"],
["一月", 120],
["二月", 150],
["三月", 180]
],
"left": 914400, # 1 英寸
"top": 1828800, # 2 英寸
"width": 6096000, # 6.7 英寸
"height": 3429000 # 3.8 英寸
}
)

自定义样式与高级布局

自定义文本框

1
2
3
4
5
6
7
8
9
10
11
12
13
14
use_mcp_tool(
server_name="ppt",
tool_name="add_textbox",
arguments={
"slide_id": slide_id_data,
"left": 457200, # 0.5 英寸
"top": 914400, # 1 英寸
"width": 3048000,# 3.3 英寸
"height": 914400,# 1 英寸
"text": "注:以上数据为示例,仅供参考",
"font_size": 12,
"bold": False
}
)

流程图形状

1
2
3
4
5
6
7
8
9
10
11
12
use_mcp_tool(
server_name="ppt",
tool_name="add_shape",
arguments={
"slide_id": slide_id_data,
"shape_type": "flowChart_process",
"left": 914400,
"top": 4000000,
"width": 2000000,
"height": 800000
}
)

进阶技巧与最佳实践

性能优化与批量处理

复用文档对象:尽量一次性读取/打开文档,批量操作后再统一保存,减少 I/O 开销。

并行调用:在支持异步或多进程环境中,可并行启动多个 MCP 会话,实现多文档并行生成。

缓存配置:将 MCP 配置文件缓存到内存,仅在变更时重读,提升启动效率。

脚本与 CI/CD 集成

将生成脚本纳入项目仓库,配置 CI 管道(如 GitHub Actions、GitLab CI)自动执行文档构建任务。

配合版本控制系统,对演示文稿版本进行追踪与差异分析。

使用容器化部署(Docker)封装运行环境,确保跨平台一致性。

跨平台部署注意事项

Windows 系统下需保证 PowerPoint 文件路径长度 ≤ 260 字符,避免路径截断错误。

Linux 或 macOS 环境下,注意 python-pptx 对 EMU 单位的换算与 DPI 设定。

若在无 GUI 环境运行,确认脚本无需调用 Office COM 接口,仅依赖 python-pptx 即可。

常见问题与排查指南

  1. 启动报错 “ModuleNotFoundError: No module named ‘pptx’”

原因:未执行 pip install python-pptx

解决:执行 pip install -r requirements.txt 或单独安装依赖。

  1. 插入图片后显示空白

原因:图片路径错误或 Base64 格式不正确。

解决:检查 add_image 参数,确保 path 指向可读文件,或验证 Base64 数据完整性。

  1. 表格格式不生效

原因:调用 format_table_cell 前需确认表格已成功插入并获取表格对象。

解决:先使用 add_table 获取表格 ID,再依次调用 format_table_cell

  1. 图表数据未更新

原因:图表数据需要与布局配合,某些布局会覆盖手动添加的图表。

解决:使用自定义布局或空白布局(layout_index 对应空白页)以避免覆盖。

Python高手必备:用Office-PowerPoint-MCP-Server实现PPT自动化神操作 | 高效码农

PHP 使用 Headless Chrome 把 HTML 生成 PDF

可以使用 wkhtmltopdf

这里使用 chrome-php/chrome: Instrument headless chrome/chromium instances from PHP 来实现。

1
composer require chrome-php/chrome
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
namespace App\Services;

use HeadlessChromium\BrowserFactory;

class HtmlToPdfConverter
{
/**
* Render a PDF from the given HTML.
*/
public static function render(string $html): string
{
$browser = (new BrowserFactory())->createBrowser([
'windowSize' => [1920, 1080]
]);

$page = $browser->createPage();

$page->setHtml($html);

return base64_decode(
$page->pdf([
'printBackground' => true
])->getBase64()
);
}
}
1
2
3
4
5
6
7
8
9
10
use App\Services\HtmlToPdfConverter;

Route::get('/html-to-pdf', function () {
$pdf = HtmlToPdfConverter::render('<h1>Hello World</h1>');

return response($pdf, 200, [
'Content-Type' => 'application/pdf',
'Content-Disposition' => 'inline; filename="hello.pdf"'
]);
});

参考:

Convert HTML to PDF using Headless Chrome in PHP — Amit Merchant — A blog on PHP, JavaScript, and more

Laravel 访问器中的值对象及性能提升

Laravel 的 Eloquent ORM 通过内置缓存和值对象支持增强了访问器功能。这些特性能够有效地处理复杂的计算和结构化数据,同时保持干净、可维护的代码。

当处理计算成本高昂的操作或需要将复杂的数据结构表示为适当的对象而不是普通数组时,这种方法被证明特别有价值。

1
2
3
4
5
6
protected function complexStats(): Attribute
{
return Attribute::make(
get: fn () => $this->calculateStats()
)->shouldCache();
}

下面是一个使用值对象实现位置处理的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?php

namespace App\Models;

use App\ValueObjects\Location;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Casts\Attribute;

class Store extends Model
{
protected function location(): Attribute
{
return Attribute::make(
get: fn ($value) => new Location(
latitude: $this->latitude,
longitude: $this->longitude,
address: $this->address,
timezone: $this->timezone
),
set: function (Location $location) {
return [
'latitude' => $location->latitude,
'longitude' => $location->longitude,
'address' => $location->address,
'timezone' => $location->timezone
];
}
)->shouldCache();
}

protected function operatingHours(): Attribute
{
return Attribute::make(
get: fn () => $this->calculateHours()
)->withoutObjectCaching();
}

private function calculateHours()
{
// Dynamic calculation based on timezone and current time
return $this->location->getLocalHours();
}
}
1
2
3
4
5
6
$store = Store::find(1);
$store->location->address = '123 New Street';
$store->save();

// Access operating hours (recalculated each time)
$hours = $store->operatingHours;

Laravel 的访问器特性为处理复杂的数据结构和通过智能缓存优化性能提供了强大的工具

Laravel 访问器中的值对象及性能提升 | 日思录

基于 Laravel 的 Dimension 规则进行图像大小验证

Laravel 通过 Dimensions 规则提供了强大的图像验证功能,为应用的媒体上传提供了对图像大小和比例的精细控制。

下例中的基本实现展示了规则的灵活性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;

// Basic validation
$validator = Validator::make($request->all(), [
'profile_photo' => [
'required',
Rule::dimensions()
->minWidth(400)
->minHeight(400)
->maxWidth(2000)
->maxHeight(2000)
]
]

以下是如何再媒体管理系统中实现复杂图片验证的策略:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
class MediaController extends Controller
{
public function upload(Request $request)
{
$imageType = $request->input('type', 'standard');

$rules = [
'image' => array_merge(
['required', 'image', 'max:10240'], // 10MB max
$this->getDimensionRules($imageType)
)
];

$request->validate($rules);

// Process and store the validated image
$path = $request->file('image')->store('media/' . $imageType);

return response()->json([
'success' => true,
'path' => $path
]);
}

protected function getDimensionRules(string $type): array
{
return match($type) {
'thumbnail' => [
Rule::dimensions()
->width(300)
->height(300)
],
'hero' => [
Rule::dimensions()
->minWidth(1200)
->minHeight(600)
->maxHeight(800)
->ratio(2)
],
'gallery' => [
Rule::dimensions()
->minWidth(800)
->minHeight(600)
->maxWidth(3000)
->maxHeight(2000)
],
default => [
Rule::dimensions()
->minWidth(400)
->minHeight(400)
]
};
}
}

Dimensions 规则可确保整个应用的最佳图像质量,同时防止因上传大小不当而导致的存储和带宽问题。

基于 Laravel 的 Dimension 规则进行图像大小验证 | 日思录

laravel-route-fallback

Laravel 的 Route::fallback 提供了一种优雅的方式来处理与任何定义的路由都不匹配的请求。你可以为遇到缺失页面的用户创建有意义的体验,而不是显示通用的 404 页面。

当页面被移动或重命名,或者处理旧系统的遗留 URL 时,此功能对于保持用户参与度特别有价值。它还有助于收集缺失页面的数据,为网站的结构和内容策略提供信息。

1
2
3
4
Route::fallback(function () {
return view('errors.404')
->with('message', 'Page not found');
});

你还可以使用 Request 对象来获取更多上下文:

1
2
3
4
5
6
7
8
9
10
11
12
13
use Illuminate\Http\Request;

Route::fallback(function (Request $request) {
// Access current path
$path = $request->path();

// Check if it's an API request
if ($request->expectsJson()) {
return response()->json(['error' => 'Not Found'], 404);
}

return view('errors.404', compact('path'));
});

【转】在 Laravel 中处理不匹配的路由 | 日思录

Laravel 使用中间件实现 JWT 认证

JSON Web Tokens(JWT)

JSON Web 令牌 (JWT) 是紧凑的、URL 安全的令牌,用于在各方之间安全地传输信息作为 JSON 对象。JWT 由标头(Header)、有效负载(Payload)和签名(Signature)三部分组成,包含元数据、声明和签名,以确保完整性和真实性。

JWT token 示例:

1
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

使用逗号分隔:

  1. Header: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
  2. Payload: eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
  3. Signature: SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Laravel 中的 JSON Web 令牌实现

这里使用 firebase/php-jwt :

1
composer require firebase/php-jwt
1
php artisan make:middleware JsonWebTokenAuthentication
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Firebase\JWT\JWT;
use Firebase\JWT\SignatureInvalidException;
use Firebase\JWT\BeforeValidException;
use Firebase\JWT\ExpiredException;
use DomainException;
use InvalidArgumentException;
use UnexpectedValueException;

class JsonWebTokenAuthentication
{
public function handle(Request $request, Closure $next)
{
$bearerToken = $request->bearerToken() ?? '';
$key = config('PATH_TO_YOUR_KEY');

try {
$decoded = JWT::decode($bearerToken, $key);
} catch (InvalidArgumentException $e) {
// provided key/key-array is empty or malformed.
} catch (DomainException $e) {
// provided algorithm is unsupported OR
// provided key is invalid OR
// unknown error thrown in openSSL or libsodium OR
// libsodium is required but not available.
} catch (SignatureInvalidException $e) {
// provided JWT signature verification failed.
} catch (BeforeValidException $e) {
// provided JWT is trying to be used before "nbf" claim OR
// provided JWT is trying to be used before "iat" claim.
} catch (ExpiredException $e) {
// provided JWT is trying to be used after "exp" claim.
} catch (UnexpectedValueException $e) {
// provided JWT is malformed OR
// provided JWT is missing an algorithm / using an unsupported algorithm OR
// provided JWT algorithm does not match provided key OR
// provided key ID in key/key-array is empty or invalid.
}

return $next($request);
}
}

JWT authentication using Laravel middleware - HiBit

垃圾评论过滤:spatie/laravel-honeypot

介绍

对于提供了评论功能的博客系统,最大的威胁或许就是垃圾评论,各种铭感词、链接,烦不胜烦。 spatie/laravel-honeypot 创建一个隐藏的 div,其中包含两个字段,即蜜罐字段和加密时间戳,用于标记页面提供给用户的时刻。当包含这些用户不可见的输入的表单提交到您的应用程序时,包附带的自定义验证器会检查蜜罐字段是否为空,并检查用户填写表单所花费的时间。如果表单填写得太快,或者蜜罐字段中输入了值,则此提交很可能来自垃圾邮件机器人。

安装

1
composer require spatie/laravel-honeypot

发布配置文件(可选):

1
php artisan vendor:publish --provider="Spatie\Honeypot\HoneypotServiceProvider" --tag=honeypot-config

使用

1
2
3
4
5
6
7
8
9
<form method="POST" action="...">

@honeypot

<input type="text" name="my_normal_input" value="">

...

</form>

在处理表单提交的路由中使用 ProtectAgainstSpam 中间件。该中间件将拦截任何被困在蜜罐中的请求。如果请求的提交速度超过配置的时间,它也会拦截请求。

1
2
3
4
5
6
7
use Spatie\Honeypot\ProtectAgainstSpam;

Route::middleware(ProtectAgainstSpam::class)->group(function() {

// Routes protected with honeypot technique

});

全局启用:

1
2
3
4
5
// app/bootstrap/app.php

->withMiddleware(function (Middleware $middleware): void {
$middleware->append(\Spatie\Honeypot\ProtectAgainstSpam::class);
})

避免在 Laravel 9 中发送垃圾表单 - HiBit

Filament v4 自定义登入、登出后的跳转

在多面板或基于角色的应用程序中,默认重定向可能会感觉笨拙。常见需求包括:

  • 登录后→将用户重定向到他们尝试访问的页面或仪表板
  • 注销后→将用户重定向回您的公共主页
  • 意图保留 → 记住用户在登录屏幕停止时的目标页面

第 1 步:自定义 LoginResponse

创建 app/filament/dashboard/pages/auth/LoginResponse.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php

namespace App\Filament\Dashboard\Pages\Auth;

use Illuminate\Http\RedirectResponse;
use Livewire\Features\SupportRedirects\Redirector;
use Filament\Auth\Http\Responses\Contracts\LoginResponse as LoginResponseContract;

class LoginResponse implements LoginResponseContract
{
public function toResponse($request): RedirectResponse|Redirector
{
if (session()->has('intended_url')) {
$intendedUrl = session()->pull('intended_url');
return redirect()->to($intendedUrl);
}

return redirect()->intended(filament()->getUrl());
}
}
  • 继承 Filament’s contract
  • 检查 session 中的 intended_url
  • 返回到 panel home

第 2 步:自定义 LogoutResponse

创建 app/filament/dashboard/pages/auth/LogoutResponse.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php

namespace App\Filament\Dashboard\Pages\Auth;

use Illuminate\Http\RedirectResponse;
use Filament\Auth\Http\Responses\Contracts\LogoutResponse as Responsable;

class LogoutResponse implements Responsable
{
public function toResponse($request): RedirectResponse
{
return redirect()->route('home');
}
}

第 3 步:在服务提供商中注册

编辑 AppServiceProvider:

1
2
3
4
5
6
7
8
9
10
use App\Filament\Dashboard\Pages\Auth\LoginResponse;
use App\Filament\Dashboard\Pages\Auth\LogoutResponse;
use Filament\Auth\Http\Responses\Contracts\LoginResponse as LoginResponseContract;
use Filament\Auth\Http\Responses\Contracts\LogoutResponse as LogoutResponseContract;

public function register(): void
{
$this->app->bind(LogoutResponseContract::class, LogoutResponse::class);
$this->app->bind(LoginResponseContract::class, LoginResponse::class);
}

通过将 Filament 的默认响应与您自己的响应交换,您可以确保跨仪表板、登录和注销的无缝体验。

【转自】Custom Login & Logout Redirects in Filament v4

Jetpack Compose 中的剪裁与遮罩功能

剪裁使用 Modifier 的 clip 方法:

1
2
3
4
5
6
7
Image(
painter = painterResource(R.drawable.avatar),
contentDescription = null,
modifier = Modifier
.size(72.dp)
.clip(CircleShape)
)

自定义形状

官方提供了 CircleShapeRoundedCornerShap, 如果不能满足需求,则可以自定义。

1
2
3
4
5
6
7
8
9
10
11
12
13
class SquishedOvalShape : Shape {
override fun createOutline(
size: Size,
layoutDirection: LayoutDirection,
density: Density
): Outline {
return Outline.Generic(
Path().apply {
addOval(Rect(0f, size.height / 4f, size.width, size.height))
}
)
}
}

堆叠的头像

1. 绘制头像

1
2
3
4
5
6
7
8
9
10
@Composable
fun Avatar(image: Painter, modifier: Modifier = Modifier) {
Image(
painter = image,
contentDescription = null,
modifier = modifier
.size(48.dp)
.clip(CircleShape)
)
}

2. 添加边框

使用 drawWithContent 先绘制头像,然后使用 BlendMode.Clear 绘制边框:

1
2
3
4
5
6
7
8
9
modifier.drawWithContent {
drawContent()
drawCircle(
color = Color.Black,
style = Stroke(width = 4f),
radius = size.minDimension / 2,
blendMode = BlendMode.Clear
)
}

透明混合模式会移除该区域的像素,而不是直接覆盖它们。

3. 采用离屏合成技术隔离图层

若不进行图层隔离,清除操作还会破坏背景的完整性。为防止这种情况,请将每个头像包裹在独立的图形层中:

1
2
3
Modifier.graphicsLayer {
compositingStrategy = CompositingStrategy.Offscreen
}

渐变效果的遮罩

1. 定义渐变遮罩

由黑到透明的遮罩:

1
2
3
val mask = Brush.verticalGradient(
colors = listOf(Color.Black, Color.Transparent)
)

2. 应用 Blend Mode

1
2
3
4
5
6
7
8
9
10
11
12
Box(
modifier = Modifier
.fillMaxWidth()
.height(24.dp)
.graphicsLayer {
compositingStrategy = CompositingStrategy.Offscreen
}
.drawWithContent {
drawContent()
drawRect(mask, blendMode = BlendMode.DstIn)
}
)

DstIn keeps only the destination pixels (your content) where the source (the mask) is drawn, resulting in a fade.

3. 使之可复用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Composable
fun FadingEdgeBox(content: @Composable () -> Unit) {
Box {
content()
Box(
modifier = Modifier
.matchParentSize()
.graphicsLayer {
compositingStrategy = CompositingStrategy.Offscreen
}
.drawWithContent {
drawRect(
brush = Brush.verticalGradient(
colors = listOf(Color.Black, Color.Transparent)
),
blendMode = BlendMode.DstIn
)
}
)
}
}

Now you can pass a LazyColumn or any scrollable content, and it will automatically fade at the edge.

Clipping and Masking in Jetpack Compose by Victor Brandalise