用qml仿照ios开关按钮效果

✤ Feb 27, 2015 ✤

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

苹果开关按钮效果

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

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

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

恩,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
        }

    }
}

运行截图:

未选:

未选

选中:

选中

按下的过程中:

按下的过程

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

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