Flex vs Java Deel 2: Effecten


Inleiding

Dat zowel met Flex als Java i.c.m Swing relatief makkelijk een simpel formuliertje te maken is, geloof ik wel. Ik wil me richten op geavanceerdere features die beide platforms te bieden hebben. Er valt hierbij te denken aan die sexy overgangs-effecten met transparantie, glijdende componenten, enzovoort.

In dit artikel wordt een uiteenzetting gegeven wat beide platforms bieden aan voorzieningen om een GUI echt sexy te maken.

Ten slotte geef ik een head-to-head vergelijking, waarbij identieke schermen en efffecten worden geïmplementeerd.

Effecten in Flex

Gezien het feit dat Flex uit de Flash-stal komt, zal het niemand verbazen dat ook Flex een rijk scala aan kant-en-klare visuele effecten biedt. Als we een blik in de mx.effects package werpen zien we onder andere: blur, fade, glow, rotate, diverse masking effecten, etc…

Net zoals alle Flex elementen, kunnen effecten imperatief dan wel declaratief gebruikt worden.
Voorbeeld van imperatief gebruik:

private var currentResize:Resize;

private function getWider():void {
	if (currentResize != null)
		currentResize.end();
	currentResize = new Resize(button);
	currentResize.widthBy = 100;
     currentResize.heightBy = 100;
	currentResize.play();
}
private function getNarrower():void {
	if (currentResize != null)
		currentResize.end();
	currentResize = new Resize(button);
	currentResize.widthBy = -100;
     currentResize.heightBy = -100;
	currentResize.play();
}
<mx:Button id="button" label="Click me!" mouseDown="getWider()" mouseUp="getNarrower()" />

En dezelfde functionaliteit, maar dan declaratief:

<mx:Resize id="wide" widthBy="100" heightBy="100" />
<mx:Resize id="narrow" widthBy="-100" heightBy="100" />
<mx:Button label="Click me" mouseDownEffect="wide" mouseUpEffect="narrow" />

Een leuk aspect van Flex effecten is dat ze ook parallel kunnen lopen. Neem het volgende voorbeeld:

<mx:Parallel id="ef" target="{button}" duration="3000">
  <mx:children>
    <mx:Blur blurXTo="0" blurYTo="0"/>
    <mx:Rotate angleFrom="0" angleTo="360" />
  </mx:children>
</mx:Parallel>
<mx:Button id="button" label="Click me!" click="ef.end();ef.play()" />

codebase="https://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,0,0"
data="/roller/luminis/resource/flexvsjava/ParallelEffectExample.swf"
height="60"
type="application/x-shockwave-flash2-preview"
width="150" > quality="high" src="/roller/luminis/resource/flexvsjava/ParallelEffectExample.swf" type="application/x-shockwave-flash2-preview" width="150" height="60" />

Sequentiële afhandleing, d.w.z. achter elkaar, van effecten is ook mogelijk:

<mx:Sequence id="ef" target="{button}" duration="3000">
  <mx:children>
    <mx:Blur blurXTo="0" blurYTo="0"/>
    <mx:Rotate angleFrom="0" angleTo="360" />
  </mx:children>
</mx:Sequence>
<mx:Button id="button" label="Click me!" click="ef.end();ef.play()" />

codebase="https://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,0,0"
data="/roller/luminis/resource/flexvsjava/SequentialEffectExample.swf"
height="60"
type="application/x-shockwave-flash2-preview"
width="150" > quality="high" src="/roller/luminis/resource/flexvsjava/SequentialEffectExample.swf" type="application/x-shockwave-flash2-preview" width="150" height="60" />

De tijdsduur dat een effect actief is kan ook gezet worden en wel met de duration property. Standaard duurt een effect 1 seconde (1000 milliseeconden). Een voorbeeld:

<mx:Rotate id="ef" angleFrom="0" angleTo="360" duration="3000"/>
<mx:Button id="button" label="Click me!" click="ef.end();ef.play()" />

