Jump to content

Qt for HarmonyOS/user development guide/a11y zh

From Qt Wiki

开启 Qt 无障碍功能

目前 Qt 提供的 SDK 版本,默认是关闭鸿蒙平台下的无障碍功能的。如果需要使用或测试无障碍功能,需要开启相关选项。

需要注意的是,目前在HarmonyOS平台,只有Qt5支持无障碍。

修改工程模板,让 hap 携带无障碍开关

其中 launchWant 是 Ability 的启动参数,io.qt.experimental.enableA11ySupport 是 Qt OHOS 插件识别的无障碍开关。

QAbility.ets 中加入:

private enableQtAccessibilitySupport(): void {
  this.launchWant.parameters = this.launchWant.parameters ?? {};
  this.launchWant.parameters['io.qt.experimental.enableA11ySupport'] = true;
}

然后确保在 Qt 创建窗口之前调用它:

onWindowStageCreate(windowStage: Window.WindowStage) {
  this.enableQtAccessibilitySupport();
  qpa.handleAbilityOnWindowStageCreate(this, windowStage);
}

恢复窗口时也需要保留:

onWindowStageRestore(windowStage: Window.WindowStage): void {
  this.enableQtAccessibilitySupport();
  qpa.handleAbilityOnWindowStageRestore(this, windowStage);
}

命令行携带无障碍参数启动 hap 包

代码版本

分支 日期
tqtc/harmonyos-5.12.12 2025/7/7
tqtc/harmonyos-5.15.16 2025/7/16

启动方式

want 中添加启动参数 io.qt.experimental.enableA11ySupport

  • 命令行启动:
aa start -a QAbility -b com.ohos.ohosqttemplate --pb io.qt.experimental.enableA11ySupport true
  • DevEco 启动:在 launch 配置中添加 --pb io.qt.experimental.enableA11ySupport true

官方适配文档

https://doc.qt.io/archives/qt-5.15/accessible-qwidget.html

SwitchButton 继承自 QAbstractButton

这种继承自 QAbstractButton 的自定义控件,无需做特殊处理。

class SwitchButton : public QAbstractButton {
    Q_OBJECT
    Q_PROPERTY(double offset READ offset WRITE setOffset)
    Q_PROPERTY(QColor backgroundColor READ backgroundColor WRITE setBackgroundColor)
public:
    explicit SwitchButton(QWidget* parent = nullptr);
    ~SwitchButton() override;

    QSize sizeHint() const override;
    QSize minimumSizeHint() const override;

    void setCheckedColor(const QColor& color);
    void setUncheckedColor(const QColor& color);
    void setDisabledColor(const QColor& color);
    void setHandleColor(const QColor& color);
    void setAnimationDuration(int duration);

protected:
    void paintEvent(QPaintEvent* event) override;
    void resizeEvent(QResizeEvent* event) override;

private:
    double offset() const;
    void setOffset(double value);
    QColor backgroundColor() const;
    void setBackgroundColor(const QColor& color);

    double m_offset;
    QColor m_checkedColor;
    QColor m_uncheckedColor;
    QColor m_disabledColor;
    QColor m_handleColor;
    QColor m_backgroundColor;
    QPropertyAnimation* m_animation;
    int m_handleRadius;
    int m_margin;
};

SwitchButton2 继承自 QWidget

这种继承自 QWidget 的自定义控件,只能得到通用的无障碍描述(没有选中状态、也没有可执行动作),需要从 QAccessibleWidget 派生,或重新实现 QAccessibleActionInterface,并调用 QAccessible::installFactory 注册工厂方法。

鸿蒙的读屏服务主要依据以下三项来识别开关类控件,请确保都实现:

  • state().checkable —— 控件可勾选
  • state().checked —— 当前是否选中
  • actionNames() 中包含 toggleAction(),并在 doAction() 中响应

SwitchButton2 头文件如下:

class SwitchButton2 : public QWidget {
    Q_OBJECT
    Q_PROPERTY(double offset READ offset WRITE setOffset)
    Q_PROPERTY(bool checkable READ isCheckable WRITE setCheckable)
public:
    explicit SwitchButton2(QWidget* parent = nullptr);
    ~SwitchButton2() override = default;

    void setCheckable(bool);
    bool isCheckable() const;

    // 设置颜色
    void setCheckedColor(const QColor& color);
    void setUncheckedColor(const QColor& color);
    void setDisabledCheckedColor(const QColor& color);
    void setDisabledUncheckedColor(const QColor& color);
    void setHandleColor(const QColor& color);
    void setDisabledHandleColor(const QColor& color);

    // 获取状态
    bool isChecked() const;

    // 设置动画时间
    void setAnimationDuration(int duration);

signals:
    void toggled(bool checked);

public slots:
    void setChecked(bool checked);
    void toggle();

protected:
    void paintEvent(QPaintEvent* event) override;
    void mousePressEvent(QMouseEvent* event) override;
    void mouseReleaseEvent(QMouseEvent* event) override;
    void resizeEvent(QResizeEvent* event) override;
    QSize sizeHint() const override;
    QSize minimumSizeHint() const override;
    double offset() const;
    void setOffset(double value);

private:
    bool m_checkable { true };
    bool m_checked { false };
    double m_offset { 0 };
    double m_radius { 0 };
    QColor m_checkedColor { Qt::green };
    QColor m_uncheckedColor { Qt::gray };
    QColor m_disabledCheckedColor { Qt::darkGreen };
    QColor m_disabledUncheckedColor { Qt::lightGray };
    QColor m_handleColor { Qt::white };
    QColor m_disabledHandleColor { Qt::gray };
    QPropertyAnimation* m_animation { nullptr };
    bool m_pressed { false };
};

