Membuat Animasi Di Swing Dengan Trident


Salah satu library yang sangat membantu dalam membuat animasi di Swing adalah Trident Java animation library.   File JAR untuk library ini dapat di-download di situs kenai.com/projects/trident/.   Kali ini saya akan mencoba menganimasikan tree fractal yang saya buat pada tulisan Menggambar Di Canvas Dengan GfxBuilder.  Seperti yang telah saya tuliskan, saya tidak menemukan cara yang fleksibel di GfxBuilder untuk memperbaharui tampilan animasi, sehingga saya akan membuat kode program Java 2D seperti biasa tanpa melalui builder.

Untuk memakai Trident, saya perlu meletakkan trident.jar yang saya download di folder lib di proyek Griffon.

Sebagai variasi, kali ini saya tidak akan menggunakan Line2D untuk menggambar setiap cabang, melainkan menggunakan CubicCurve2D.  Dengan demikian, tampilan cabang kini tidak lagi lurus, tetapi bisa berkelok seperti yang terlihat pada gambar berikut ini:

Tampilan Tree Fractal Dengan CubicCurve2D

Tampilan Tree Fractal Dengan CubicCurve2D

Kode program yang membentuk frame utama akan terlihat seperti berikut ini:

application(title: 'latihan',
        preferredSize: [500, 350],
        pack: true,
        locationByPlatform: true,
        iconImage: imageIcon('/griffon-icon-48x48.png').image) {

    borderLayout()
    TreeFractal treeFactal = widget(new TreeFractal(), constraints: CENTER)
    button("Tambah Level Kedalam", constraints: PAGE_END, actionPerformed: { evt ->
        treeFactal.increaseDepth()
    })
}

Pada kode program ini, saya akan menambahkan sebuah turuan JComponent yaitu TreeFractal yang berisi gambar tree factal melalui node widget().

Saya membuat TreeFractal dalam sebuah file Groovy terpisah yang isinya akan terlihat seperti berikut ini:

import org.pushingpixels.trident.Timeline
import org.pushingpixels.trident.ease.Spline
import org.pushingpixels.trident.swing.SwingRepaintTimeline

import javax.swing.*
import java.awt.*
import java.awt.geom.CubicCurve2D

class TreeFractal extends JComponent{

    Map cabang = [:]
    int depth = 0
    private Random random = new Random()
    private SwingRepaintTimeline repaintTimeline

    public TreeFractal() {
        repaintTimeline = new SwingRepaintTimeline(this)
        repaintTimeline.playLoop(Timeline.RepeatBehavior.LOOP)
    }

    @Override
    protected void paintComponent(Graphics g) {
        Graphics2D g2 = (Graphics2D) g
        g2.with {
            setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)

            setColor(Color.BLACK)
            fillRect(0, 0, getWidth(), getHeight())

            cabang.each { int curDepth, listCabang ->
                listCabang.each { Cabang c ->
                    setColor(new Color(97, Math.max(255,Math.min(144,141+curDepth*20)), 53, c.opacity))
                    setStroke(new BasicStroke(c.width))
                    draw(c.getCubicCurve())
                }
            }
        }
    }

    public void increaseDepth() {
        cabang[++depth] = []
        if (depth == 1) {
            cabang[depth] << buatCabang(200, 300, -90)
        } else {
            cabang[depth-1].each { Cabang c ->
                cabang[depth] << buatCabang(c.x2, c.y2, c.angle - random.nextInt(50))
                cabang[depth] << buatCabang(c.x2, c.y2, c.angle + random.nextInt(50))
            }
        }
    }

    private Cabang buatCabang(double x, double y, double angle) {
        def j = (15-depth) * 3.5
        def x2 = x + Math.cos(Math.toRadians(angle)) * j
        def y2 = y + Math.sin(Math.toRadians(angle)) * j
        double selangX = x2-x
        double selangY = y2-y
        Cabang c = new Cabang(x1:x, y1:y, x2: x2, y2: y2, opacity: 0,
            ctrlX1: x+random.nextDouble()*selangX+random.nextDouble()*15-7,
            ctrlY1: y+random.nextDouble()*selangY+random.nextDouble()*15-7,
            ctrlX2: x2-random.nextDouble()*selangX+random.nextDouble()*15-7,
            ctrlY2: y2-random.nextDouble()*selangY+random.nextDouble()*15-7,
            angle: angle, width: Math.max(1.0f, 13-depth*1.5))
        c.getTimeline().play()
        return c
    }

    class Cabang {

        double angle
        double x1, y1, ctrlX1, ctrlY1, ctrlX2, ctrlY2, x2, y2
        int opacity
        float width

        Timeline getTimeline() {
            Timeline t = new Timeline(this)
            t.with {
                addPropertyToInterpolate("x2", x1, x2)
                addPropertyToInterpolate("y2", y1, y2)
                addPropertyToInterpolate("ctrlX1", x1, ctrlX1)
                addPropertyToInterpolate("ctrlY1", y1, ctrlY1)
                addPropertyToInterpolate("ctrlX2", x1, ctrlX2)
                addPropertyToInterpolate("ctrlY2", y1, ctrlY2)
                addPropertyToInterpolate("width", 0.0f, width)
                addPropertyToInterpolate("opacity", 0, 255)
                setEase(new Spline(0.2f))
                setDuration(2500)
            }
            return t
        }

        CubicCurve2D getCubicCurve() {
            new CubicCurve2D.Double(x1, y1, ctrlX1, ctrlY1, ctrlX2, ctrlY2, x2, y2)
        }
    }
}