Het laatste aspect dat ik hier ga behandelen ten aanzien van Flex effecten zijn de zogenaamde easing functions. Dit zijn functies die het verloop van de animatie bepalen, bijvoorbeeld of er een versnelling aan het begin en vertraging aan het einde van de animatie plaatsvindt. Het effect krijgt hierdoor een natuurlijker gevoel. In de package mx.effects.easing zijn diverse classes met easing functies aanwezig, maar er zelf een schrijven kan natuurlijk ook.

<mx:Script>
  <![CDATA[
    import mx.effects.easing.Cubic;
    import mx.effects.easing.Linear;
  ]]>
</mx:Script>

<mx:Rotate id="efNoEasing" target="{button1}" angleFrom="0" angleTo="360" duration="3000" easingFunction="{Linear.easeNone}"/>
<mx:Rotate id="efWithEasing" target="{button2}" angleFrom="0" angleTo="360" duration="3000" easingFunction="{Cubic.easeInOut}" />

	<mx:VBox paddingLeft="40" paddingTop="40">
		<mx:Button id="button1" label="Click me (no easing)!" click="efNoEasing.end();efNoEasing.play()" />
		<mx:Button id="button2" label="Click me (with easing)!" click="efWithEasing.end();efWithEasing.play()" />
</mx:VBox>

codebase="https://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,0,0"
data="/roller/luminis/resource/flexvsjava/EffectEasingExample.swf"
height="150"
type="application/x-shockwave-flash2-preview"
width="250" > quality="high" src="/roller/luminis/resource/flexvsjava/EffectEasingExample.swf" type="application/x-shockwave-flash2-preview" width="250" height="150" />

Effecten in Java

Componenteffeten in Java zijn niet zo rechttoe rechtaan als in Flex. De voor de hand liggende reden hiervoor is dat de API’s van standaard componenten hiertoe geen mogelijkheden bieden. Dit wil niet zeggen dat het niet mogelijk is. Als je bijvoorbeeld kijkt naar de Java2D demo (https://jdk6.dev.java.net/Java2DApplet.html) zie je dat Java wel degelijk de mogelijkheid biedt om funky dingetjes te doen.

Hoe zit dit nou?

Java biedt een zeer uitgebreide, maar ook zeer complexe API voor 2-dimesionale beeldbewerking. Het gros hiervan is verspreid over de java.awt, java.awt.geom en java.awt.image packages. Deze API’s bieden de developer de mogelijkheid om complexe figuren en krommingen te tekenen, clips te definiëren, afbeeldings en overige graphics transformaties te maken, enz..

De uitdaging zit ‘m echter in de manier waarop deze effecten toegepast worden. Alle visuele component klassen hebben een gemeenschappelijke ouder: java.awt.Component. Iedere keer dat de AWT thread het nodig acht dat een component zichzelf opnieuw tekent, roept het de public void paint(Graphics) methode van dat component aan. Deze methode is vervolgens verantwoordelijk voor het tekenen van de staat van het component. Het Graphics object biedt de mogelijkheid om een heel scala aan graphische operaties uit te voeren, zoals tekenen van stukken tekst en basale vormen, en het zetten van kleuren.

In versie 1.2 van Java is de java.awt.Graphics2D klasse geïntroduceerd, die een extensie is van Graphics, en geavanceerdere functionaliteit biedt zoals vorm- en coördinatentransformaties, geometrie, transparantie en kleurenbeheer.

Een aspect aan effecten wat hier nog niet aan bod is gekomen, is die van tijd. Een effect is immers een animatie, maar de paint methode is verantwoordelijk voor het tekenen van een component op een specifiek moment in de tijd. Het gebruik van threads is hier dus noodzakelijk.

Willen we nou een arbitrair lijkend effect op een component toepassen, zullen we dus zijn paint(Graphics) methode moeten overriden en met behulp van een Thread in de loop van de animatie eigenschappen van het component wijzigen.

Laten we als voorbeeld een nemen een om as draaiende JLabel.
We beginnen met het schrijven van een JLabel subclass die te roteren is:

class RotatableLabel extends JLabel {

	private double rotation;

	public RotatableLabel(String s) {
		super(s);
	}

	public double getRotation() {
		return rotation;
	}

	public void setRotation(double r) {
		this.rotation = r;
		repaint();
	}

	public Dimension getPreferredSize() {
		Dimension d = super.getPreferredSize();
		return new Dimension(Math.max(d.width, d.height), Math.max(d.width,
				d.height));
	}

	public void paint(Graphics g) {
		Graphics2D g2 = (Graphics2D) g;

		AffineTransform tr = g2.getTransform();
		tr.rotate(this.rotation, getWidth() / 2.0, getHeight() / 2.0);

		g2.setTransform(tr);
		super.paint(g);
	}
}

In de paint methode wordt een zogenaamde rotate transform toegepast.
Vervolgens maken we een java.lang.Runnable waarin een instantie van RotatableLabel wordt geanimeerd:

class RotationAnimation implements Runnable {
	RotatableLabel m_labelToRotate;
	RotationAnimation(RotatableLabel labelToRotate) {
		m_labelToRotate = labelToRotate;
	}
	public void run() {
		double origRotation = m_labelToRotate.getRotation();
		int steps = 100;
		double step = Math.PI * 2 / 100;
		double curRotation = origRotation;
		for (int i = 0; i < steps; i++) {
			double newRotation = curRotation + step;
			if (newRotation > (Math.PI * 2)) {
				newRotation = newRotation - (Math.PI * 2);
			}
			m_labelToRotate.setRotation(newRotation);
			curRotation = newRotation;
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
			}
		}
		if (m_labelToRotate.getRotation() != origRotation) {
			m_labelToRotate.setRotation(origRotation);
		}
	}
}

