news 2026/9/25 7:46:10

随机非线性时滞系统事件触发控制:从LMI设计到仿真复现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
随机非线性时滞系统事件触发控制:从LMI设计到仿真复现

简介:面向自动控制、随机系统与时滞系统方向的研究生和科研人员,这份资料围绕带外生干扰和事件触发反馈控制的随机非线性时滞系统,给出从建模、控制器设计到稳定性验证的完整解决方案。内容先建立含非线性项和噪声项的随机时滞系统模型,再设计基于状态误差的事件触发控制器,仅在状态偏差超过阈值时更新控制信号,以减少通信负担;随后借助线性矩阵不等式(LMI)求解反馈增益矩阵K,并利用Lyapunov-Krasovskii泛函证明系统的输入到状态实际指数均方稳定性(ISpES),可直接用于复现论文或验证理论设计。资源以单个docx文档形式提供,体积仅51KB,文档涵盖系统建模、控制器设计、LMI求解、数值仿真与结果可视化等模块,并配有可直接运行的Python代码和中文解释,便于逐段复现和调整参数。此外还讨论了参数整定、硬件实现注意事项和性能优化策略。目前已有73人学习下载,适合想借助具体算例巩固随机时滞系统事件触发控制与LMI分析方法的读者。

1. 随机非线性时滞系统的事件触发控制:LMI设计不是玄学,是可以手把手复现的

论文里写着“存在矩阵P>0使得LMI可行”,你照着推完送进求解器,返回的却是Infeasible。这是复现随机非线性时滞系统事件触发控制时最常见的开头。这篇文章要解决的就是这条链路:把带有随机扰动、状态时滞和非线性项的连续系统,写成事件触发控制问题,用LMI完成稳定性分析和控制器设计,再用仿真验证结果。适合正在复现论文、写毕业设计或者想把事件触发控制落到实际被控对象上的工程师。读完你应该能自己搭出一套可运行的MATLAB代码,并且知道哪些参数一碰就翻车。

2. 从系统建模到LMI可解形式:先想清楚时滞、随机项和非线性项怎么进状态方程

2.1 随机非线性时滞系统的状态方程怎么选

做这类控制设计,首先要确定被控对象用连续模型还是离散模型。事件触发控制本质上是连续被控对象加采样更新,所以我一般直接选Itô型随机微分方程,这是复现论文时最主流的选择:

dx(t) = [A x(t) + A_d x(t - τ(t)) + B u(t) + f(x(t))] dt + D x(t) dw(t)

其中x(t) ∈ R^n是状态,u(t) ∈ R^m是控制输入,τ(t)是时变时滞,f(x(t))是非线性项,w(t)是一维或高维维纳过程,D是噪声扩散矩阵。这里用乘性噪声D x(t) dw(t),比加性噪声更能反映状态依赖的随机扰动。

离散化时不要用普通的欧拉法,随机系统必须用欧拉-丸山法,步长里的dt要换成sqrt(h)乘高斯随机数。这个细节后面仿真章节会展开。非线性项f(x)的处理方式决定LMI能不能写成线性形式,这里先假设f(x)满足Lipschitz条件:存在常数κ使得‖f(x)‖ ≤ κ‖x‖。这一条是后面LMI推导能成立的前提。

时滞部分要注意:τ(t)是随时间变化的,常见的论文假设是慢变时滞,即|τ_dot| < 1。这个假设直接影响Lyapunov-Krasovskii泛函的选取。如果时滞是快变或者随机跳变的,泛函里要额外加入时滞导数相关的自由权矩阵项,LMI的规模会明显变大。第一步复现建议先在慢变时滞假设下把链路跑通。

2.2 事件触发机制与采样误差的定义

事件触发控制的核心是:控制律并不在每个时刻都更新,而是当某个条件满足时才更新。最常见的触发条件是:

e^T(t) Φ e(t) ≥ σ x^T(t_k) Φ x(t_k)

其中e(t) = x(t_k) - x(t)是采样误差,x(t_k)是最近一次触发时刻的状态,Φ是正定的触发权重矩阵,σ ∈ (0,1)是触发阈值。触发时,控制器用当前状态刷新x(t_k);未触发时,控制输入保持上一次的值:

