如何在代码中滚动ScrollPane?

4
我正在尝试在LibGDX中制作这样一个滚轮组件: enter image description here 我使用ScrollPane,因为它具有输入和快速滑动处理功能。我有一个图像用于滚轮,该图像分成14个部分,滚动窗格本身短两个部分,以便左右两侧都可以滚动到一个部分。一旦滚动位置到达任一方向的末尾,我想将滚动位置重置回中心。反复执行此操作应该创建无限滚动轮的幻觉(希望如此)。
我遇到的问题是如何在代码中定位ScrollPane以在达到任一端时重置图像。到目前为止,我尝试过的所有设置滚动位置的方法都没有起作用。我已经尝试了setScrollX()和scrollTo()方法。我还尝试了将scrollpane的大小设置为各种大小(与图像相同的大小和比图像小两个部分)。我尝试调用scrollpane上的layout、invalidate和pack,以确保在设置滚动值之前正确布局它。我认为updateVisualScroll()可能会强制其更新滚动位置,但这也没有效果。
无论我做什么,它都会忽略我改变滚动位置的所有调用,所以我显然错过了什么。在下面的代码中,我正在尝试让滚轮从图像的中心开始,而不是从最左边开始。
我还需要能够获取当前滚动位置以检测是否已达到任一端。我尝试覆盖act()方法并打印scrollPane.getX(),但即使我手动单击和拖动它来滚动ScrollPane,这个值始终为“0”。
手动单击和拖动时滚动确实有效,因此我认为ScrollPane已正确设置,我只是无法在代码中滚动它。
这是我的代码,为了简便起见,我移除了所有的实验代码,因为没有一个实验成功。
public class MyScrollWheel extends Container<ScrollPane> {
    private ScrollPane scrollPane;
    private Image image;
    private int scrollOffset;

    public MyScrollWheel(){
        Texture texture = new Texture(Gdx.files.internal("internal/scrollwheel.png"));
        image = new Image(texture);

        scrollOffset = (int)(image.getWidth()/14);

        scrollPane = new ScrollPane(image);
        scrollPane.setOverscroll(false, false);

        setActor(scrollPane);
        size(image.getWidth()-(scrollOffset*2), image.getHeight());

        scrollPane.setScrollX(scrollOffset); // << this doesn't scroll
        scrollPane.updateVisualScroll();
    }
}

1
使用“滑块”怎么样?您可以在监听器中获取金额,并且可以轻松地将其位置设置在监听器内部或外部。 - Madmenyo
好主意,我会尝试并回报的。 :) - Tekkerue
如何摆脱演员,使背景水平包裹,并编辑源x。我只是在谈论精灵批处理。查看Texture.wrap,也许当你掌握它时可以创建一个扩展演员的类。 - Madmenyo
如果你没有成功,明天我会尝试解决这个问题。 - Madmenyo
我的应用程序完全基于UI,并且仅依赖于Stage和各种不同类型的Actor。这个滚轮将与其他Actor一起添加到Table中,因此它确实需要是一个Actor。ScrollPane似乎可以做到我需要的一切(裁剪滚轮图像,单击+拖动滚动,快速滑动等),但我无法弄清楚如何在代码中直接设置滚动位置...似乎这是可以做到的。有什么想法可以让它工作或者我需要修改ScrollPane源代码?非常感谢您的帮助! :) - Tekkerue
显示剩余3条评论
2个回答

2

我希望我能给你提供一些有用的东西。我所做的就是扩展actor并使其接受纹理,这样我就可以使用Texture.wrap并使用SpriteBatch.draw()进行绘制。现在我能够让它滚动,根据滚动增量,您可以确定它已经滚动了多远。我不认为有必要重置滚轮,但如果您真的想这样做,您只需执行wheel.setScroll(0)。

一个限制是它不是Drawable,因此无法像NinePatch那样进行缩放。您必须给它一个普通的轮子纹理,并将其绘制为所需的大小,然后手动保持纵横比并添加边框,也可以在上面叠加渐变色以创建深度。

滚轮:

public class ScrollWheel extends Actor {
    Texture wheelTexture;
    private int scroll = 0;

    public int getScroll() {
        return scroll;
    }
    public void setScroll(int scroll) {
        this.scroll = scroll;
    }

    public ScrollWheel(Texture texture)
    {
        wheelTexture = texture;
        wheelTexture.setWrap(Texture.TextureWrap.Repeat, Texture.TextureWrap.ClampToEdge);

        setWidth(texture.getWidth());
        setHeight(texture.getHeight());
    }

    @Override
    public void draw(Batch batch, float parentAlpha) {
        super.draw(batch, parentAlpha);

        batch.draw(wheelTexture, getX(), getY(), scroll, 0,
                wheelTexture.getWidth(), wheelTexture.getHeight());
    }
}

在屏幕中的使用:

public class TestScreen implements Screen {
    Stage stage;
    ScrollWheel wheel;