Bij het bovenstaande voorbeeld moet opgemerkt worden dat de grootte van het geanimeerde label is aangepast (zie getPreferredSize()) om de animatie te accomoderen.

codebase="." archive="/roller/luminis/resource/flexvsjava/javavsflex.jar" width="200" height="200">

Een mooi alternatief voor het zelf ontwikkelen van effectenframework is de open source library AnimatedTransitions. Deze biedt een vijftal standaard effecten, en de mogelijkheid er zelf nog meerdere aan toe te voegen. Het centrale concept in deze library is een zogenaamde screen transition: effecten worden alleen toegepast indien er op het scherm noemenswaardige wijziging ten aanzien van de zichtbare componenten plaatsvindt.

Een klein voorbeeldje:

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;

import org.jdesktop.animation.transitions.ComponentState;
import org.jdesktop.animation.transitions.Effect;
import org.jdesktop.animation.transitions.EffectsManager;
import org.jdesktop.animation.transitions.ScreenTransition;
import org.jdesktop.animation.transitions.TransitionTarget;
import org.jdesktop.animation.transitions.EffectsManager.TransitionType;
import org.jdesktop.animation.transitions.effects.Move;

public class TransitionExample extends JPanel {

	boolean state;

	public TransitionExample() {
		setLayout(null);

		final JButton b = new JButton("Move me!");
		b.setBounds(50, 50, 150, 30);

		final JButton b2 = new JButton("Move me back!");
		b2.setBounds(100, 100, 150, 30);

		add(b);

		Effect effect = new Move(new ComponentState(b), new ComponentState(b2));
		EffectsManager.setEffect(b, effect, TransitionType.CHANGING);
		final ScreenTransition st = new ScreenTransition(this, new TransitionTarget() {
			public void setupNextScreen() {
				removeAll();
				if (state) {
					add(b2);
				} else {
					add(b);
				}

			}
		}, 1000);
		ActionListener listener = new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				st.start();
				state = !state;
			}
		};
		b.addActionListener(listener);
		b2.addActionListener(listener);
	}
}

codebase="." archive="/roller/luminis/resource/flexvsjava/javavsflex.jar,/roller/luminis/resource/flexvsjava/AnimatedTransitions-0.11.jar,/roller/luminis/resource/flexvsjava/TimingFramework-1.0.jar" width="300" height="200">

Een vergelijking in de praktijk

Om de vergelijking tussen Java en Flex wat meer te illustreren, zal ik een tweetal implementatie voorbeelden geven. Ten eerste een crossfading animatie, en vervolgens een component slide.