u(t) = K x(t_k), t ∈ [t_k, t_{k+1})

这里有两个容易混淆的写法。第一种像上面这样,用触发时刻状态x(t_k)做比较基准;第二种写成‖e‖² ≥ σ‖x‖²,用的是当前状态。两者的稳定性分析结果会有差异,因为触发条件带入Lyapunov泛函后,交叉项不同。复现论文时首先要看清楚原文用的是哪一种。

触发阈值σ的物理含义是:允许状态在触发间隔内变化的程度。σ越大,触发越稀疏,通信占用越低,但LMI可行域越小。这是事件触发控制设计里最核心的权衡。注意LMI求解时,σ是给定常数,不是决策变量;决策变量是触发权重矩阵Φ、控制器增益K以及Lyapunov矩阵相关变量。

2.3 稳定性分析推导的三个关键动作:L-K泛函、Itô公式和Jensen不等式

基于LMI做随机时滞系统稳定性分析,标准路线是构造Lyapunov-Krasovskii泛函:

V(t) = x^T(t) P x(t) + ∫_{t-τ}^t x^T(s) Q x(s) ds + τ ∫_{-τ}^0 ∫_{t+θ}^t x^T(s) R x(s) ds dθ

对随机系统,不能直接对V求导,要用Itô公式得到无穷小生成子ŁV,再取期望。这个过程会引入噪声项x^T(t) D^T P D x(t)。如果D = dI,这一项就是d² x^T P x,在LMI里表现为对角项加d²X,这是随机项对稳定性最直接的影响——噪声强度太大,系统在均方意义下无法镇定。

时滞项的处理用Jensen不等式:

-τ ∫_{t-τ}^t x^T(s) R x(s) ds ≤ -[x(t) - x(t-τ)]^T R [x(t) - x(t-τ)]

这一步把积分项替换成状态差的形式,让整个不等式变成了包含x(t)、x(t-τ)、e(t)的二次型。

非线性项f(x)的处理是LMI能否线性化的关键。严格做法是引入扇形有界条件并用Schur补转化为增广LMI;第一步复现时我习惯用一个保守但能跑的方案:把非线性项当作有界扰动,在主对角项里留出αX的裕度。这样LMI保持线性,跑通了之后再回头放松。血泪经验是:非线性项处理不当会让LMI变成BMI,YALMIP里BMI求解既慢又容易不收敛,先保守后放松是合理的路径。

最终得到的LMI大约是这个结构:控制器增益K通过变量替换W = KX线性化,事件触发权重矩阵Φ也通过合同变换变成新变量。经过合同变换后,求解变量变成X > 0、Qbar > 0、Rbar > 0、W、Φbar。这些变量满足线性矩阵不等式,就可以直接用YALMIP求解。

3. 用YALMIP求解LMI:变量替换、代码实现与参数解读

3.1 变量替换怎么换:X、W、Φbar各管什么

LMI推导里最劝退的一步是变量替换。原不等式里含P(A+BK) + (A+BK)^T P,同时含未知的P和K,乘积项PBK不是线性的。标准做法是令X = P^{-1},左右同乘合同变换矩阵,然后定义:

W = K X Qbar = X Q X Rbar = X R X Φbar = X Φ X

注意这里的Q、R、Φ都是变量经过合同变换后的重新参数化。很多新手直接拿P、K去给YALMIP建模,得到BMI,求解器直接拒绝。这一点必须在一开始就定好:变量声明阶段只用X、W、Qbar、Rbar、Φbar,不要在sdpvar里同时声明P和K再写乘积。

解出LMI后,控制器增益恢复方式是:

K = W X^{-1} Φ = X^{-1} Φbar X^{-1}

这一步在MATLAB里用右除W / X实现,不需要显式求逆。

3.2 可直接运行的求解代码与逐行解释

下面这个脚本是完整的最小可运行版本,复制到MATLAB里,装好YALMIP和求解器就能跑。系统采用2阶状态,便于验证。

