Własny ekran powitalny

Koziołek

     1 Wstęp
     2 Program testowy
     3 Interfejsy Loader i Renderer
     4 Implementacja Loader dla Java 1.6
     5 Implementacja Loader dla Java 1.5 i starszych
          5.1 Dlaczego to działa?

Wstęp

Jeżeli chcemy stworzyć własny ekran powitalny dla naszej aplikacji JSE to musimy przygotować plik graficzny, który będzie wyświetlany.
Istotne jest też zdecydowanie dla której wersji języka piszemy. Od Javy 1.6 mamy do dyspozycji klasę SplashScreen. We wcześniejszych wersjach języka należy samodzielnie utworzyć odpowiedni kod emulujący za pomocą klasy Frame. Powoduje to oczywiście pewne ograniczenia i trudności.
W tym krótkim poradniku zostaną przedstawione obie metody. Przykładowy program będzie też wstanie uruchomić odpowiednią wersję ekranu w zależności od wersji języka.

Program testowy

Program ten będzie demonstrował zasadę działania ekranu powitalnego. Należy zastąpić go własnym kodem.

package pl.koziolekweb.programmers.java.splashscreen;

public class SplashScreenDemo {

	public static void main(String[] args) throws InterruptedException {
		Loader myLoader = getLoader();
		myLoader.setRenderer(new DefaultRenderer());
		myLoader.configure();
		for (int i = 0; i < 100; i += 3) {
			myLoader.update(i);
			Thread.sleep(10);
		}
		myLoader.done();
	}

	private static Loader getLoader() {
		double ver = Double.parseDouble(System.getProperty("java.version").substring(0, 3));
		if (ver >= 1.6) {
			return new Java6Loader();
		}
		return new Java5Loader("./pl/koziolekweb/programmers/java/splashscreen/dragons.jpg");
	}
}

Interfejsy Loader i Renderer

Interfejs Loader zapewnia wygodny sposób dostępu do ekranu powitalnego i jego aktualizacji.

package pl.koziolekweb.programmers.java.splashscreen;

import java.awt.Graphics;
import java.awt.Graphics2D;

/**
 * Interfejs opakowujący różne rodzaje ekranu powitalnego.
 * 
 * @author koziolek
 */
interface Loader {

	/**
	 * Wywoływana po utworzeniu instancji interfejsu w celu dokonania
	 * konfiguracji.
	 */
	public abstract void configure();

	/**
	 * Wywoływana po zakończeniu ładowania.
	 */
	public abstract void done();

	/**
	 * Wywoływana przy zmianie stanu ładowania.
	 * 
	 * @param step numer kroku.
	 */
	public abstract void update(int step);

	/**
	 * Pozwala na pobranie obiektu {@link Graphics2D} dla aktualnej wersji ekranu powitalnego. 
	 * @return
	 */
	public abstract Graphics getGraphics();

}

Interfejs Renderer dostarcza znowuż metod, za pomocą których można stworzyć pasek postępu.

package pl.koziolekweb.programmers.java.splashscreen;

import java.awt.Graphics2D;

public interface Renderer {

	/**
	 * Określa położenie paska postępu i konfiguruje renderer na podstawie {@link Loader Loadera}.
	 * @param loader 
	 */
	public abstract void configure(Loader loader);

	/**
	 * Renderuje pasek postępu dla danego kroku.
	 * @param splashScreen
	 * @param step
	 */
	public abstract void render(Graphics2D splashScreen, int step);

}

Klasa DefaultRenderer jest znowuż przykładową implementacją interfejsu, której zadaniem jest obsługa bardzo prostego paska postępu. We własnym kodzie zastąp tą klasę własną implementacją.

package pl.koziolekweb.programmers.java.splashscreen;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Graphics2D;

public class DefaultRenderer implements Renderer{

	private static final int barHeight = 18;
	private static final int barWidth = 200;
	private int posX;
	private int posY;
	private int bytesWidth;
	private int bytesHeight;
	
	public void configure(Loader loader) {
		Dimension size = loader.getSize();
		posX = (int) (0.5 * (size.getHeight() - barHeight));
		posY = (int) (0.9 * (size.getWidth() - barWidth));
		if (loader.getGraphics() != null) {
			FontMetrics fontMetrics = loader.getGraphics().getFontMetrics();
			bytesWidth = 50 + (int) (0.25 * (fontMetrics.bytesWidth(
					"Loading 00%"
							.getBytes(), 0, 11)));
			bytesHeight = (int) (fontMetrics.getHeight() + ((barHeight * 0.75 - fontMetrics
					.getHeight()) * 0.75));
		}
	}

	public void render(Graphics2D splashScreen, int step) {
		splashScreen.setColor(Color.GRAY);
		splashScreen.fillRect(posX, posY, barWidth, barHeight);
		splashScreen.setColor(Color.BLACK);
		splashScreen.fillRect(posX, posY, (step * barWidth / 100), barHeight);
		splashScreen.setColor(Color.WHITE);
		splashScreen.drawString("Loading "
				+ (step < 10 ? (" " + step) : (step)) + "%", posX + bytesWidth,
				posY + bytesHeight);
	}
}