Om te beginnen met de crossfade. Een crossfade is een transitie van een scherm naar het andere in dezelfde ruimte. Het effect hierbij is dat geleidelijk de transparantie van het initiële component toeneemt en die van het volgende scherm afneemt. Hierbij krijg je een vloeiende overgang tussen de schermen.

Om te beginnen met de Flex implementatie.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" width="200" height="200">

    <mx:Boolean id="fadeToggle" />
    <mx:Fade id="fadeOut" duration="1000" alphaFrom="1.0" alphaTo="0.0"/>
    <mx:Fade id="fadeIn" duration="1000" alphaFrom="0.0" alphaTo="1.0"/>

	<mx:VBox height="100%" width="100%" paddingLeft="20" paddingTop="20" paddingRight="20" paddingBottom="20">
		<mx:Canvas width="100%" styleName="content">
			<mx:VBox id="box1" visible="{!fadeToggle}" hideEffect="{fadeOut}" showEffect="{fadeIn}" >
				<mx:Image x="45" y="52" source="@Embed(source='../assets/images/duke.gif')"/>
				<mx:Button label="Duke!!" enabled="false"/>
			</mx:VBox>
			<mx:VBox id="box2" visible="{fadeToggle}" hideEffect="{fadeOut}" showEffect="{fadeIn}">
				<mx:Image x="45" y="52" source="@Embed(source='../assets/images/penduke.gif')"/>
				<mx:CheckBox label="Duke with pen!!" enabled="false" selected="true"/>
			</mx:VBox>
		</mx:Canvas>
		<mx:Button click="fadeToggle=!fadeToggle" label="Cross Fade"/>
	</mx:VBox>

</mx:Application>



Hier worden twee containers, box1 en box2, gedeclareerd die in elkaar zullen overvloeien. Er zijn twee fade-effecten, fadeIn en fadeOut, die verantwoordelijk zijn voor de fading (transparantie).
Middels binding (fadeToggle) op het visiblity attribuut worden de containers zichtbaar en onzichtbaar gemaakt. De effecten worden geactiveerd middels de hideEffect en showEffect attributen van de containers.

codebase="https://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,0,0"
data="/download/attachments/14812620/ButtonExample.swf"
height="200"
type="application/x-shockwave-flash2-preview"
width="200" > quality="high" src="/roller/luminis/resource/flexvsjava/CrossFadingExample.swf" type="application/x-shockwave-flash2-preview" width="200" />

Voor de Java implementatie dient er om te beginnen een subklasse van JPanel gemaakt te worden:

public class FadeablePanel extends JPanel {
	private float m_opacity;

	public FadeablePanel(float initialOpacity) {
		super();
		setOpaque(false);
		m_opacity = initialOpacity;
	}

	/**
	 * Overridden to set the opaqueness of <code>comp</code> to false
	 */
	protected void addImpl(Component comp, Object constraints, int index) {
		if (comp instanceof JComponent) {
			((JComponent)comp).setOpaque(false);
		}
		super.addImpl(comp, constraints, index);
	}

	/**
	 *
	 * @return
	 */
	public float getOpacity() {
		return m_opacity;
	}

	/**
	 *
	 * @param f
	 */
	public void setOpacity(float f) {
		if (f >= 0.0 && f <= 1.0) {
			m_opacity = f;
			repaint();
		}
	}

	/**
	 * Overridden to set the opacity to the level set using {@link #setOpacity(float)}.
	 */
	public void paintComponent(Graphics g) {
		Graphics2D g2 = (Graphics2D) g;
		g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
				m_opacity));
		super.paintComponent(g);
	}

}

Deze klasse herimplementeert de paint methode om de gezette transparantie te tekenen. Vervolgens de implementatie van het animatie effect voor een enkel component:

public class FadeEffect {
	private static final int SLEEP_TIME = 10;
	private float m_fromAlpha;
	private float m_toAlpha;
	private int m_duration;

	public FadeEffect(float fromAlpha, float toAlpha, int duration) {
		if (fromAlpha < 0.0 || fromAlpha > 1.0)
			throw new IllegalArgumentException("From alpha value must be between 0.0 and 1.0");
		if (toAlpha < 0.0 || toAlpha > 1.0)
			throw new IllegalArgumentException("From alpha value must be between 0.0 and 1.0");
		m_fromAlpha = fromAlpha;
		m_toAlpha = toAlpha;
		m_duration = duration;
	}