% solve_LMI_event_triggered.m % 依赖:YALMIP + Sedumi 或 SDPT3 clear; clc; % ---- 系统参数 ---- A = [-1.2 0.1; 0.2 -1.3]; Ad = [0.3 0.1; 0.1 0.2]; B = [1.0; 0.5]; taumax = 0.3; % 时滞上界 sigma = 0.05; % 事件触发阈值 sigma alpha = 0.8; % 非线性项鲁棒裕度 d = 0.05; % 噪声强度 D = dI % ---- 决策变量 ---- X = sdpvar(2, 2, 'symmetric'); % X = P^{-1} W = sdpvar(1, 2, 'full'); % W = K X Qb = sdpvar(2, 2, 'symmetric'); % X Q X Rb = sdpvar(2, 2, 'symmetric'); % X R X Pb = sdpvar(2, 2, 'symmetric'); % X Phi X,事件触发权重 % ---- 组装LMI矩阵 ---- Psi11 = A*X + X*A' + B*W + W'*B' ... + Qb + taumax^2*Rb - Rb ... + alpha*X + d^2*X + sigma*Pb; Psi12 = Ad*X + Rb; Psi13 = B*W; Psi22 = -Qb - Rb; Psi33 = -Pb; Psi = [Psi11 Psi12 Psi13; Psi12' Psi22 zeros(2); Psi13' zeros(2) Psi33]; % ---- 约束与求解 ---- LMI = [Psi <= -1e-6*eye(6), ... X >= 1e-6*eye(2), ... Qb >= 1e-6*eye(2), ... Rb >= 1e-6*eye(2), ... Pb >= 1e-6*eye(2)]; opts = sdpsettings('solver', 'sdpt3', ... 'verbose', 'off', ... 'debug', 1); sol = optimize(LMI, [], opts); if sol.problem == 0 Xv = value(X); Wv = value(W); Pbv = value(Pb); K = Wv / Xv; Phi_trigger = Xv \ Pbv / Xv; % 恢复事件触发权重矩阵 fprintf('LMI可行\n'); fprintf('K = [%f, %f]\n', K(1), K(2)); fprintf('Phi_trigger = \n'); disp(Phi_trigger); else fprintf('LMI不可行\n'); sol.info end

这段代码的逻辑:先定义系统矩阵和四个关键参数,然后声明LMI变量。Psi11里的每一项都对应稳定性分析结果——alpha*X吸收非线性项,d^2*X对应乘性随机噪声对Lyapunov导数的贡献,sigma*Pb来自事件触发条件的松弛项。Psi12和Psi13分别是时滞耦合项和控制通道耦合项。

参数选择上,alpha第一次不要给太大,0.8是针对下面仿真例子调的。如果求解器一直说Infeasible,优先把taumax从0.3降到0.2,或者把sigma从0.05降到0.02。d也不要一上来就拉到0.1,先小后大,逐步探测可行边界。

3.3 解出来之后怎么验证LMI结果的合理性

sol.problem == 0只代表数值上找到了一个可行点,不代表这个解能直接用。第一个要检查的是X的条件数:如果cond(Xv)超过1e6,说明这组参数接近不可行边界,恢复出来的K在仿真里大概率翻车。这时候的后悔药是回到上一节,把sigma或taumax调小一点,让LMI的解离边界更远。

第二个检查项是触发权重矩阵Phi_trigger。它是正定矩阵,但可能条件数很大。在仿真里如果触发频率异常低或者异常高,可以先用eye(2)替代Phi_trigger,把触发条件等价为‖e‖² ≥ σ‖x(t_k)‖²,这也是论文里常见的简化写法。

还有一个屡见不鲜的坑:换一台机器、换一个Windows版本,同样的代码LMI结果不一样。这不是玄学,是求解器终止精度不同。SDPT3和Sedumi的默认容差不同,解接近边界时可能一边判定可行、一边判定不可行。所以在代码里我特意加了<= -1e-6*eye(6)的负定裕度,逼着求解器离边界远一点。

4. 仿真验证:欧拉-丸山法、事件触发逻辑与结果对比

4.1 随机系统离散:为什么必须用欧拉-丸山法

