LINUX.ORG.RU

QML ScrollBar / ScrollIndicator - отключить авто скрытие

 , ,


0

2

Добрый день.

Есть Flickable и внутри его указано свойство ScrollBar.vertical:


Flickable {
    // ...
    ScrollIndicator.vertical: ScrollIndicator {}
    // ...
}

Все работает, но есть нюанс :-)

Если высота контента у Flickable требует полосы прокрутки, то она (полоса) появляется только в момент начала прокрутки, а в состоянии покоя полоса скрыта. После осуществления прокрутки полоса через пару секунд плавно исчезает.

Хочется, чтобы полоса, если высота контента ее требует, была видна всегда и не исчезала. Сразу предупрежу, что свойство ScrollBar.policy: ScrollBar.AlwaysOff не совсем то, т.к. при этом полоса прокрутки становится видимой постоянно, даже в том случае, когда высота контента Flickable меньше высоты самого Flickable (а у ScrollIndicator в принципе нету свойства policy).

Если добавить такой код:

ScrollIndicator.vertical: ScrollIndicator {
    contentItem.onOpacityChanged: {
        console.log("contentItem.onOpacityChanged: " + contentItem.opacity)
    }
}

то можно увидеть, что исчезновение организовано методом постепенной смены contentItem.opacity.

По информации отсюда https://stackoverflow.com/a/42203178 можно использовать вот такие хаки, чтобы добиться постоянной видимости у ScrollBar или ScrollIndicator:

Вариант 0
Flickable {
    // ...
    ScrollIndicator.vertical: ScrollIndicator {
        contentItem.opacity: 1.0
        contentItem.onOpacityChanged: {
            contentItem.opacity = 1.0
            
            // Можно раскомментировать данную строку и убедиться,
            // что в процессе работы меняется именно contentItem.opacity
            // (предварительно нужно закомментировать установку contentItem.opacity выше)
            // console.log("contentItem.onOpacityChanged: " + contentItem.opacity)
        }
    }
    // ...
}
Вариант 1
Flickable {
    // ...
    ScrollIndicator.vertical: ScrollIndicator {
        active: true
        onActiveChanged: {
            active = true
            
            // Можно раскомментировать данную строку и убедиться,
            // что в процессе работы меняется именно onActiveChanged
            // (предварительно нужно закомментировать установку onActiveChanged выше)
            // console.log("onActiveChanged " + active)
        }
    }
    // ...
}    

Это конечно работает, но согласитесь, это все таки некрасиво: ведь какой-то обработчик там внутри все же срабатывает и еще таймер запускает для постепенной смены прозрачности. Может кто-нибудь знает более умное решение?

А да, что вообще думаете про такое поведение скролбара с автоскрытием? Может и хрен с ним? На смартфоне так практически во всех приложениях и работает (но в некоторых приложениях при первом отображении области со скролбаром он на секунду отображается, потом снова пропадает).

Вот вариант, при котором полоса прокрутки отображается сразу после загрузки qml сцены, затем через несколько секунд исчезает:

Flickable {
    // ...
    ScrollIndicator.vertical: ScrollIndicator {
        active: true
        Component.onCompleted: {
            active = false
        }
    }
    // ...
}    

@Pavval, милости прошу на обсуждение.

★★★★★

Если тебе нужно чтобы скролл вел себя так, как тебе надо, то не аттачь его к Flickable, а подключай руками. Например:

import QtQuick
import QtQuick.Controls
import QtQuick.Window

Window {
    width: 300
    height: 300
    visible: true
    title: qsTr("Hello World")


    Flickable
    {
        id: view
        anchors.fill: parent
        contentWidth: contentRect.width
        contentHeight: contentRect.height
        Rectangle
        {
            id: contentRect
            color: "red"
            width: 400
            height: 400
            radius: width / 2
        }
    }

    ScrollBar
    {
        id: hbar
        anchors
        {
            left: view.left
            right: view.right
            rightMargin: vbar.width
            bottom: view.bottom
        }

        policy: ScrollBar.AlwaysOn
        orientation: Qt.Horizontal
        size: view.visibleArea.widthRatio
        position: view.visibleArea.xPosition
        onPositionChanged: if (size !== 1 && !view.flicking) view.contentX = position * view.contentWidth - view.originX
        visible: size !== 1
    }

    ScrollBar
    {
        id: vbar
        anchors
        {
            right: view.right
            top: view.top
            bottom: view.bottom
            bottomMargin: hbar.height
        }

        policy: ScrollBar.AlwaysOn
        orientation: Qt.Vertical
        size: view.visibleArea.heightRatio
        position: view.visibleArea.yPosition
        onPositionChanged: if (size !== 1 && !view.flicking) view.contentY = position * view.contentHeight - view.originY
        visible: size !== 1
    }
}