	public void apply(final FadeablePanel componentToFade) {
		new SwingWorker<Object, Object>() {
			protected Object doInBackground() throws Exception {
				int numSteps = m_duration / SLEEP_TIME;
				float stepAmount = (m_toAlpha - m_fromAlpha) / (float)numSteps;
				float currentOpacity = componentToFade.getOpacity();
				for (int i = 0; i < numSteps; i++) {
					float newOpacity = currentOpacity + stepAmount;
					componentToFade.setOpacity(newOpacity);
					currentOpacity = newOpacity;
					try {
						Thread.sleep(SLEEP_TIME);
					} catch (InterruptedException e) {}
				}
				if (componentToFade.getOpacity() != m_toAlpha) {
					componentToFade.setOpacity(m_toAlpha);
				}
				return null;
			}
		}.execute();
	}
}

Hier wordt bij de constructor de start- en eindtransparantie en de duur van de animatie opgegeven. De apply methode start de animatie, die middels een background thread wordt gedaan. Voor de echte crossfade een voorbeeld paneltje:

public class CrossFadeExamplePanel extends JPanel {

	private static final FadeEffect FADE_IN = new FadeEffect(0.0f, 1.0f, 500);
	private static final FadeEffect FADE_OUT = new FadeEffect(1.0f, 0.0f, 500);
	private FadeablePanel panel1;
	private FadeablePanel panel2; 

	public CrossFadeExamplePanel() {
		super(new BorderLayout());

		JLabel label1;
		JLabel label2;
		try {
			ImageIcon icon1 = new ImageIcon(getClass().getResource("duke.gif"));
			ImageIcon icon2 = new ImageIcon(getClass().getResource(
					"penduke.gif"));
			label1 = new JLabel(icon1);
			label2 = new JLabel(icon2);

		} catch (Exception e) {
			label1 = new JLabel("Could not load image duke.gif");
			label2 = new JLabel("Could not load image penduke.gif");
		}

		panel1 = new FadeablePanel(1.0f);
		panel1.setLayout(new BorderLayout());
		panel1.add(label1, BorderLayout.CENTER);

		panel2 = new FadeablePanel(0.0f);
		panel2.setLayout(new BorderLayout());
		panel2.add(label2, BorderLayout.CENTER);

		JPanel content = new JPanel();
		content.setLayout(new OverlayLayout(content));
		content.add(panel1);
		content.add(panel2);

		add(content, BorderLayout.CENTER);

		JButton fadeButton = new JButton(new FadeAction());

		JPanel buttonPane = new JPanel(new FlowLayout(FlowLayout.RIGHT));
		buttonPane.add(fadeButton);

		add(buttonPane, BorderLayout.SOUTH);
	}

	private class FadeAction extends AbstractAction {

		FadeAction() {
			super("Cross fade");
		}

		public void actionPerformed(ActionEvent e) {
			if (panel1.getOpacity() == 1.0f) {
				FADE_IN.apply(panel2);
				FADE_OUT.apply(panel1);
			} else {
				FADE_IN.apply(panel1);
				FADE_OUT.apply(panel2);
			}
		}

	}
}

Hier worden twee componenten over elkaar geplaatst, en middels de twee gedeclareerde fade-effecten wordt de crossfade bereikt.

codebase="." archive="/roller/luminis/resource/flexvsjava/javavsflex.jar" width="400" height="250">