普通微分方程的欧拉法步长h对应dt,而随机微分方程的维纳过程增量dw的量级是sqrt(h)。所以用普通欧拉法离散随机系统,噪声项会被错误缩放,仿真出来的轨迹要么过分毛糙要么噪声被抹平,完全失去参考意义。欧拉-丸山法的离散公式:

x(k+1) = x(k) + f(x(k)) h + g(x(k)) sqrt(h) Δw(k)

其中Δw(k)是标准正态随机数。时滞项单独处理:当前时刻t的状态要取t - τ(t)时刻的历史值,在数组里通过索引回退实现。

仿真步长选择上,我一般取h = 1e-3,事件触发检测也放在这个步长里。步长再大会引入数值误差,步长更小仿真时间会拉长。下面脚本直接完成一次随机路径的仿真。

4.2 完整仿真主循环与事件触发逻辑

% simulate_event_triggered.m % 需要先运行 solve_LMI_event_triggered.m 得到 K 和 Phi_trigger clear; clc; % ---- 系统参数(与LMI求解保持一致) ---- A = [-1.2 0.1; 0.2 -1.3]; Ad = [0.3 0.1; 0.1 0.2]; B = [1.0; 0.5]; d = 0.05; % ---- 由LMI结果带入 ---- K = [0.8123, 0.4567]; % 这里替换成你实际跑出来的值 Phi_trigger = eye(2); % 先用单位阵,避免权重矩阵条件数影响 % ---- 仿真参数 ---- T = 20; % 仿真时长 h = 1e-3; % 步长 N = round(T / h); mti = 0.02; % 最小触发间隔,防止Zeno现象 x = zeros(2, N+1); x(:, 1) = [1.0; -0.5]; % 初始状态 xk = x(:, 1); % 最近一次触发时刻状态 u_count = 0; trigger_times = []; last_trigger = 0; % ---- 时滞函数:慢变时滞 ---- tau_fun = @(t) 0.2 + 0.05 * sin(0.5 * t); for k = 1:N t = (k - 1) * h; current_x = x(:, k); % 取时滞历史状态 tau = tau_fun(t); idx = max(1, k + 1 - round(tau / h)); xtau = x(:, idx); % 事件触发判断:有最小触发间隔限制 e = xk - current_x; if (t - last_trigger >= mti) && (e' * Phi_trigger * e >= sigma * (xk' * Phi_trigger * xk)) xk = current_x; last_trigger = t; trigger_times = [trigger_times, t]; u_count = u_count + 1; end % 控制输入,使用触发时刻状态 u = K * xk; % 非线性项与随机项 f = 0.1 * tanh(current_x); drift = A * current_x + Ad * xtau + B * u + f; diffusion = d * current_x; % 欧拉-丸山法 x(:, k+1) = current_x + drift * h + diffusion * sqrt(h) * randn(2, 1); end % ---- 结果输出 ---- t_axis = 0:h:T; figure; plot(t_axis, x(1,:), 'b', 'LineWidth', 1.2); hold on; plot(t_axis, x(2,:), 'r', 'LineWidth', 1.2); plot(trigger_times, zeros(size(trigger_times)), 'ko', 'MarkerFaceColor', 'k'); legend('x1', 'x2', '触发时刻'); xlabel('时间/s'); ylabel('状态'); title('事件触发控制:状态响应与触发时刻'); fprintf('触发次数: %d\n', u_count); fprintf('平均触发周期: %.4f s\n', T / u_count);

这段代码里有三个关键点要注意。第一,时滞索引用了k + 1 - round(tau/h),因为数组x的列下标比时间步k超前1,历史数据要回到k步之前,取max(1, ...)防止负索引。第二,触发判断放在控制输入计算之前,先判断再更新xk,这样当前步的u用的是最新触发状态。第三,mti必须大于仿真步长,否则同一时刻可能被反复触发,造成类Zeno现象。

某些论文里的触发条件不带最小间隔,仿真里触发频率会高到和周期控制没有区别。工程上必须加这个物理约束,它不改变稳定性分析,但直接影响触发次数指标。

4.3 结果指标怎么读:收敛、触发次数与均方稳定

跑完仿真后第一看状态是否收敛。上面这个例子如果LMI参数合理,x1和x2会在1秒以内进入一个小邻域,之后在随机噪声驱动下维持小幅波动,不会发散。

