新年过后刚上班,略感无聊,然后手里玩着苹果的测试机,突然觉得苹果的开关按钮效果很惹人,遂打算实现一个。

苹果开关按钮效果

我随便从网上找了一个苹果按钮的gif图,嗯,这个就是我想要实现的效果。

首先,从外观效果来分析一下吧。

  • 底板是椭圆的,并且左右两端是半个圆。
  • 按钮是圆形的。
  • 关闭状态(按钮在左侧)的时候,底板是白色的,并有灰色的边框。
  • 打开状态(按钮在右侧)的时候,底板是绿色的,看不出有没有边框。
  • 在关闭状态,按下按钮或拖动按钮到右侧,变为打开状态。
  • 在打开状态,按下按钮或拖动按钮到左侧,变为关闭状态。
  • 按下按钮不释放(pressed && !release)的时候,按钮会横向变肥一下,释放后,变回最初的宽度。
  • 按下按钮不释放的时候,底板的椭圆会从边框按照边缘的椭圆形慢慢吞噬成灰色。
  • 如果边按下不释放,边快速拖动按钮,被拖动过的左侧区域不会显示吞噬的效果。
  • 按钮的圆形外侧有阴影。

大概观察了下,按钮好像差不多就这些属性。

恩,QtQuick 2.4里,我记得好像是已经新添加了一个类似开关按钮的控件,本来是打算用那个控件重新设计style实现,不过后来我找了半天也没找到那个控件,忘记叫啥名字了。(已经记起名字叫做Switch。)于是,最后决定还是自己完全重新用Rectangle画一个开关按钮粗来。

根据上面分析的结果,可以这样设计这个组件:整个组件分为3层,底下的背景层、上面的圆按钮、还有在背景层和按钮之间要有一层遮照层。背景、和按钮很好理解,至于为什么还要有一层遮照层呢,主要是因为按下按钮并快速拖动的时候,那个背景吞噬的效果并不是所有控件覆盖的,而是只是在未被拖过的背景才显示,=。=我是实在没啥好的解决办法了,所以才在加上了一层遮照层。

恩,感觉整个设计里面没有啥太大的难度,哦,就是有一点,不是在按下的过程中有吞噬的效果么,本来想可能需要再放一个Rectangle实现。后来发现,其实我可以用背景矩形的边框做文章。于是,当按钮被按下的时候,背景矩形的边框越來越大越来越大,然后吞噬了整个面板~~嘿嘿

同样,在实现的时候,我还是将控件分为了control和style部分,所有行为的处理都在control里,而样式效果放在了style里。

代码如下:

这是control的代码

//RadioButton.qml

import QtQuick 2.4
import QtQuick.Controls 1.2
import QtQuick.Controls.Private 1.0

Control {
    id:root

    implicitWidth: 140
    implicitHeight: 80

    style: Qt.createComponent("RadioButtonStyle.qml", root)

    /*! 是否被选中 */
    property bool checked: false
    /*! 是否被按下,按下并不一定选中 */
    readonly property bool pressed: mousearea.pressed || upperma.pressed

    MouseArea {
        id: mousearea
        anchors.fill: parent
        onClicked: root.checked = !root.checked
    }

    Loader {
        id: maskloader
        property Component __mask //上层按钮
        anchors.left: parent.left
        anchors.right: parent.right
        anchors.leftMargin: __style && __style.bkBorderWidth ? __style.bkBorderWidth : 0
        anchors.rightMargin: parent.width-upperloader.x-upperloader.width
        anchors.verticalCenter: parent.verticalCenter
        sourceComponent: __style && __style.mask ? __style.mask : __mask
    }

    Loader {
        id: upperloader
        property Component __up //上层按钮
        property real bkBorderWidth: __style && __style.bkBorderWidth ? __style.bkBorderWidth : 0
        anchors.verticalCenter: parent.verticalCenter
        sourceComponent: __style && __style.upper ? __style.upper : __up
        x: root.checked ? root.width-width-bkBorderWidth : bkBorderWidth

        onXChanged: {
            if (x===root.width-width-bkBorderWidth) {
                root.checked = true
            }else if (x===bkBorderWidth) {
                root.checked = false
            }
        }

        MouseArea {
            id:upperma
            anchors.fill: parent
            drag.target: parent
            drag.axis: Drag.XAxis
            drag.maximumX: root.width-upperloader.width-upperloader.bkBorderWidth
            drag.minimumX: upperloader.bkBorderWidth
            onClicked: root.checked = !root.checked
        }
    }
}

style的代码

//RadioButtonStyle.qml

import QtQuick 2.4
import QtQuick.Controls 1.2
import QtQuick.Controls.Private 1.0


Style {
    id:style
    property real bkBorderWidth: 2 //背景边框宽度
    property real btnBorderWidth: 1 //按钮边框宽度

    property color borderColor: "#B4B4B4" //边框颜色
    property color checkedColor: "#4BD962" //选中颜色
    property color defaultColor: "#FFFFFF" //未选中颜色

    property Component upper: Rectangle {
        height: control.height-style.bkBorderWidth*2
        width: control.pressed ? 4/3*height : height
        radius: height/2
        color: style.defaultColor
        border.width: style.btnBorderWidth
        border.color: style.borderColor

        Behavior on width {
            PropertyAnimation {
            }
        }

    }

    property Component mask: Rectangle {
        height: control.height-style.bkBorderWidth*2
        width: parent.width
        radius: height/2
        color: control.checked ? style.checkedColor : style.borderColor
    }

    property Component background: Rectangle {
        height: control.height
        width: control.width
        radius: height/2
        color: style.defaultColor
        border.width: control.checked || (control.pressed && !control.checked) ? height : style.bkBorderWidth
        border.color: control.checked ? style.checkedColor : style.borderColor

        Behavior on border.width {
            PropertyAnimation {
                duration: 350
            }
        }
    }

    //布局面板
    property Component panel: Item {
        anchors.fill: parent

        Loader {
            anchors.centerIn: parent
            sourceComponent: background
        }

    }
}

运行截图:

未选:

未选

选中:

选中

按下的过程中:

按下的过程

最后,我有两个效果没有实现,被我放弃了。

  • 按钮圆圈的阴影,我没有加,我试着加了DropShadow,结果整个圈的边缘都看着像狗啃的一样,所以删掉了。
  • 在按钮滑块自动移动的时候,我没有加动画效果,原因是加了效果后,如果再按下按钮而又不释放的话,会看到奇异的按钮慢慢滑动的效果。原因是,我目前没有太好的办法区分按下造成的滑块移动和点击造成的滑块移动的区别。

QAQ谁有好方法,可以教教我~