Het volgende voorbeeld betreft een een component in twee delen waarbij het onderste gedeelte omhoog en omlaag ‘glijdt’, vaak ook wel het ‘accordion’ effect genoemd. Het onderste gedeelte heeft een knop in breedte die het uit- en invouwen initiëert.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" width="378" height="528" applicationComplete="init()">
	<mx:Style>
		Text {
			padding-bottom: 10;
			padding-left: 10;
			padding-right: 10;
			padding-top: 10;
		}
	</mx:Style>
	<mx:Script>
		<![CDATA[
			import mx.effects.Resize;
			import mx.effects.Effect;
			import mx.events.EffectEvent;

			private var targetHeight:int = 100;
			private var collapsed:Boolean = false;

			private function init():void {
				collapse.addEventListener(EffectEvent.EFFECT_END,
					function():void
					{
						collapsed = true;
						header.label = "Click to expand";
					}
				);
				expand.addEventListener(EffectEvent.EFFECT_END,
					function():void
					{
						collapsed = false;
						header.label = "Click to collapse";
					}
				);
			}

			private function doCollapse():void {
				var resize:Effect;
				if (collapsed) {
					resize = expand;
				} else {
					resize = collapse;
				}
				resize.play();
			}
		]]>
	</mx:Script>

	<mx:Parallel id="collapse">
		<mx:Resize target="{panel1}" heightBy="100" />
		<mx:Resize target="{panel2}" heightBy="-100" />
		<mx:Move target="{panel2}" yBy="100"/>
	</mx:Parallel>
	<mx:Parallel id="expand">
		<mx:Resize target="{panel1}" heightBy="-100" />
		<mx:Resize target="{panel2}" heightBy="100" />
		<mx:Move target="{panel2}" yBy="-100"/>
	</mx:Parallel>

	<mx:Canvas x="56" y="40" width="250" height="420"
		horizontalScrollPolicy="off" verticalScrollPolicy="off" borderStyle="solid" cornerRadius="2">

		<mx:VBox id="panel1" width="100%" height="66%" top="0" verticalScrollPolicy="off">
				<mx:Text width="100%" height="100%">
					<mx:text>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</mx:text>
				</mx:Text>	

		</mx:VBox>
		<mx:VBox id="panel2" width="100%" height="34%" bottom="0" verticalScrollPolicy="off">
			<mx:Button id="header" label="Click to collapse" width="247" click="doCollapse()"/>
				<mx:Text width="100%" height="100%">
					<mx:text>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</mx:text>
				</mx:Text>
		</mx:VBox>
	</mx:Canvas>

</mx:Application>

De truc zit hem voornamelijk in de collapse en expand effecten; het collapse effect vergroot het bovenste en onderste gedeelte, en verplaatst het onderste naar beneden. Het expand effect doet hetzelfde, maar met inverse waarden.

De Flex versie:

codebase="https://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,0,0"
data="/download/attachments/14812620/SlideEffectExample.swf"
height="400"
type="application/x-shockwave-flash2-preview"
width="250" > quality="high" src="/roller/luminis/resource/flexvsjava/SlideEffectExample.swf" type="application/x-shockwave-flash2-preview" width="250" height="400" />

Vervolgens de Java implementatie:

public class SlideExamplePanel extends JPanel {
	private static final String TEXT_1 = "Lorem ipsum dolor sit amet, " +
			"consectetur adipisicing elit, " +
			"sed do eiusmod tempor incididunt ut labore et " +
			"dolore magna aliqua. Ut enim ad minim veniam, " +
			"quis nostrud exercitation ullamco laboris nisi " +
			"ut aliquip ex ea commodo consequat. Duis aute " +
			"irure dolor in reprehenderit in voluptate velit " +
			"esse cillum dolore eu fugiat nulla pariatur. " +
			"Excepteur sint occaecat cupidatat non proident, " +
			"sunt in culpa qui officia deserunt mollit anim " +
			"id est laborum.";
	private static final String TEXT_2 = "Lorem ipsum dolor sit amet, " +
	"consectetur adipisicing elit";

	private static final int TOP_HEIGHT_EXPANDED = 300;
	private static final int BOTTOM_HEIGHT_EXPANDED = 100;
	private boolean collapsed = false;
	private JPanel topPanel;

	private JPanel bottomPanel;
	private JButton resizeButton;