А пытаться в приаттаченом скроллбаре зафорсить свойство - это костыли.

Pavval ★★★★★ ()
Последнее исправление: Pavval (всего исправлений: 1)
Ответ на: комментарий от rumgot

Гена нна:

import QtQuick
import QtQuick.Controls
import QtQuick.Window

Window {
    width: 300
    height: 300
    visible: true
    title: qsTr("Hello World")


    Flickable
    {
        id: view
        anchors.fill: parent
        contentWidth: contentRect.width
        contentHeight: contentRect.height
        Rectangle
        {
            id: contentRect
            color: "red"
            width: 400
            height: 400
            radius: width / 2
        }

        ScrollBar.vertical: ScrollBar
        {
            policy: ScrollBar.AlwaysOn
            visible: size !== 1
        }

        ScrollBar.horizontal: ScrollBar
        {
            policy: ScrollBar.AlwaysOn
            visible: size !== 1
        }
    }
}

Pavval ★★★★★ ()
Ответ на: комментарий от Pavval

А вот еще как бы ты упростил следующее:

import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Window 2.12
import QtQuick.Controls.Material 2.12
import QtQml 2.12

ApplicationWindow {

    function show_hide_scrollbar(s) {
        s.active = true
        s.active = false
    }

    visible: true
    // Material.theme: Material.Dark
    Material.theme: Material.Light
    Material.accent: Material.Red

    width: 600
    height: 400

    Column {
        anchors.fill: parent

        SwipeView {
            id: view
            clip: true
            width: parent.width
            height: parent.height  - indicator.height

            onCurrentIndexChanged: {
                if (currentIndex == 0) {
                    show_hide_scrollbar(si0)
                } else if (currentIndex == 1) {
                    show_hide_scrollbar(si1)
                }

            }

            Component.onCompleted: {
                show_hide_scrollbar(si0)
            }

            Item {
                Flickable {
                    anchors.fill: parent
                    contentHeight: c0.height

                    Column {
                        id: c0
                        padding: 20
                        spacing: 10
                        width: parent.width

                        Label {text: "Label"; anchors.horizontalCenter: parent.horizontalCenter}
                        Slider {anchors.horizontalCenter: parent.horizontalCenter}
                        RangeSlider {anchors.horizontalCenter: parent.horizontalCenter}
                        SpinBox {anchors.horizontalCenter: parent.horizontalCenter}
                        Slider {anchors.horizontalCenter: parent.horizontalCenter}
                        RangeSlider {anchors.horizontalCenter: parent.horizontalCenter}
                        SpinBox {anchors.horizontalCenter: parent.horizontalCenter}
                        Slider {anchors.horizontalCenter: parent.horizontalCenter}
                        RangeSlider {anchors.horizontalCenter: parent.horizontalCenter}
                        SpinBox {anchors.horizontalCenter: parent.horizontalCenter}
                        Button {text: "Button0"; anchors.horizontalCenter: parent.horizontalCenter}
                        Button {text: "Button1"; anchors.horizontalCenter: parent.horizontalCenter}
                        Button {text: "Button2"; anchors.horizontalCenter: parent.horizontalCenter}
                    }

                    ScrollIndicator.vertical: ScrollIndicator {id: si0}
                }
            }

            Item {
                Flickable {
                    anchors.fill: parent
                    contentHeight: c1.height

                    Column {
                        id: c1
                        padding: 20
                        spacing: 10
                        width: parent.width

                        Label {text: "Label"; anchors.horizontalCenter: parent.horizontalCenter}
                        Slider {anchors.horizontalCenter: parent.horizontalCenter}
                        RangeSlider {anchors.horizontalCenter: parent.horizontalCenter}
                        SpinBox {anchors.horizontalCenter: parent.horizontalCenter}
                        Slider {anchors.horizontalCenter: parent.horizontalCenter}
                        RangeSlider {anchors.horizontalCenter: parent.horizontalCenter}
                        SpinBox {anchors.horizontalCenter: parent.horizontalCenter}
                        Slider {anchors.horizontalCenter: parent.horizontalCenter}
                        RangeSlider {anchors.horizontalCenter: parent.horizontalCenter}
                        SpinBox {anchors.horizontalCenter: parent.horizontalCenter}
                        Button {text: "Button0"; anchors.horizontalCenter: parent.horizontalCenter}
                        Button {text: "Button1"; anchors.horizontalCenter: parent.horizontalCenter}
                        Button {text: "Button2"; anchors.horizontalCenter: parent.horizontalCenter}
                    }

                    ScrollIndicator.vertical: ScrollIndicator {id: si1}
                }
            }

        }

        PageIndicator {
            id: indicator
            count: view.count
            currentIndex: view.currentIndex
            anchors.horizontalCenter: parent.horizontalCenter
        }
    }
}