    public TestScreen() {
        stage = new Stage();
        Table t = new Table();
        t.setFillParent(true);
        stage.addActor(t);

        wheel = new ScrollWheel(new Texture("hud/wheel_part.png"));
        wheel.addListener(new DragListener() {
            @Override
            public void drag(InputEvent event, float x, float y, int pointer) {
                super.drag(event, x, y, pointer);
                wheel.setScroll(wheel.getScroll() + (int)getDeltaX());
            }
        });

        t.add(wheel);
        Gdx.input.setInputProcessor(stage);
    }

    @Override
    public void render(float delta) {
        Gdx.gl.glClearColor(.3f, .36f, .42f, 1);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

        stage.act();
        stage.draw();
    }
    //...Other mandatory screen methods...
}

所以只需创建一个可平铺的轮毂纹理,并将其与 ScrollWheel 构造函数一起使用。如果您使用此确切代码,则会在屏幕中央绘制轮毂。

scroll变量基本上保存滚动量,因此,如果您想将其限制在0到100之间,则只需在setScroll()中添加此功能。

if (scroll > 100) scroll = 100; 
else if (scroll < 0) scroll = 0;

你可以添加一个步骤。因此,如果你想用滑块旋转图像,你可以通过 scroll * 3,6fscroll * (maxScroll / maxStep) 来设置旋转。
我真的很喜欢这个结果,将来我会在我的滑块中使用它:D。我已经扩展和修改了它,你可以在这里看到我的实现:https://youtu.be/RNLk5B-VfYg

哇,谢谢!太棒了! :) 我遇到了一个问题,因为我的主窗口是在滚动窗格中实现的,而滚轮似乎与滚动窗格相互作用,所以滚轮只能移动几个像素就会锁定并停止移动。如果我删除滚动窗格,它就可以完美地工作,所以问题肯定是滚动窗格和滚轮之间的交互问题。我认为这可能是一个焦点问题,所以我尝试在拖动监听器中添加getStage().setScrollFocus(wheel); 但这并没有解决问题。你有什么想法吗?如果需要,我可以发布示例代码。 - Tekkerue
在我之前为游戏开发比赛制作的游戏中,我想要在滚动窗格内拖动演员。但是滚动窗格却成了一个麻烦事,因为它会覆盖这些操作。我不知道如何让它只在拖动可触摸演员之外时才起作用。似乎没有任何简单的实现方法。我唯一能想到的是在轮子上触发触摸时删除监听器。另一个解决方法是实现多个“页面”而不是滚动。您仍然可以让用户轻扫屏幕以向上或向下翻一页。 - Madmenyo
那真是令人失望啊。我的应用程序无法使用分页系统,因为它具有滚动UI、按钮、文本框等,并且根据用户定义的设置可以更改其大小。因此,想出一种让ScrollPane放开输入的方法是我最好的选择。我以前使用ScrollPane作为ScrollWheel的方法确实有效,至少对于捕获输入来说是这样的。只要我在ScrollWheel上滚动,外部窗口就不会滚动。我从ScrollPane源代码中得到了尝试setScrollFocus方法的想法,但这并不适用于您的ScrollWheel。 - Tekkerue
哇!我找到了如何防止ScrollPane成为“ScrollPain”的方法。;)请参见此线程上的最后一篇帖子。我向您的ScrollWheel添加了这个新的监听器,现在即使在ScrollPane内部也可以完美地工作。http://badlogicgames.com/forum/viewtopic.php?f=11&t=12612 - Tekkerue
1
再次感谢您的所有帮助。我真的很喜欢这个!我也想在未来的某个时候进行微调,包括支持fling和使其与可绘制对象一起工作。如果我成功实现这些功能,我会在完成后发布更新。 :) - Tekkerue
Menno Gouw,我刚刚将我的更新添加到你的滚轮中,我很想听听你的想法/意见或任何进一步的调整。再次感谢你的所有帮助!我不知道我是否会考虑完全从头开始设计一个,可能只会继续尝试提供的LibGDX组件,但这绝对是最好的方法。它运行得非常好! - Tekkerue

2

在Menno Gouw的滚轮基础上,我增加了一些功能:

  • 支持快速滑动,并可设置滑动时间
  • 精度设置,以调整滚轮的灵敏度
  • 接受Drawable对象
  • 兼容ScrollPane使用

注意: 为了我的目的,在构造函数中,它需要一个Label,但如果您不想将其绑定到Label,这很容易更改。

这是我用手机录制的演示滚轮的视频。

https://www.youtube.com/watch?v=RwVrez4BZsY&feature=youtu.be

- 编辑1: 布局错误现已修复(希望如此)。当在ScrollPane中移动时,它现在会更新其位置,并且Drawable对象被剪裁到Actor的边界。

- 编辑2: 添加了对于静态Drawable对象的支持以进行阴影处理,并添加了更改滚轮方向的方法(setRightPositiveDirection())。

public class ScrollWheel extends Actor {
    private Drawable wheelDrawable, wheelShading;
    private Label label;

    private int unscaledScrollValueX=0, scrollValueX=0;
    private boolean isNotEdge;
    private int precision=40;
    private int direction=1;
    private int minValue=Integer.MIN_VALUE, maxValue=Integer.MAX_VALUE;
    // MANUAL SCROLL
    private int separator;
    private int wheelWidth;
    // FLING
    private float flingTimer, flingTime=1f;
    private float velocityX;

