How to Scroll More Than One Object at the Same Time

How can I scroll more than one object at the same time?

"How can I make my Game.class scroll more than one instance of Pipes.class while adding a preset distance between them? "

Here's some simple logic. You want to use a data structure to hold you pipes. What this data structure will hold is whatever data is required to paint then, like x, y, coordinates. For this task, I prefer just to create a new class with it's own draw method, that I pass the paintComponent's Graphics context to. For example

public class Pipe {
int x;
int y;
public class Pipe(int x, int y) {
this.x = x;
this.y = y;
}

public void drawPipe(Graphics g) {
g.fillRect(x, y, 50, 100);
}
}

Now this is just an example class. The above only draws a rectangle, but this is just to show you what you should be doing.

So next you want to have the data structure to hold three Pipe objects, like an array. I prefer to use a List. You'll want that List in your Pipes class, and add three Pipe object to it. You can specify the x to be anything you like, to keep them the same distance apart

public class Pipes extends JPanel {
List<Pipe> pipes = new ArrayList<Pipe>();

public Pipes() {
pipes.add(new Pipe(50, 100));
pipes.add(new Pipe(150, 100));
pipes.add(new Pipe(250, 100));
}
}

Now in the paintComponent method, all you need to do is loop through them and use its drawPipe method

protected void paintComponent(Graphics g) {
super.paintComponent(g);

for ( Pipe pipe : pipes ){
pipe.drawPipe(g);
}
}

Now you move them all you need to do is move there x positions in the timer, and call repaint. You may also want to check against the x to make sure it doesn't do off the screen, or if you moving them the right, you could put them the the very left then whey go off the screen, like a conveyor belt. So you could do something like this

private static final int X_INC = 5;
...
Timer timer = new Timer(40, new ActionListener(){
public void actionPerformed(ActionEvent e) {
for (Pipe pipe : pipes ){
if (pipe.x >= screenWidth) {
pipe.x = 0;
} else {
pipe.x += X_INC;
}
}
repaint();
}
});

As you can see, what I do is loop through the List and just change all their x coordinates, then repaint(). So you can create your own Pipe class with whatever values you need to paint, and just move them around in the loop.


For the changing of speed, instead of using a hard coded vakue like 10 for the timer, use a variable delay, that you can change like with the click of a button

int delay = 100;
JButton speedUp = new JButton("Speed UP");
JButton slowDown = new JButton("Slow Down");
Timer timer = null;
public Pipes() {
timer = new Timer(delay, new ActionListener(){
...
});
timer.start();

speedUp.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e) {
if (!((delay - 20) < 0)) {
delay -=20;
timer.setDelay(delay);
}
}
});
// do the same for slowDown, but decrease the delay
}

Test this out

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;

public class Mario extends JPanel {

private static final int D_W = 800;
private static final int D_H = 300;
private static final int X_INC = 5;

BufferedImage bg;
BufferedImage pipeImg;

List<Pipe> pipes = new ArrayList<>();

int delay = 50;

Timer timer = null;

public Mario() {

try {
bg = ImageIO.read(new URL("http://farm8.staticflickr.com/7341/12338164043_0f68c73fe4_o.png"));
pipeImg = ImageIO.read(new URL("http://farm3.staticflickr.com/2882/12338452484_7c72da0929_o.png"));
} catch (IOException ex) {
Logger.getLogger(Mario.class.getName()).log(Level.SEVERE, null, ex);
}

pipes.add(new Pipe(100, 150, pipeImg));
pipes.add(new Pipe(400, 150, pipeImg));
pipes.add(new Pipe(700, 150, pipeImg));

timer = new Timer(delay, new ActionListener(){
public void actionPerformed(ActionEvent e) {
for (Pipe pipe : pipes) {
if (pipe.x > D_W) {
pipe.x = 0;
} else {
pipe.x += X_INC;
}
}
repaint();
}
});
timer.start();
}

@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(bg, 0, 0, getWidth(), getHeight(), this);
for (Pipe pipe : pipes) {
pipe.drawPipe(g);
}
}

@Override
public Dimension getPreferredSize() {
return new Dimension(D_W, D_H);
}

public class Pipe {

int x;
int y;
Image pipe;

public Pipe(int x, int y, Image pipe) {
this.x = x;
this.y = y;
this.pipe = pipe;
}

public void drawPipe(Graphics g) {
g.drawImage(pipe, x, y, 75, 150, Mario.this);
}
}

public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JFrame frame = new JFrame("Mario Pipes");
frame.add(new Mario());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
}