Тут смысл в том, что полосы прокрутки отображаются сразу после загрузки qml сцены и затем исчезают. А также при пролистывании SwipeView снова появляются и исчезают.

rumgot ★★★★★ ()
Ответ на: комментарий от rumgot

Вот смотрю я на это и думаю, что хваленая декларативность куда-то ушуршала :-)

Ну все, одна строчка отменила весь остальной файл. Реализуй аналогичное решение на плюсах и посмотри сколько поведенческого кода у тебя будет связывать отдельный скроллбар и вью.

Pavval ★★★★★ ()
Ответ на: комментарий от rumgot

Я бы в каждом item, которые под SwipeView, сделал бы property alias с одинаковым именем на соответствующие скроллбары, чтобы можно было обращаться к ним как view.currentItem.scrollBar. Затем можно добавить Behavior на изменение view.currentItem и в нём вызывать соответствующую анимацию (использовать PropertyAction, PauseAnimation). Получится то же самое, но декларативно, без дублирования кода и с возможностью настройки продолжительности анимации или использования другой анимации.

unC0Rr ★★★★★ ()
Ответ на: комментарий от unC0Rr

@unC0Rr, @Pavval

Ребят, а почему не работает анимация поведения так:

import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Window 2.12
import QtQuick.Controls.Material 2.12
import QtQml 2.12

ApplicationWindow {
    visible: true
    // Material.theme: Material.Dark
    Material.theme: Material.Light
    Material.accent: Material.Red

    width: 600
    height: 400

    Column {
        anchors.fill: parent

        SwipeView {
            id: view
            width: parent.width
            height: parent.height - indicator.height
            currentIndex: indicator.currentIndex
            
            // Это не срабатывает !!!
            Behavior on currentIndex {
                ScriptAction {script: console.log("currentIndex changed")}
            }

            Item {}
            Item {}
            Item {}
        }

        PageIndicator {
            id: indicator
            interactive: true
            count: view.count
            currentIndex: view.currentIndex
            anchors.horizontalCenter: parent.horizontalCenter
        }
    }
}

но работает так:

import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Window 2.12
import QtQuick.Controls.Material 2.12
import QtQml 2.12

ApplicationWindow {
    visible: true
    // Material.theme: Material.Dark
    Material.theme: Material.Light
    Material.accent: Material.Red

    width: 600
    height: 400

    Column {
        anchors.fill: parent

        SwipeView {
            id: view
            width: parent.width
            height: parent.height - indicator.height
            currentIndex: indicator.currentIndex
            property bool currentIndexWasChanged: false

            // А с этим костылем работает
            onCurrentIndexChanged: currentIndexWasChanged = !currentIndexWasChanged
            Behavior on currentIndexWasChanged {
                ScriptAction {script: console.log("currentIndex changed")}
            }

            Item {}
            Item {}
            Item {}
        }

        PageIndicator {
            id: indicator
            interactive: true
            count: view.count
            currentIndex: view.currentIndex
            anchors.horizontalCenter: parent.horizontalCenter
        }
    }
}

Кстати говоря, с ApplicationWindow.width такая же херня: не срабатывает Behavior on width, зато если внутрь ApplicationWindow поместить Rectangle у которого width: parent.width и далее сделать Behavior on width на width у этого Rectangle, то тогда срабатывает. Подскажите плиз, как это лечить?

rumgot ★★★★★ ()
Последнее исправление: rumgot (всего исправлений: 3)
Ответ на: комментарий от rumgot

Кстати говоря, с ApplicationWindow.width такая же херня: не срабатывает Behavior on width, зато если внутрь ApplicationWindow поместить Rectangle у которого width: parent.width и далее сделать Behavior on width на width у этого Rectangle, то тогда срабатывает. Подскажите плиз, как это лечить?

Мне кажется что ApplicationWindow - плюсовой класс, который ставит свойство width изнутри как результат фактически произошедшего ресайза окна, потому видимо ему твой behavior до одного места.

А чего не работает currentIndex - хз, но я б не лепил Behavior просто так, если я не собираюсь делать собственно анимацию. Я бы просто в onCurrentIndexChanged написал код и все.

Pavval ★★★★★ ()