main.cpp

#include <QAccessible>
#include <QApplication>
#include <QtWidgets/qaccessiblewidget.h>

#include "mainwindow.h"
#include "switchbutton2.h"

class AccessibleSwitchButton : public QAccessibleWidget {
public:
    AccessibleSwitchButton(QWidget* w)
        : QAccessibleWidget(w)
    {
        Q_ASSERT(button());
        if (button()->isCheckable())
            addControllingSignal(QLatin1String("toggled(bool)"));
        else
            addControllingSignal(QLatin1String("clicked()"));
    }

    QAccessible::State state() const override
    {
        QAccessible::State state = QAccessibleWidget::state();
        SwitchButton2* b = button();
        if (b->isCheckable())
            state.checkable = true;
        if (b->isChecked())
            state.checked = true;
        return state;
    }

    QAccessible::Role role() const override
    {
        return QAccessible::Role::CheckBox;
    }

    QStringList actionNames() const override
    {
        QStringList names;
        if (widget()->isEnabled()) {
            names << toggleAction();
        }
        names << QAccessibleWidget::actionNames();
        return names;
    }

    void doAction(const QString& actionName) override
    {
        if (actionName == toggleAction()) {
            button()->toggle();
        } else {
            QAccessibleWidget::doAction(actionName);
        }
    }

protected:
    SwitchButton2* button() const
    {
        return qobject_cast<SwitchButton2*>(object());
    }
};

QAccessibleInterface* customWidgetFactory(const QString& classname, QObject* object)
{
    QAccessibleInterface* interface = 0;
    if (classname == QLatin1String("SwitchButton2") && object && object->isWidgetType())
        interface = new AccessibleSwitchButton(static_cast<QWidget*>(object));
    return interface;
}

int main(int argc, char* argv[])
{
    QApplication a(argc, argv);
    QAccessible::installFactory(customWidgetFactory);
    MainWindow w;
    w.show();
    return a.exec();
}

复用 simplewidgets_p.h

Qt 默认对基础控件做了无障碍支持。如果自行实现的控件虽然继承自 QWidget,但实际行为和 Button、Label 等基础控件类似,也可以直接复用 Qt 实现的无障碍控件。

但是由于 Qt 未导出这些控件,如果要复用,需要自行修改 simplewidgets_p.h 代码,将要复用的控件导出(在头文件里添加 Q_WIDGETS_EXPORTQ_DECL_EXPORT 并重新编译)。

例如 QAccessibleButton(注意它位于 #if QT_CONFIG(abstractbutton) 条件编译块内,导出时不要破坏该结构):

class Q_WIDGETS_EXPORT QAccessibleButton : public QAccessibleWidget
{
    Q_DECLARE_TR_FUNCTIONS(QAccessibleButton)
public:
    QAccessibleButton(QWidget *w);

    QString text(QAccessible::Text t) const override;
    QAccessible::State state() const override;
    QRect rect() const override;
    QAccessible::Role role() const override;

    QStringList actionNames() const override;
    void doAction(const QString &actionName) override;
    QStringList keyBindingsForAction(const QString &actionName) const override;

protected:
    QAbstractButton *button() const;
};

默认支持的无障碍控件

Qt 默认实现的支持无障碍的常用控件,可以自行查看 src\widgets\accessible\qaccessiblewidgetfactory.cpp

Qt 控件 对应无障碍控件
QLineEdit QAccessibleLineEdit(QSpinBox 内部的行编辑器除外)
QTextEdit QAccessibleTextEdit
QPlainTextEdit QAccessiblePlainTextEdit
QComboBox QAccessibleComboBox
QAbstractSpinBox QAccessibleAbstractSpinBox
QSpinBox QAccessibleSpinBox
QDoubleSpinBox QAccessibleDoubleSpinBox
QScrollBar QAccessibleScrollBar
QAbstractSlider QAccessibleAbstractSlider
QSlider QAccessibleSlider
QToolButton QAccessibleToolButton
QCheckBox / QRadioButton / QPushButton / QAbstractButton QAccessibleButton
QDialog / QMessageBox / QToolBar / QFrame QAccessibleWidget
QMainWindow QAccessibleMainWindow
QLabel / QLCDNumber / QStatusBar / QTipLabel QAccessibleDisplay
QProgressBar QAccessibleProgressBar
QMenuBar QAccessibleMenuBar
QMenu QAccessibleMenu
QTreeView QAccessibleTree
QTableView / QListView QAccessibleTable
QTabBar QAccessibleTabBar
QStackedWidget QAccessibleStackedWidget
QGroupBox QAccessibleGroupBox
QToolBox QAccessibleToolBox
QDialogButtonBox QAccessibleDialogButtonBox
QDial QAccessibleDial
QTextBrowser QAccessibleTextBrowser
QAbstractScrollArea QAccessibleAbstractScrollArea
QScrollArea QAccessibleScrollArea
QCalendarWidget QAccessibleCalendarWidget
QDockWidget QAccessibleDockWidget
QMdiArea QAccessibleMdiArea
QMdiSubWindow QAccessibleMdiSubWindow
QSplitter / QSizeGrip / QSplitterHandle / QRubberBand QAccessibleWidget
QWidget QAccessibleWidget

注:如果自定义控件继承自上表中的某个 Qt 控件,会自动沿基类链匹配到对应的无障碍实现,无需额外处理。

第三方 Qt 控件库

需要查阅三方控件库的相关文档。