    public ScrollWheel(Drawable wheelDrawable, Drawable wheelShading, Label label) {
        this.wheelDrawable = wheelDrawable;
        this.wheelShading = wheelShading;
        this.label = label;

        wheelWidth = (int)wheelDrawable.getMinWidth();
        separator = wheelWidth;

        setWidth(wheelDrawable.getMinWidth());
        setHeight(wheelDrawable.getMinHeight());

        // stops ScrollPane from overriding input events
        InputListener stopTouchDown = new InputListener() {
            public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) {
                event.stop();
                return false;
            }
        };
        addListener(stopTouchDown);

        ActorGestureListener flickScrollListener = new ActorGestureListener() {
            public void pan (InputEvent event, float x, float y, float deltaX, float deltaY) {
                updateScroll(deltaX);
            }
            public void fling (InputEvent event, float x, float y, int button) {
                if (Math.abs(x) > 150) {
                    flingTimer = flingTime;
                    velocityX = x;
                }
            }
            public boolean handle (Event event) {
                if (super.handle(event)) {
                    if (((InputEvent)event).getType() == InputEvent.Type.touchDown) flingTimer = 0;
                    return true;
                }
                return false;
            }
        };
        addListener(flickScrollListener);
    }

    private void updateScroll(float delta){
        unscaledScrollValueX += (delta * direction);
        scrollValueX = (int)(unscaledScrollValueX / precision);

        isNotEdge = true;
        if (scrollValueX <= minValue){
            scrollValueX = minValue;
            unscaledScrollValueX = minValue * precision;
            isNotEdge = false;          
        }
        else if (scrollValueX >= maxValue){
            scrollValueX = maxValue;
            unscaledScrollValueX = maxValue * precision;
            isNotEdge = false;          
        }
        if (isNotEdge){         
            separator += delta;
            if (separator <= 0){
                separator = wheelWidth;
            }
            else if (separator >= wheelWidth) {
                separator = 0;
            }
        }

        updateLabel();      
    }
    private void updateLabel(){
        label.setText("" + scrollValueX);   
    }

    public void setMinValue(int minValue){ this.minValue = minValue; }
    public void setMinValueToNone(){ minValue=Integer.MIN_VALUE; }
    public void setMaxValue(int maxValue){ this.maxValue = maxValue; }
    public void setMaxValueToNone(){ minValue=Integer.MAX_VALUE; }
    public void setFlingTime(float flingTime){ this.flingTime = flingTime; }
    public void setPrecision(int precision){ this.precision = precision; }
    public void setRightPositiveDirection(boolean rightPositive){ direction = (rightPositive) ? 1 : -1; }

    @Override
    public void act(float delta){
        super.act(delta);

        boolean animating = false;
        if (flingTimer > 0) {
            float alpha = flingTimer / flingTime;
            updateScroll(velocityX * alpha * delta);

            flingTimer -= delta;
            if (flingTimer <= 0) {
                velocityX = 0;
            }
            animating = true;
        }

        if (animating) {
            Stage stage = getStage();
            if (stage != null && stage.getActionsRequestRendering()){
                Gdx.graphics.requestRendering();
            }
        }
    }

    @Override
    public void draw(Batch batch, float parentAlpha){
        super.draw(batch, parentAlpha);
        batch.flush();
        if (clipBegin(getX(), getY(), getWidth(), getHeight())){
            wheelDrawable.draw(batch, getX() + separator - wheelWidth, getY(), wheelDrawable.getMinWidth(), wheelDrawable.getMinHeight());      
            wheelDrawable.draw(batch, getX() + separator, getY(), wheelDrawable.getMinWidth(), wheelDrawable.getMinHeight());
            wheelShading.draw(batch, getX(), getY(), wheelShading.getMinWidth(), wheelShading.getMinHeight());
            batch.flush();
            clipEnd();
        }
    }
}

太棒了!我甚至不知道有一个ActorGestureListener,但这很好,我应该能够修复从滚动面板中拖出actors的问题。很想看到它在视频中的效果 ;) - Madmenyo
我猜我发布得太早了,因为布局仍然存在一些错误。我增加了ScrollPane的大小以允许屏幕滚动,但滚轮没有随着ScrollPane移动。此外,显然表格集与轮子演员的大小完全相同,这导致扩展到轮子演员外部的图像被裁剪掉。使表格更宽暴露了在轮子外绘制的额外图像。所以现在我正在尝试弄清楚如何裁剪绘图以保持在演员内部(我假设是剔除?)...在所有这些问题解决后会发布更新。 - Tekkerue
好的,我终于修复了布局错误...至少我知道的是这样。希望我不会再发现任何错误。我明天会尝试制作一个视频。目前我还没有完成图形设计,所以它只是一个俗气的画图板涂鸦,但足以展示机械结构的运作。 - Tekkerue
1
我刚刚添加了一个演示滚轮的视频,并对代码进行了一些更新。目前我想不到还有什么可以添加的,所以我认为它现在可能已经完成了。 :) - Tekkerue

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接