Implementacja Loader dla Java 1.6

W przypadku Javy 1.6 i nowszej nasza implementacja ma za zadanie przesłonięcie właściwej klasy SplashScreen, tak by mógł z niej korzystać renderer.

package pl.koziolekweb.programmers.java.splashscreen;

import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.SplashScreen;

class Java6Loader implements Loader {

	private static final long serialVersionUID = 1L;

	private SplashScreen screen;

	private Renderer renderer;

	public Java6Loader() {
		screen = SplashScreen.getSplashScreen();
		if (screen == null) {
			System.out.println("Splash Screen not supported");
			return;
		}
	}

	public void configure() {
		renderer.configure(this);
	}

	public void done() {
		if (screen != null) {
			screen.close();
		}
	}

	public Graphics2D getGraphics() {
		return screen == null ? null : screen.createGraphics();
	}

	public Dimension getSize() {
		return screen == null ? new Dimension(0, 0) : screen.getSize();
	}

	public void setRenderer(Renderer renderer) {
		this.renderer = renderer;
	}

	public void update(int step) {
		if (screen != null) {
			renderer.render(getGraphics(), step);
			screen.update();
		}
	}
}

Uruchomienie kodu w tej wersji wymaga od nas podania źródła obrazka, który będzie użyty. Można zrobić to na dwa sposoby.
Po pierwsze można uruchomić program z parametrem -splash:ścieżka_do_obrazka. W takim przypadku zostanie wybrany obrazek umieszczony w ścieżce. Jeżeli ścieżka będzie nieprawidłowa to ekran powitalny nie zostanie wyświetlony. Po drugie można w pliku manifestu umieścić flagę SplashScreen-Image, która będzie wskazywała położenie obrazka wewnątrz pliku jar.

Implementacja Loader dla Java 1.5 i starszych

W tej wersji jesteśmy niestety mocno ograniczeni, przez co nie mamy możliwości swobodnego definiowania obrazu dla ekranu powitalnego.

package pl.koziolekweb.programmers.java.splashscreen;

import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Graphics2D;
import java.awt.Image;

public class Java5Loader implements Loader {

	private final Dimension size = new Dimension(800, 600);
	private final Frame frame = new Frame();
	private Image image;
	private Graphics2D graphics;
	private Renderer renderer;

	public Java5Loader(String imageSRC) {
		image = frame.getToolkit().getImage(imageSRC);
		frame.setUndecorated(true);
		frame.setSize(size);
		frame.setVisible(true);
		int screenHeight = frame.getGraphicsConfiguration().getDevice()
				.getDisplayMode().getHeight();
		int screenWidth = frame.getGraphicsConfiguration().getDevice()
				.getDisplayMode().getWidth();
		frame.setLocation((int) ((screenWidth - size.getWidth()) * 0.5),
				(int) ((screenHeight - size.getHeight()) * 0.5));
	}

	@Override
	public void configure() {
		renderer.configure(this);
	}

	private void createGraphics() {
		graphics = (Graphics2D) frame.getGraphics();
		graphics.drawImage(image, 0, 0, null);
	}

	@Override
	public void done() {
		frame.setVisible(false);
		frame.dispose();
	}

	@Override
	public Graphics2D getGraphics() {
		createGraphics();
		return graphics;
	}

	@Override
	public Dimension getSize() {
		return size;
	}

	public void setRenderer(Renderer renderer) {
		this.renderer = renderer;
	}

	@Override
	public void update(int step) {
		renderer.render(getGraphics(), step);
	}

}

Klasa w konstruktorze przyjmuje położenie obrazka. Oznacza to, że musimy samodzielnie zadbać o to, by wskazać poprawne źródło. Po drugie przyjmujemy, że ekran powitalny ma stałą wielkość 800x600. Metoda setUndecorated() powoduje, że nie zostaną wyświetlone elementy takie jak krawędzie i pasek tytułowy ramki.

Dlaczego to działa?

Pytanie czy kod będzie działał w przypadku, gdy nie mamy javy 1.6, a korzystamy z klasy SplashScreen? Java wymaga tylko by klasa ta była dostępna w trakcie kompilacji. Później jeżeli nasza aplikacja będzie uruchamiana na starszej wersji JVM - to jako że sprawdzamy z jaką wersją języka mamy do czynienia i ładujemy odpowiednią implementację Loader - klasa SplashScreen nie będzie ładowana.

Jednocześnie należy pamiętać, że jeżeli chcemy uzyskać możliwość uruchamiania aplikacji na starszej wersji maszyny wirtualnej to należy skompilować kod pod określoną wersję języka. Kompilacja tego typu jest możliwa poprzez użycie odpowiednich flag kompilatora -source i -target. Co ważne ustawienie wersji języka nie wpływa na dostępność klas.

0 komentarzy