Toolbar的简单使用和封装

本文介绍了一种自定义Toolbar的方法,通过简单的封装实现更灵活的头部布局显示,支持ActionBar特性,并展示了如何在BaseActivity中集成及具体Activity中的使用案例。

因为Actionbar的扩展性比较弱,所以一般都是自定义一个titlebar,但是现在有了toolbar,完全可以解决任何头布局的显示。

简单封装后的效果是这样的:


代码:

public class MyToolbar {
    private Toolbar           mToolbar;
    private AppCompatActivity mActivity;
    private ActionBar         mActionBar;
    private RelativeLayout    rlCustom;

    public MyToolbar(Toolbar mToolbar, final AppCompatActivity mActivity) {
        this.mToolbar = mToolbar;
        this.mActivity = mActivity;
        rlCustom = (RelativeLayout) mToolbar.findViewById(R.id.rlCustom);
        //支持ActionBar
        mActivity.setSupportActionBar(mToolbar);
        mActionBar = mActivity.getSupportActionBar();
        mActionBar.setDisplayHomeAsUpEnabled(true);
        mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mActivity.finish();
            }
        });
    }

    //设置主标题
    public MyToolbar setTitle(String title) {
        mActionBar.setTitle(title);
        return MyToolbar.this;
    }

    //设置副标题
    public MyToolbar setSubTitle(String subTitle) {
        mActionBar.setSubtitle(subTitle);
        return MyToolbar.this;
    }

    public MyToolbar setTitle(int resId) {
        mActionBar.setTitle(resId);
        return MyToolbar.this;
    }

    public MyToolbar setSubTitle(int resId) {
        mActionBar.setSubtitle(resId);
        return MyToolbar.this;
    }

    //设置导航栏图标的点击监听
    public MyToolbar setNavigationOnClickListener(View.OnClickListener listener) {
        mToolbar.setOnClickListener(listener);

        return MyToolbar.this;
    }

    //设置用于工具栏导航按钮的图标
    public MyToolbar setNavigationIcon(int resId) {
        mToolbar.setNavigationIcon(resId);
        return MyToolbar.this;
    }

    // 给左上角图标的左边加上一个返回的图标
    public MyToolbar setDisplayHomeAsUpEnabled(boolean isShow) {
        mActionBar.setDisplayHomeAsUpEnabled(isShow);
        return MyToolbar.this;
    }

    //自定义toolbar的布局
    public MyToolbar setCustomView(View view) {
        rlCustom.removeAllViews();
        rlCustom.addView(view);
        return MyToolbar.this;
    }

    //自定义toolbar的布局
    public MyToolbar setCustomView(int resId) {
        View view = mActivity.getLayoutInflater().inflate(resId, null);
        rlCustom.removeAllViews();
        rlCustom.addView(view);
        return MyToolbar.this;
    }
}
BaseActivity

public abstract class BaseActivity extends AppCompatActivity {

    private RelativeLayout rlContent;
    private Toolbar        toolBar;
    private MyToolbar      mMyToolbar;

    @Override
    public void startActivity(Intent intent) {
        super.startActivity(intent);
        overridePendingTransition(R.anim.anim_in_right_left, R.anim.anim_out_right_left);
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_base_layout);
        initView();
        View view = getLayoutInflater().inflate(getLayoutId(), null, false);
        rlContent.addView(view);
        mMyToolbar = new MyToolbar(toolBar, this);
    }

    private void initView() {
        rlContent = (RelativeLayout) findViewById(R.id.rlContent);
        toolBar = (Toolbar) findViewById(R.id.toolBar);
    }

    public abstract int getLayoutId();

    public MyToolbar getToolbar() {
        if (null == mMyToolbar) {
            mMyToolbar = new MyToolbar(toolBar, this);
        }
        return mMyToolbar;
    }

    @Override
    public void finish() {
        super.finish();
        overridePendingTransition(R.anim.anim_in_left_right, R.anim.anim_out_left_right);
    }
}

MainAncitity

public class MainActivity extends BaseActivity {
    private MyToolbar mMyToolbar;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mMyToolbar = getToolbar();
        mMyToolbar.setTitle("主标题").setSubTitle("副标题");
    }

    @Override
    public int getLayoutId() {
        return R.layout.activity_main;
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        return super.onOptionsItemSelected(item);
    }

    public void first(View view) {
        startActivity(new Intent(this, SecondActivity.class));
    }
}
SecondActivity

