The learning goals for this assignment are:
The requirements for this assignment are as follows:
Take a look at the image above. You'll see that the image is showing something we haven't seen before in a Swing application: both the current page component and the underlying page component are displayed, as well as the semi-rectangular area between them representing the turning page. How can you display two components plus some other stuff at the same time? How do you occlude the current page component with the rectangular turn area and let the page underneath "show through?"
In order to do this, a key goal of this assignment is to explore a common mechanism called "off screen rendering" that's used by Swing and other toolkits.
The basic idea with off-screen rendering is that we'll "capture" the image of both our current page, and the underlying page, in an off-screen buffer. We do this by causing the page components to draw themselves to the buffer, rather than to the screen. Then we can combine these buffers along with other graphics during our animation.
How do we get a component to draw itself onto an offscreen buffer? Well, first we create the buffer itself. Next, Swing lets us get a Graphics object for drawing into that buffer. Then, we just call the component's normal paint() code, passing that graphics object to it. Essentially, we're just causing the component's normal painting code to be called, but this time the component is rendering onto the graphics object we've passed to it (which causes the drawing to go to the buffered image) rather than to the screen.
Here's a snippet of code that will do this for you. You can pass in any component as the source, and this code will create a BufferedImage of the same size as the source, render the source component into it, and return it. (FYI, there are less verbose ways of creating an image and rendering a component into it, but this is the safest and most portable):
import java.awt.image.BufferedImage; import java.awt.Graphics2D; import java.awt.GraphicsConfiguration; import java.awt.GraphicsEnvironment; public BufferedImage makeOffscreenImage (JComponent source) { // Create our BufferedImage and get a Graphics object for it GraphicsConfiguration gfxConfig = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration(); BufferedImage offscreenImage = gfxConfig.createCompatibleImage(source.getWidth(), source.getHeight(); Graphics2D offscreenGraphics = (Graphics2D) offscreenImage.getGraphics(); // Tell the component to paint itself onto the image source.paint(offscreenGraphics); // return the image return offscreenImage; }Essentially what this code is doing is: 1) We pass it a JComponent called source, which is the component we want to render to an off-screen buffer. 2) We get the "graphics configuration" for the current screen, which is basically just an object that contains a bunch of information about resolution, color capabilities, etc., of the current screen. 3) Then, we use this to create a BufferedImage, called offscreenImage. This is just an image buffer with the same width and height of the source component. 4) We get the graphics object for the image, by calling getGraphics. 5) Then we just tell the source component to paint itself onto this graphics object, using its normal paint() method (which calls paintComponent(), paintChildren, and paintBorders()). This causes the source component's normal painting code to be called but this time, the component is rendering onto the graphics object we've passed to it (which causes the drawing to go to the buffered image) rather than to the screen. Finally, 6) we return this buffered image.
Using code like the snippet above, you can capture an image of both your current page, and the underlying page, just before you start the page turn animation. This will make drawing the animation easier.
Next, let's see how we'll use this in our animation.
The first change you'll need to make is that the listener code for your next and previous buttons, and the gesture recognizer, will no longer "instantly" change the page. Instead, they'll kick off the animation process for page turning. So, you'll need to update the code in these listeners, and this part of your recognizer.
Your new code should first set a flag that you'll use to keep track of the fact that you're in the midst of "page turning mode," and then set up a Swing timer that will fire periodically, say once every tenth of a second, for five or so iterations (giving a total animation time of 0.5 seconds).
What will this timer do? Before the first iteration, it should capture an image of the current and underlying pages; these images will be used throughout the animation cycle. It should also set up some variables that you create that will indicate the current position of the turned page for that iteration; you'll use these to keep track of how much of the current and underlying page images to display, and where the turning page rectangular graphic currently is.
Then, in each successive iteration of the animation, the code will update the variables that indicate the current position in the animation, and call repaint() to trigger the drawing of the current iteration in the animation.
Finally, after the last iteration of the animation, the page change is now complete. The timer can unset the flag that indicates you're in the midst of a page change animation. Also, once the animation is completed, you can also remove the current page from your layout and add the next or previous one; be sure to call revalidate() and repaint() so that the application correctly updates.
A key to creating the page turn animation is to realize that--just like with all drawing in Swing--the actual drawing of the animation has to happen in the paintComponent() method of your components. In your paintComponent code, you can look at your "page turning mode" flag to determine if you should draw your component normally, or instead draw the current iteration of your animation if you're in the middle of a page turn.
If you're in the middle of a page turn, your paintComponent() method should draw a portion of the current component (taken from the offscreen image you stored previously), the semi-rectangular region that represents the turning page, and a portion of the underlying component (also taken from the offscreen image you stored previously).
To draw a portion of a BufferedImage to the screen, you can do something like the following:
public void paintComponent(Graphics g) { // ... BufferedImage portion = offscreenImage.getSubimage(x, y, width, height); g.drawImage(portion, 0, 0, this); }getSubImage() returns a rectangular sub-region of the buffered image that you call it on. Then, you can use the drawImage() method on the Graphics object that gets passed to your paint component to render this image onto the screen. (The first two parameters to drawImage() are the coordinates at which the image will be drawn within your component; the last parameter is typically the current JComponent.)
You can use code like this to "mix" the selected regions of both your current and underlying page images, as well as the page turning rectangle; the specific coordinates at which these get mixed will be determined by the variables that indicate the current position in the animation cycle, set via successive iterations in your timer.
In essence, the strategy here is that the current page component is responsible for drawing the entire animation cycle up until it is finished, at which point it is replaced by the underlying component.
The user completes the page turning by releasing the right mouse button. If the user releases before dragging at least half of the page width, the page "snaps back" into place and the turn is aborted. If the user releases after dragging at least half the page width, however, the page turn completes. In both cases, the snap back and the completion should be animated from the point where the user releases the mouse button.
Almost all of the code you've written for the first part of the assignment should be tweakable to do dynamic page turning. Basically, when you're in "dynamic page turning mode" (detected by a right click and drag within some small number of pixels from either the left or right edge), you update the variables that control the position of the rectangular area and the displayed regions of the current and underlying pages from the current mouse position, rather than via iteration through the animation cycle. Your paint code stays the same, combining the two off-screen regions and the page turn rectangle depending on these variables. Then, when the mouse is released, you start up a Swing timer that iterates through an animation cycle starting from the current position.