	/**
	 * Create the panel.
	 */
	public SlideExamplePanel() {
		super();

		topPanel = new JPanel(new GridLayout());
		JTextPane textPane1 = new JTextPane();
		textPane1.setContentType("text/html");
		textPane1.setText(TEXT_1);
		textPane1.setEditable(false);
		textPane1.setBackground(topPanel.getBackground());
		topPanel.add(textPane1, BorderLayout.CENTER);

		bottomPanel = new JPanel(new BorderLayout());
		resizeButton = new JButton(new ResizeAction());
		resizeButton.setMargin(new Insets(5, 5, 5, 5));

		bottomPanel.add(resizeButton, BorderLayout.NORTH);
		JTextPane textPane2 = new JTextPane();
		textPane2.setContentType("text/html");
		textPane2.setText(TEXT_2);
		textPane2.setEditable(false);
		textPane2.setBackground(bottomPanel.getBackground());
		bottomPanel.add(textPane2, BorderLayout.CENTER);

		setLayout(null);

		topPanel.setBounds(0, 0, 300, TOP_HEIGHT_EXPANDED);
		bottomPanel.setBounds(0, 300, 300, BOTTOM_HEIGHT_EXPANDED);
		add(topPanel);
		add(bottomPanel);

		addComponentListener(new ComponentAdapter() {
			public void componentResized(ComponentEvent e) {
				Dimension size = getSize();
				int newTopPanelHeight =  (int)(size.getHeight() - bottomPanel.getHeight());
				int newBottomPanelHeight = bottomPanel.getHeight();
				int newBottomPanelY = newTopPanelHeight; 

				topPanel.setSize((int)size.getWidth(), newTopPanelHeight);
				bottomPanel.setBounds(0, newBottomPanelY, (int)size.getWidth(), newBottomPanelHeight);
			}
		});
	}

	/**
	 *
	 *
	 */
	private class ResizeAction extends AbstractAction {
		ResizeAction() {
			super("Collapse");
		}
		public void actionPerformed(ActionEvent e) {
			int currentHeight = getHeight();
			int prefBottomHeaderHeight = (int)resizeButton.getSize().getHeight();

			final int newTopHeight;
			if (collapsed) {
				newTopHeight = currentHeight - BOTTOM_HEIGHT_EXPANDED;
			} else {
				newTopHeight = (currentHeight - prefBottomHeaderHeight);
			}
			// Animation thread
			new Thread(new Runnable() {
				public void run() {
					int increment = collapsed ? -3 : 3;
					while (((int)topPanel.getHeight()) != newTopHeight) {

						topPanel.setSize(topPanel.getWidth(), topPanel.getHeight() + increment);
						bottomPanel.setBounds(0, bottomPanel.getY() + increment, bottomPanel.getWidth(), bottomPanel.getHeight() + increment);

						try {
							Thread.sleep(10);
						} catch (InterruptedException e) {}
					}
					collapsed = !collapsed;
					putValue(NAME, collapsed ? "Expand" : "Collapse");
				}
			}).start();
		}

	}
}

In de klasse-constructor wordt dezelfde componenten hiërarchie opgebouwd als in de Flex applicatie. In de ResizeAction klasse wordt de collapse/expand logica uitgevoerd. In deze implementatie wordt gebruikt gemaakt van de void setBounds(int,int,int,int) methode van Component, om het effect te bereiken, op in principe dezelfde methode als in de eerder getoonde Flex applicatie. Door in een Thread de setBounds methode met aangepaste waarden aan te roepen wordt er een animatie van gemaakt.

codebase="." archive="/roller/luminis/resource/flexvsjava/javavsflex.jar" width="400" height="400">

Conclusie

Door naar de bovenstaande voorbeelden te kijken wordt een al snel duidelijk dat beide platforms een rijk scala aan component effecten mogelijk maakt.

Een aspect springt echter meteen in het oog: de mate van complexiteit die vereist is bij de Java implementaties om een met Flex vergelijkbaar resultaat te krijgen. Het is allemaal wel mogelijk, maar het vraagt aardig wat inspanning van de ontwikkelaar.

Nu zijn er wel een hoop libraries beschikbaar die ‘t leven van een Java-ontwikkelaar een stuk makkelijker maken, maar feit is wel dat de Swing/AWT API’s duidelijk niet zijn geschreven met effect-rijke GUI in het oog.

Java sources: Download

,

  1. Nog geen reacties.
(wordt niet gepubliceerd)
  1. Nog geen trackbacks.