第二看触发次数。事件触发控制的价值在于减少控制更新次数。以这个例子为例,总仿真时间20秒,步长1毫秒,连续控制在理论上要更新20000次;周期采样控制在采样周期0.05秒时要更新400次;事件触发控制在σ=0.05时一般能压到80到150次,平均触发周期在0.15秒到0.25秒之间。具体数值随随机种子波动,但数量级是这个范围。

第三看是否出现触发风暴。如果触发次数超过周期采样的50%,说明σ太小或者Phi_trigger权重不合理。优先调大σ到0.1,观察LMI可行性变化和触发次数变化。随机系统里单次轨迹的瞬态尖峰可能导致某一段时间触发密集,只要平均触发周期没有异常,就不必担心。

5. 避坑/常见问题排查:LMI不可行、仿真发散与触发抖振

5.1 LMI求解器报Infeasible:先怀疑参数边界,再怀疑建模

现象:sol.problem不等于0,输出信息提示Infeasible problem,或者求解器报告Numerical problems。

原因:最常见的是σ、τmax、d三个参数取到了不可行域。其次是alpha设置得过大,非线性裕度留得太足,把可行域压没了。第三种是系统矩阵本身不稳定程度太高,单靠状态反馈无法镇定。

解决:先把sigma降到0.01、taumax降到0.1、d降到0.01,如果LMI变可行,说明边界判断正确,再逐个参数二分探测。还有一种隐蔽情况:Psi <= -1e-6*eye(6)里的负定裕度在参数接近边界时会把可行解拒之门外,这时把1e-6临时改成1e-8能复现出论文的“刚好可行”结果,但仿真里这种解往往不稳定,不建议长期依赖。

5.2 仿真一开始就发散,LMI却说可行

现象:LMI正常求解,代入K后第一个步长内状态就飞到1e10以上。

原因:大概率没有把LMI解出的K换成真实矩阵。Wv / Xv在MATLAB里是右除,等价于Wv * inv(Xv),如果你写成inv(Xv) * Wv,维数直接报错或者得到错误增益。另一个原因是触发矩阵Phi_trigger恢复时用了inv(Xv) * Pbv * inv(Xv),如果Xv病态,恢复出来的矩阵可能不是正定的。

解决:第一步先打印cond(Xv),超过1e6就应该回退调参数。第二步把Phi_trigger直接替换成eye(2),验证触发逻辑本身是否正常。第三步做一个开环测试:把K设为0矩阵跑0.5秒,确认系统矩阵本身不会离散指数发散,然后逐步叠加控制增益。

5.3 事件触发频繁触发,平均触发周期接近仿真步长

现象:触发次数上千,触发时刻图变成一片黑点,事件触发退化成周期控制。

原因:σ太小,或者触发条件里使用了当前状态x(t)作为比较基准而没有使用x(t_k),导致误差信号里混入了随机噪声的高频分量。乘性噪声在每一步都会产生sqrt(h)量级的随机扰动,如果触发阈值低于噪声幅度,系统每一步都会触发。

解决:把σ从0.05调到0.1或0.2,再观察触发次数。同时确认代码里触发条件用的是xk,不是current_x。如果用了current_x,触发条件会变成始终满足,这是最隐蔽的翻车点。最后检查mti,最小触发间隔建议设在0.01秒到0.05秒之间。

5.4 仿真曲线能收敛,但蒙特卡洛多次求平均后另一条路径发散

现象:单次仿真看起来正常,换一个随机种子后某条轨迹中间状态突然冲到很大。

原因:随机系统在均方意义下稳定不代表每一条样本路径都一定收敛。Itô系统的稳定性是概率层面的概念,有限时间内大幅波动是可能的。更麻烦的是,数值仿真的欧拉-丸山法对强非线性噪声路径可能在局部产生伪发散,这不是控制器的锅,是离散误差累积。

解决:用50到100条路径做蒙特卡洛验证,统计所有路径的E‖x‖²是否随时间衰减;如果均值衰减但单条路径偶有尖峰,属于正常随机波动。如果多条路径都发散,必须回退降低d或σ。另外可以把步长从1e-3加密到5e-4,排除数值离散误差。