Pada kode program ini, terdapat sebuah inner-class Cabang yang mewakili setiap cabang dalam pohon.  Class ini sebenarnya lebih tepat diletakkan sebagai model tetapi saya letakkan disini untuk menghemat tempat😉  Di dalam class Cabang, terdapat method yang mengembalikan sebuah Timeline Trident.

Disini terlihat betapa gampangnya memakai Trident.   Untuk meng-animasikan setiap titik di cabang, saya tinggal memanggil addPropertyToInterpolate() dengan menyertakan atribut yang akan dianimasikan, nilai awal, dan nilai akhir.  Saya bisa meng-interpolasi  ‘nilai’ apa saja.  Misalnya, pada kode program di atas, saya juga meng-interpolasi nilai yang mewakili ukuran (dari kecil ke besar) dan transparansi (dari transparan menjadi terang).   Saya menyebut meng-interpolasi nilai bukan menganimasikan gambar, karena animasi terbentuk dari nilai-nilai yang di-interpolasi ini.

Selain itu, saya juga memberikan kode program setEase() untuk memberikan easing sehingga animasi tidak linear. Saya juga menentukan waktu durasi dengan memanggil setDuration().

Semua perubahan atau interpolasi saat ini hanya berlaku pada model!  Tidak akan ada yang berubah ditampilan.  Mengapa demikian?  Karena method paintComponent()  yang akan menghasilkan gambar berdasarkan nilai di model tidak dipanggil. Oleh sebab itu, saya juga memakai SwingRepaintTimeLine yang akan memperbaharui layar secara otomatis.  Saya mendeklarasikan SwingRepaintTimeLine sebagai variabel milik TreeFractal karena saya hanya butuh 1 instance saja yang terus memperbaharui layar selamanya (Timeline.RepeatBehavior.LOOP).   Hal ini berbeda dengan Timeline yang saya letakkan di dalam class Cabang karena setiap individu cabang masing-masing punya animasinya tersendiri.

Sekarang, bila saya menjalankan program, setiap kali men-klik tombol yang ada, maka cabang pohon akan tumbuh disertai dengan animasi, seperti yang terlihat pada animasi GIF berikut ini:

Animasi Tree Fractal Dengan Trident

Animasi Tree Fractal Dengan Trident

Perihal Solid Snake
I'm nothing...

Apa komentar Anda?

Please log in using one of these methods to post your comment:

Logo WordPress.com

You are commenting using your WordPress.com account. Logout / Ubah )

Gambar Twitter

You are commenting using your Twitter account. Logout / Ubah )

Foto Facebook

You are commenting using your Facebook account. Logout / Ubah )

Foto Google+

You are commenting using your Google+ account. Logout / Ubah )

Connecting to %s

%d blogger menyukai ini: