1

In many android applications I faced the "Sticky Header For List" title and we love it. https://github.com/search?q=header+sticky+android, Now, I am trying to do it in libGDX framework using Scrollpane and another scene2dui actors.

enter image description here

I achieve that about 60% or more because I think its very simple to do it, But I am missing something to complete it, So, I need some help!

The full example when amountY of scrollpane1 (parent) arrive into specific height, so, please stop scrolling for your child :

package com.mygdx.game;

import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.ui.Image;
import com.badlogic.gdx.scenes.scene2d.ui.Label;
import com.badlogic.gdx.scenes.scene2d.ui.ScrollPane;
import com.badlogic.gdx.scenes.scene2d.ui.Table;
import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable;
import com.badlogic.gdx.utils.Align;

public class MyGdxGame extends ApplicationAdapter {
    private Stage stage;

    private ScrollPane scrollPane1;
    private ScrollPane scrollPane2;

    @Override
    public void create() {
        stage = new Stage();

        Table baseContainer = new Table();

        Table container = new Table();
        scrollPane1 = new ScrollPane(container);

        Table list = new Table();
        scrollPane2 = new ScrollPane(list);

        Table mainHeader = new Table();
        mainHeader.background(new TextureRegionDrawable(this.getPixel()).tint(Color.GRAY));

        Table stickyHeader = new Table();
        container.add(mainHeader).height(100).growX();
        container.row();
        container.add(stickyHeader).height(200).growX();
        container.row();
        container.add(scrollPane2).grow();

        baseContainer.setFillParent(true);
        baseContainer.add(scrollPane1).grow();

        stage.addActor(baseContainer);

        Gdx.input.setInputProcessor(stage);

        this.fillDummyDataInHeader(stickyHeader);
        this.fillDummyDataInList(list);
    }

    @Override
    public void resize(int width, int height) {
        stage.getViewport().update(width, height);
    }

    @Override
    public void render() {
        Gdx.gl.glClearColor(1, 1, 1, 1);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

        stage.act();
        stage.draw();

        // to fix sticky header
        if (scrollPane1.getScrollY() >= 100) {
            // stop scrolling in scrollpane 1
            scrollPane1.setScrollingDisabled(true, true);
            scrollPane1.cancel();

            // run scrollpane 2
            scrollPane2.setScrollingDisabled(true, false);

        }
    }

    @Override
    public void dispose() {
        stage.dispose();
    }

    private void fillDummyDataInHeader(Table stickyHeader) {
        stickyHeader.background(new TextureRegionDrawable(this.getPixel()).tint(new Color(0.1960F, 0.3921F, 0.7843F, 0.8F)));
        Image ic = new Image(new Texture("badlogic.jpg"));
        Label label = new Label("Hi, I am sticky header!", new Label.LabelStyle(new BitmapFont(), Color.WHITE));
        label.setAlignment(Align.center);
        stickyHeader.add(ic).pad(10).center().size(50);
        stickyHeader.row();
        stickyHeader.add(label).pad(10).center().growX();
    }

    private void fillDummyDataInList(Table list) {
        for (int i = 0; i < 10; i++) {
            Table row = new Table().background(new TextureRegionDrawable(this.getPixel()).tint(new Color(1F, 0.63921F, 0, 0.6F)));
            Image ic = new Image(new Texture("badlogic.jpg"));
            Label label = new Label("Row Item [" + (i + 1) + "]", new Label.LabelStyle(new BitmapFont(), Color.DARK_GRAY));
            row.add(ic).pad(20).size(40);
            row.add(label).height(100).growX();

            list.add(row).pad(20).padTop(0).growX();
            list.row();
        }
    }

    private Texture getPixel() {
        Pixmap pixmap = new Pixmap(1, 1, Pixmap.Format.RGBA8888);
        pixmap.setColor(1F, 1F, 1F, 1F);
        pixmap.fill();

        Texture texture = new Texture(pixmap);
        pixmap.dispose();
        return texture;
    }

}
iibrahimbakr
  • 1,116
  • 1
  • 13
  • 32
  • 1
    Do you really need more than one scrollable component? Judging from the link you provided I get the impression that you normally only have one scrollable component and display the 'sticky header' element on top of it, maybe with some more or less fancy animation that when you replace it it appears to be scrolling up. – second Jun 26 '19 at 05:21

1 Answers1

3

Using Stack is the solution as a @second mention as the following code:

package com.mygdx.game;

import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.ui.Cell;
import com.badlogic.gdx.scenes.scene2d.ui.Container;
import com.badlogic.gdx.scenes.scene2d.ui.Image;
import com.badlogic.gdx.scenes.scene2d.ui.Label;
import com.badlogic.gdx.scenes.scene2d.ui.ScrollPane;
import com.badlogic.gdx.scenes.scene2d.ui.Stack;
import com.badlogic.gdx.scenes.scene2d.ui.Table;
import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable;
import com.badlogic.gdx.utils.Align;

public class MyGdxGame extends ApplicationAdapter {
    private Stage stage;

    private ScrollPane scrollPane1;
    private Stack stack;
    private Table stickyHeader;
    private Container<Table> tableContainer;
    private Cell<Container<Table>> oldCell;

    @Override
    public void create() {
        stage = new Stage();

        Table baseContainer = new Table();

        Table container = new Table();
        scrollPane1 = new ScrollPane(container) {
            @Override
            protected void scrollY(float pixelsY) {
                super.scrollY(pixelsY);

                if (pixelsY >= 100F) {
                    stack.add(tableContainer);
                } else {
                    oldCell.setActor(tableContainer);
                }
            }
        };

        Table list = new Table();

        Table mainHeader = new Table();
        mainHeader.background(new TextureRegionDrawable(this.getPixel()).tint(Color.GRAY));

        stickyHeader = new Table();
        tableContainer = new Container<Table>(stickyHeader);
        container.add(mainHeader).height(100).growX();
        container.row();
        oldCell = container.add(tableContainer.fill().top().height(100)).height(100).growX();
        container.row();
        container.add(list).grow();

        baseContainer.setFillParent(true);
        stack = new Stack();
        stack.setFillParent(true);
        stack.add(scrollPane1);

        stage.addActor(stack);

        Gdx.input.setInputProcessor(stage);

        this.fillDummyDataInHeader(stickyHeader);
        this.fillDummyDataInList(list);
    }

    @Override
    public void resize(int width, int height) {
        stage.getViewport().update(width, height);
    }

    @Override
    public void render() {
        Gdx.gl.glClearColor(1, 1, 1, 1);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

        stage.act();
        stage.draw();
    }

    @Override
    public void dispose() {
        stage.dispose();
    }

    private void fillDummyDataInHeader(Table stickyHeader) {
        stickyHeader.background(new TextureRegionDrawable(this.getPixel()).tint(new Color(0.1960F, 0.3921F, 0.7843F, 0.8F)));
        Image ic = new Image(new Texture("badlogic.jpg"));
        Label label = new Label("Hi, I am sticky header!", new Label.LabelStyle(new BitmapFont(), Color.WHITE));
        label.setAlignment(Align.left);
        stickyHeader.add(ic).pad(10).left().size(50);
        stickyHeader.add(label).pad(10).left().growX();
    }

    private void fillDummyDataInList(Table list) {
        for (int i = 0; i < 10; i++) {
            Table row = new Table().background(new TextureRegionDrawable(this.getPixel()).tint(new Color(1F, 0.63921F, 0, 0.6F)));
            Image ic = new Image(new Texture("badlogic.jpg"));
            Label label = new Label("Row Item [" + (i + 1) + "]", new Label.LabelStyle(new BitmapFont(), Color.DARK_GRAY));
            row.add(ic).pad(20).size(40);
            row.add(label).height(100).growX();

            list.add(row).pad(20).padTop(0).growX();
            list.row();
        }
    }

    private Texture getPixel() {
        Pixmap pixmap = new Pixmap(1, 1, Pixmap.Format.RGBA8888);
        pixmap.setColor(1F, 1F, 1F, 1F);
        pixmap.fill();

        Texture texture = new Texture(pixmap);
        pixmap.dispose();
        return texture;
    }

}

sticky header in libGDX list

iibrahimbakr
  • 1,116
  • 1
  • 13
  • 32
  • Using a Stack is a good idea. However, I think your code could be more easy. You should also rename some variables. For me it is hard to get what's going on, and I would need to debug to find out why `oldCell` is needed. – MrStahlfelge Jun 27 '19 at 07:26
  • adding 'sticky' to a stack to put it on the top and release it from lower layer. So, I should cache it position before releasing to back into lower layer. – iibrahimbakr Jun 27 '19 at 07:37