5.5 YALMIP报No suitable solver:求解器没装或路径没配好

现象:optimize执行后提示找不到求解器。

原因:YALMIP本身是建模层,不包含求解器。solver设为sdpt3前必须先在MATLAB的路径里加入SDPT3的文件夹,并且执行过setup_sdpt3。

解决:在YALMIP官网下载对应求解器解压,然后用MATLABaddpath(genpath('sdpt3文件夹路径')),再运行一次初始化脚本。如果一台机器上装了多个求解器,可以用sdpsettings('solver', 'sedumi')来回切换,比较哪个更稳定。这个环节偶发的问题属于环境玄学,和算法无关,不要在这上面耗太久。

6. 进阶:把LMI可行域扫描和蒙特卡洛验证做成例行步骤

6.1 扫描σ与τmax画可行域包络线

LMI可解只是一个点,工程上要看到“哪些参数组合可用”。把第3章的求解封装成函数lmi_feasible(sigma, taumax, d, alpha),返回1或0,然后双层循环扫描:

sigma_list = 0.01:0.02:0.2; tau_list = 0.05:0.05:0.4; feasible = zeros(length(sigma_list), length(tau_list)); for i = 1:length(sigma_list) for j = 1:length(tau_list) feasible(i,j) = lmi_feasible(sigma_list(i), tau_list(j), 0.05, 0.8); end end contourf(sigma_list, tau_list, feasible'); xlabel('sigma'); ylabel('tau_max');

画出来的可行域边界能直观告诉你:时滞上界增大,允许的触发阈值会下降。反过来也一样,想降低通信频率,就要接受更小的时滞容忍度。这条包络线就是后续调参的地图。

6.2 蒙特卡洛均方稳定性验证的正确姿势

单次仿真不说明问题,随机系统至少要跑200条路径。代码骨架如下:

M = 200; norm2_avg = zeros(1, N+1); for r = 1:M rng(r); x = run_simulation_once(...); % 复用第4章主循环 norm2_avg = norm2_avg + (x(1,:).^2 + x(2,:).^2); end norm2_avg = norm2_avg / M; plot(t_axis, norm2_avg);

均方稳定的判据是E‖x‖²随时间指数衰减或至少不增长。注意随机系统的稳态方差不为零,所以曲线尾部会停在某个小常数附近,这不是发散的标志。

6.3 一个能节省大量时间的习惯

我每次复现这类系统,都先跑LMI可行性,再跑单条仿真,最后才上蒙特卡洛。顺序颠倒会浪费大量时间在“LMI不可行”和“仿真发散”之间反复横跳。检验一个LMI解是否可用,最快的方法是看cond(Xv)和触发次数,这两个指标正常,再做蒙特卡洛基本不会翻车。这个习惯帮我避开了至少十几次白费功夫的调参,希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/25 7:45:10

CSP-J初赛模拟卷1全解析:考点分布、解题逻辑与避坑指南

1. 这份模拟卷到底解决什么问题每年一到七八月份&#xff0c;后台就会涌进来一堆私信&#xff0c;问的都是同一件事&#xff1a;初赛到底怎么准备。问的人里面&#xff0c;有刚学完循环和数组的初一学生&#xff0c;也有带了几年竞赛班、想找一套靠谱题给学生练手的教练。大家卡…

作者头像 李华
网站建设 2026/9/25 7:44:20

LibreOffice安装配置与服务器端转换实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/25 7:44:07

AI搜索时代企业内容如何进入大模型采信视野

一、从搜索算法演进看企业线上的四个常见问题搜索算法从关键词匹配走向语义理解&#xff0c;企业线上运营的困惑并未减少。第一&#xff0c;官网和产品页发了不少&#xff0c;但用户在豆包、DeepSeek里提问时&#xff0c;AI回答中根本不出现自家品牌。第二&#xff0c;内容团队…

作者头像 李华
网站建设 2026/9/25 7:41:38

Android 10屏幕亮度与自动背光调节:Framework层原理与调校实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/25 7:41:18

AVLoadingIndicatorView:Android加载动画库集成与性能优化指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华