Sample Image

Scrolling 2 different elements in same time

As answered here: synchronize two scrolling bars in multiple selection box

var s1 = document.getElementById('Select1');
var s2 = document.getElementById('Select2');

function select_scroll_1(e) { s2.scrollTop = s1.scrollTop; }
function select_scroll_2(e) { s1.scrollTop = s2.scrollTop; }

s1.addEventListener('scroll', select_scroll_1, false);
s2.addEventListener('scroll', select_scroll_2, false);

Multiple listboxes scroll at same time?

Here you go:

Two listbox scrollbar in synchronisation

How can I get 2 `Panel` Objects to scroll simultaneously?

I agree with scottm, but adding something that makes a difference:

private void ScorePanel_Scroll(object sender, ScrollEventArgs e)
{
var senderPanel = sender as Panel;

if (senderPanel == null)
{
// Might want to print to debug or mbox something, because this shouldn't happen.
return;
}

var otherPanel = senderPanel == Panel1 ? Panel2 : Panel1;

otherPanel.VerticalScroll.Value = senderPanel.VerticalScroll.Value;
}

The other way, you would always update Panel1 to the scroll offset of Panel2, so if you scrolled Panel2, it would actually not scroll anything.

Now that you have this method, you should subscribe with both panels to it, like so:

Panel1.Scroll += ScorePanel_Scroll;
Panel2.Scroll += ScorePanel_Scroll;

This would probably be best done in the ctor of the form which contains the panels.

How to properly set scrollLeft in two objects using defineSetter?

Scroll events can fire at a high rate, so you may get smoother scrolling by throttling the scroll event handler, as trincot said in his answer. You can throttle a function using setTimeout. When moving things on the screen or dealing with other visible effects, though, I prefer requestAnimationFrame.

requestAnimationFrame runs the callback before the next repaint, so the browser can do some optimizations.

This code will get you closer to the solution, it is based on this example from MDN:

var scrollA = $('#scrollA'),
scrollB = $('#scrollB'),
running = false;

scrollA.on('scroll', function() {
// if we already requested an animation frame, do nothing
if (!running) {
window.requestAnimationFrame(function() {
// synchronize table B and table A
scrollB[0].scrollLeft = scrollA[0].scrollLeft
// we're done here and can request a new frame
running = false;
});
}

running = true;
});

You can use the timestamp passed to the callback to further debounce the synchronization, but you may end with a kinda sluggish scroll in the second table if you put too much delay.

Scroll multiple scrollable widgets in sync

i managed to sync multiple scrollables by using their offset, utilizing their ScrollNotification.

here's a rough code example:

class _MyHomePageState extends State<MyHomePage> {

ScrollController _mycontroller1 = new ScrollController(); // make seperate controllers
ScrollController _mycontroller2 = new ScrollController(); // for each scrollables

@override
Widget build(BuildContext context) {
body:
Container(
height: 100,
child: NotificationListener<ScrollNotification>( // this part right here is the key
Stack( children: <Widget>[

SingleChildScrollView( // this one stays at the back
controller: _mycontroller1,
child: Column( children: <Widget>[
Text('LEFT '),
Text('LEFT '),
Text('LEFT '),
Text('LEFT '),
Text('LEFT '),
Text('LEFT '),
],)
),
SingleChildScrollView( // this is the one you scroll
controller: _mycontroller2,
child: Column(children: <Widget>[
Text(' RIGHT'),
Text(' RIGHT'),
Text(' RIGHT'),
Text(' RIGHT'),
Text(' RIGHT'),
Text(' RIGHT'),
],)
),
]),

onNotification: (ScrollNotification scrollInfo) { // HEY!! LISTEN!!
// this will set controller1's offset the same as controller2's
_mycontroller1.jumpTo(_mycontroller2.offset);

// you can check both offsets in terminal
print('check -- offset Left: '+_mycontroller1.offset.toInt().toString()+ ' -- offset Right: '+_mycontroller2.offset.toInt().toString());
}
)
)
}}

basically each SingleChildScrollView has its own controller.
each controller has their own offset values.
use the NotificationListener<ScrollNotification> to notify any movement, anytime they are scrolled.

then for each scroll gesture (i believe this is a frame by frame basis),
we can add jumpTo() command to set the offsets in anyway we like.

cheers!!

PS. if the list has different length, then the offset will be different and you will get a stack overflow error if you try to scroll past its limit. make sure to add some exceptions or error handling. (i.e. if else etc.)



Related Topics



Leave a reply



Submit