public class SecondActivity extends BaseActivity {
    private MyToolbar mMyToolbar;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mMyToolbar = getToolbar();
        mMyToolbar.setCustomView(R.layout.layout_custom_toolbar);
    }

    @Override
    public int getLayoutId() {
        return R.layout.activity_second;
    }
}
BaseActivity布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:orientation="vertical">

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolBar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="@color/colorPrimary"
        >

        <RelativeLayout
            android:id="@+id/rlCustom"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            >
        </RelativeLayout>
    </android.support.v7.widget.Toolbar>

    <RelativeLayout
        android:id="@+id/rlContent"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

    </RelativeLayout>

</LinearLayout>

menu布局:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:app="http://schemas.android.com/apk/res-auto">
    <item android:id="@+id/action_search"
          android:title="搜索"
          android:orderInCategory="80"
          android:icon="@android:drawable/ic_menu_search"
          app:showAsAction="ifRoom" />

    <item android:id="@+id/action_share"
          android:title="分享"
          android:orderInCategory="90"
          android:icon="@android:drawable/ic_menu_share"
          app:showAsAction="ifRoom" />

    <item android:id="@+id/action_settings"
          android:title="设置"
          android:orderInCategory="100"
          app:showAsAction="never"/>
</menu>
anim_in_right_left:  
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
     android:duration="300">
    <translate
        android:fromXDelta="100%p"
        android:toXDelta="0"/>
</set>
anim_out_right_left: 
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
     android:duration="300">
    <translate
        android:fromXDelta="0"
        android:toXDelta="-100%p"/>
</set>
anim_in_left_right: 
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
     android:duration="300">
    <translate
        android:fromXDelta="-100%p"
        android:toXDelta="0"/>
</set>
anim_out_left_right: 
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
     android:duration="300">
    <translate
        android:fromXDelta="0"
        android:toXDelta="100%p"/>
</set>

<think>我们参考用户提供的引用资料,特别是引用[3]中关于ToolBar的示例,来设计一个封装Toolbar组件。 目标:封装一个可复用的Toolbar组件,它应该支持以下功能: 1. 可以放置多个工具按钮(ToolButton)或其他控件。 2. 可以设置工具栏的位置(顶部、底部等)。 3. 可以自定义工具栏的背景、按钮样式等。 4. 支持动态添加移除按钮。 设计思路: 我们将创建一个自定义组件,命名为`MyToolBar`,它内部使用QML的`ToolBar`作为基础,并利用`RowLayout`来水平排列按钮。 同时,我们将暴露一个接口(如属性或方法)来允许外部动态添加按钮。 步骤: 1. 创建一个新的QML文件,例如`MyToolBar.qml`。 2. 在文件中,我们使用`ToolBar`作为根元素。 3. 在`ToolBar`内部,使用`RowLayout`来布局子元素,并设置`anchors.fill: parent`使布局填满工具栏。 4. 为了支持动态添加按钮,我们可以使用`Repeater`或通过动态对象创建。这里我们选择使用`Repeater`,并绑定一个模型(model)来生成按钮。 5. 我们将定义一个属性(例如`buttonModel`),用于存储按钮的数据(如文本、图标、点击事件等)。 但是,考虑到按钮可能有不同的类型(比如ToolButton,也可能有Label或其他控件),我们可以设计一个更通用的方式:允许添加任意组件。 然而,为了简化,我们先设计一个只支持ToolButton的工具栏,并可以设置按钮的文本点击事件。 另一种更灵活的方式是:将工具栏的内容设计为子元素,即允许在自定义工具栏标签内添加子元素(类似于默认ToolBar的用法)。这样,我们可以在使用该组件时自由添加子控件。 我们参考引用[3]中的例子,它就是在ToolBar内部使用RowLayout,然后在RowLayout中添加子控件(ToolButton、Label等)。 因此,我们可以这样封装:我们的`MyToolBar`组件允许用户在其内部直接添加子元素,就像在普通的ToolBar中添加一样。 但是,如果我们要封装一个具有固定样式的工具栏,并且希望统一风格,我们可以提供一些预定义的属性来设置。 这里我们提供两种封装方式: 方式一:允许自由添加子控件(类似于默认ToolBar,但固定一些样式) 方式二:通过模型动态生成按钮(适用于按钮固定的情况) 由于用户需求是封装,我们可以同时提供两种方式,但为了简单,我们先实现方式一(自由添加子控件)并添加一些自定义属性。 设计`MyToolBar.qml`: ``` import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.15 ToolBar { id: root // 自定义属性:设置工具栏的位置,默认为顶部 property alias position: root.position // 自定义背景颜色,默认为系统样式 property color backgroundColor: "lightgray" background: Rectangle { color: backgroundColor } // 使用RowLayout作为内容布局 RowLayout { anchors.fill: parent // 通过children属性,外部可以在MyToolBar中添加子元素 } } ``` 这样封装后,在使用时,我们可以在MyToolBar内部添加任意控件,例如: ``` MyToolBar { width: parent.width backgroundColor: "lightblue" ToolButton { text: "Back" } Label { text: "Title"; Layout.fillWidth: true } ToolButton { text: "Menu" } } ``` 但是,这样封装并没有提供动态添加按钮的模型方式。如果用户需要模型方式,我们可以再创建一个组件,例如`MyToolBarWithModel`,但用户的需求是封装Toolbar,上述方式已经足够。 然而,引用[4]中展示了一个分页组件的封装,其中使用了Repeater来生成按钮。我们可以借鉴这种方式,为工具栏提供另一种封装:通过模型动态生成按钮。 设计`MyToolBarWithModel.qml`: ``` import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.15 ToolBar { id: root property alias position: root.position property color backgroundColor: "lightgray" // 定义按钮模型,可以是数组,每个元素包含按钮的属性(如text, icon, action等) property list<QtObject> buttonModel background: Rectangle { color: backgroundColor } RowLayout { anchors.fill: parent // 使用Repeater根据模型生成按钮 Repeater { model: root.buttonModel delegate: ToolButton { text: model.text icon.source: model.iconSource onClicked: { if (model.action) { model.action() } } } } } } ``` 注意:上面的`buttonModel`属性是一个列表,每个元素应该是一个具有`text`、`iconSource``action`属性的对象。我们可以使用`QtObject`来创建,但更常用的是使用`ListModel``ListElement`。 使用示例: ``` MyToolBarWithModel { buttonModel: [ QtObject { property string text: "Back" property string iconSource: "" property var action: function() { console.log("Back clicked"); } }, QtObject { property string text: "Forward" property string iconSource: "" property var action: function() { console.log("Forward clicked"); } } ] } ``` 但是,这种模型方式在QML中定义比较繁琐,而且不能混合其他类型的控件(如Label)。所以,我们更推荐第一种方式(自由添加子控件)作为通用工具栏的封装。 因此,我们主要提供第一种封装,并说明使用方式。 另外,我们还可以添加一些常用的快捷方式,比如在工具栏中间添加一个标题,我们可以提供一个`title`属性,并自动在中间添加一个Label。 增强版`MyToolBar.qml`(可选功能): 我们可以提供一些额外的属性来快速设置标题左右两边的按钮,但这样会限制布局。因此,我们保持简单,让用户自由布局。 总结:我们封装了一个简单的工具栏组件,它允许自由添加子控件,并可以设置背景颜色位置。 示例使用: 1. 在需要的地方引入组件: 2. 使用MyToolBar,并在其中添加子控件,使用RowLayout进行布局。 参考引用[3]中的例子,我们将其改写为使用我们的MyToolBar组件: ``` Window { id: root visible: true width: 640 height: 480 title: qsTr("Menu") color: "lightgray" MyToolBar { width: parent.width backgroundColor: "lightblue" RowLayout { anchors.fill: parent ToolButton { text: qsTr("‹") onClicked: stack.pop() } Label { text: "Two" horizontalAlignment: Qt.AlignHCenter verticalAlignment: Qt.AlignVCenter Layout.fillWidth: true } ToolButton { id: fileButton text: qsTr("⋮") onClicked: menu.open() } } } Menu { id: menu x: fileButton.x y: fileButton.y Action { text: "Cut" } Action { text: "Copy" } Action { text: "Paste" } } StackView { id: stack anchors.fill: parent } } ``` 这样,我们就封装了一个基本的工具栏组件,并且可以复用。 注意:上面的例子中,我们在MyToolBar内部又写了一个RowLayout,这是因为在MyToolBar.qml中已经有一个RowLayout,而外部添加的子元素需要放在这个RowLayout中。但是,我们上面的封装中,MyToolBar的RowLayout已经存在,外部再添加一个RowLayout作为子元素,这样就会有两个RowLayout嵌套,导致布局可能不符合预期。 因此,我们需要修改封装:在MyToolBar中,我们不应该使用RowLayout作为唯一的子元素,而是应该允许外部直接添加多个子元素,并且这些子元素会自动排列在RowLayout中。 修正:我们使用`RowLayout`作为根元素,而`ToolBar`本身是可以直接包含子元素的,但默认不会自动排列。所以,我们使用`contentItem`属性来设置内容项。 修正后的`MyToolBar.qml`: ``` import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.15 ToolBar { id: root property alias position: root.position property color backgroundColor: "lightgray" background: Rectangle { color: backgroundColor } // 设置内容项为RowLayout,这样外部添加的子元素都会在这个RowLayout中 contentItem: RowLayout { // 不需要anchors.fill,因为ToolBar会自动设置contentItem的大小 // 但是,我们希望填满,所以: anchors.fill: parent // 这样,外部添加的子元素就会成为这个RowLayout的子元素 } } ``` 但是,这样封装后,外部使用MyToolBar时,添加的子元素会自动成为contentItem(即RowLayout)的子元素。所以,使用方式如下: ``` MyToolBar { width: parent.width backgroundColor: "lightblue" // 直接添加子元素,这些子元素会被自动添加到内部的RowLayout中 ToolButton { text: qsTr("‹") onClicked: stack.pop() } Label { text: "Two" horizontalAlignment: Qt.AlignHCenter verticalAlignment: Qt.AlignVCenter Layout.fillWidth: true } ToolButton { id: fileButton text: qsTr("⋮") onClicked: menu.open() } } ``` 这样,使用起来就非常直观了。 但是,我们需要注意:在QML中,如果我们在一个组件内部直接写子元素,那么这些子元素会被添加到该组件的默认子元素列表中(即children属性)。而我们这里重写了contentItem,所以这些子元素会被添加到contentItem(即RowLayout)中吗? 实际上,在ToolBar中,contentItem是负责显示工具栏内容的项目。当我们重写contentItem时,我们设置的RowLayout会成为ToolBar的内容项。然后,当我们在MyToolBar中直接添加子元素时,这些子元素会被添加到ToolBar的children中,而不是contentItem的children中。所以,我们需要将子元素重新parent到contentItem中。 因此,我们需要在组件完成时,将子元素重新设置parent到contentItem中。但是这样比较复杂。 另一种做法:我们不重写contentItem,而是使用默认的contentItem(它是一个Item),然后我们在其中放置一个RowLayout,并将所有子元素都添加到这个RowLayout中。但是,这样外部添加的子元素还是会被添加到ToolBar的children中。 所以,我们改变思路:在MyToolBar中,我们使用默认的contentItem,然后我们要求用户将子元素放在一个RowLayout中,作为MyToolBar的唯一子元素。但这样使用起来不太方便。 参考ToolBar的默认行为:ToolBar的默认contentItem是一个Item,它不会自动排列子元素。所以,我们通常需要在ToolBar内部显式使用一个布局(如RowLayout)。 因此,我们回到最初的设计:不强制要求,让用户在使用MyToolBar时,自己添加一个RowLayout作为子元素,并在其中放置按钮。这样虽然多了一层嵌套,但是最灵活且符合QML的习惯。 所以,我们采用最开始的封装(不重写contentItem),并修改文档,要求用户在使用时内部添加一个布局(如RowLayout)来排列子元素。 最终,我们提供的`MyToolBar.qml`如下: ``` import QtQuick 2.15 import QtQuick.Controls 2.15 ToolBar { id: root // 自定义属性:设置工具栏的位置,默认为顶部 property alias position: root.position // 自定义背景颜色,默认为系统样式 property color backgroundColor: "lightgray" background: Rectangle { color: backgroundColor } } ``` 然后,使用方式就是: ``` MyToolBar { width: parent.width backgroundColor: "lightblue" RowLayout { anchors.fill: parent ToolButton { ... } Label { ... Layout.fillWidth: true ... } ToolButton { ... } } } ``` 这样,用户可以在RowLayout中自由添加任意控件,并且布局由RowLayout管理。 虽然多了一层嵌套,但这样封装的好处是: 1. 保持了灵活性,用户可以使用任何布局(比如Row、RowLayout等)。 2. 可以添加任意类型的子控件。 因此,我们推荐这种封装方式。 回答用户问题: 在QML中封装Toolbar组件,我们可以创建一个自定义组件(如MyToolBar.qml),它继承自ToolBar,并添加自定义背景颜色等属性。在使用时,在组件内部添加一个布局(如RowLayout)并在布局中添加具体的控件(如ToolButton、Label等)。 示例代码已在上文给出。 相关问题: 1. 如何在QML中动态添加工具栏按钮? 2. 如何自定义工具栏按钮的样式? 3. 工具栏如何响应不同屏幕尺寸?</think>在QML中封装Toolbar组件可以通过创建自定义组件实现,结合`ToolBar`、`RowLayout``ToolButton`等元素。以下是详细封装方法示例: ### 封装步骤 1. **创建基础结构** 新建`MyToolBar.qml`文件,继承Qt Quick Controls的`ToolBar`: ```qml import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.15 ToolBar { id: root property alias backgroundColor: bg.color property list<QtObject> buttonModel // 动态按钮数据模型 background: Rectangle { id: bg color: "lightgray" // 默认背景色 } RowLayout { anchors.fill: parent spacing: 5 Repeater { model: root.buttonModel delegate: ToolButton { text: model.text icon.source: model.iconSource onClicked: model.action() } } } } ``` 2. **实现动态按钮** 通过`buttonModel`属性支持动态生成按钮: ```qml // 使用示例 MyToolBar { width: parent.width backgroundColor: "#3498db" buttonModel: [ QtObject { property string text: "保存" property string iconSource: "save.png" property var action: function() { console.log("保存操作") } }, QtObject { property string text: "撤销" property var action: function() { undoManager.undo() } } ] } ``` 3. **添加标题功能(可选)** 扩展标题居中功能: ```qml // MyToolBar.qml 新增代码 property string title: "" Label { text: root.title Layout.fillWidth: true horizontalAlignment: Qt.AlignHCenter visible: text !== "" } ``` ### 完整使用示例 ```qml import QtQuick 2.15 import QtQuick.Window 2.15 Window { width: 600; height: 400 visible: true MyToolBar { width: parent.width title: "文档编辑器" backgroundColor: "#2c3e50" buttonModel: [ QtObject { property string text: "新建" property var action: function() { fileManager.create() } }, QtObject { property string text: "打开" property string iconSource: "open.svg" property var action: function() { fileDialog.open() } } ] } } ``` ### 关键特性说明 1. **布局控制** - 使用`RowLayout`自动排列按钮 - `spacing`属性控制按钮间距 - `Layout.fillWidth`支持标题居中[^3] 2. **动态模型** - 通过`Repeater`+`buttonModel`动态生成按钮 - 支持文本/图标/点击事件的配置 3. **样式定制** - `background`属性修改工具栏颜色 - 可扩展添加边框/阴影等效果[^2] 4. **交互集成** - 按钮点击事件绑定业务逻辑 - 支持与`StackView`/`SwipeView`导航联动[^3] ### 最佳实践建议 1. **响应式设计** 添加`states`属性适配移动端横竖屏: ```qml states: State { when: Screen.width < 600 PropertyChanges { target: root; height: 80 // 移动端加大高度 } } ``` 2. **按钮封装** 创建`MyToolButton.qml`统一按钮样式: ```qml ToolButton { property color btnColor: "white" background: Rectangle { radius: 4 color: parent.down ? Qt.darker(btnColor) : btnColor } } ``` --- ### 相关问题 1. **如何实现工具栏的折叠菜单功能?** 参考`Menu`组件与`ToolButton`的联动实现[^3],可结合`visible`属性控制折叠项。 2. **怎样保存工具栏的状态(如按钮位置)?** 使用`Settings`模块持久化`Layout.preferredWidth`等属性: ```qml Component.onDestruction: Settings.setValue("toolbarState", JSON.stringify(buttonPositions)) ``` 3. **如何实现工具栏拖动排序?** 需结合`DragHandler``Repeater`模型更新: ```qml ToolButton { DragHandler { onActiveChanged: if (!active) root.reorderButtons() } } ``` 4. **工具栏如何响应系统主题变化?** 通过`Palette`对象绑定系统色: ```qml background: Rectangle { color: root.palette.window } ``` [^1]: Qt官方文档明确区分了TabBar(选项卡容器)ToolBar(操作命令容器)的设计用途 [^2]: Label组件支持文本样式定制,ToolButton支持图标/文本组合 [^3]: 工具栏通常与导航组件(StackView/SwipeView)联动实现页面切换 [^4]: 动态组件需考虑模型更新布局重计算
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SunnyRivers

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值