具有java2d或javaf口吃棒的字幕(ticker,文本滚动)

我正在尝试编写平滑的行情自动收录器(文本在屏幕上从右到左运行)。 这几乎是我想要的,但是仍然有些断断续续。我希望它像云在天空中一样平滑。 30年前,我用几行汇编代码进行管理,但在Java中失败了。

如果我提高速度(一次移动文本的像素数),情况会变得更糟。

是否缺少对屏幕刷新的某种同步?

编辑

我根据@camickr的说明更新了代码,以在专用的全屏窗口中启动该窗口,从而改善了视线。 我尝试过的其他事情:

  • 添加了应该考虑vsync的ExtendedBufferCapabilities
  • Toolkit.getDefaultToolkit()。sync();
  • 已启用的opengl
  • 尝试了游戏循环
  • 添加了一些调试信息

当我使用30 fps并仅将文本移动一个像素时,在4k显示器上看起来不错,但也很慢。一旦我将速度提高到2像素,它就会开始结结巴巴。

我开始认为使用java2d根本无法实现我的目标,我不得不转向一些opengl-library。

这是我的代码:

package scrolling;

import java.awt.AWTException;
import java.awt.BufferCapabilities;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.ImageCapabilities;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Toolkit;
import java.awt.event.actionEvent;
import java.awt.event.actionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;

import javax.swing.JFrame;
import javax.swing.Timer;

import sun.java2d.pipe.hw.ExtendedBufferCapabilities;

/**
 * A smooth scroll with green background to be used with a video cutter.
 * 
 * sun.java2d.pipe.hw.ExtendedBufferCapabilities is restricted https://stackoverflow.com/questions/25222811/access-restriction-the-type-application-is-not-api-restriction-on-required-l
 * 
 */
public class MyScroll extends JFrame implements actionListener {

    private int targetFps = 30;
    private boolean isOpenGl = false;
    private boolean isVsync = true;
    private boolean useGamingLoop = false;
    private int speed = 1;
    private String message;

    private int fontSize = 120;
    private Font theFont;
    private transient int leftEdge; // Offset from window's right edge to left edge
    private Color bgColor;
    private Color fgColor;
    private int winWidth;
    private int winHeight;
    private double position = 0.77;
    private FontMetrics fontMetrics;
    private int yPositionScroll;
    private boolean isFullScreen;
    private long lastTimerStart = 0;
    private BufferedImage img;
    private Graphics2D graphicsScroll;
    private GraphicsDevice currentScreenDevice = null;
    private int msgWidth = 0;
    private Timer scrollTimer;
    private boolean isRunning;

    /* gaming loop variables */
    private static final long NANO_IN_MILLI = 1000000L;
    // num of iterations with a sleep delay of 0ms before
    // game loop yields to other threads.
    private static final int NO_DELAYS_PER_YIELD = 16;
    // max num of renderings that can be skipped in one game loop,// game's internal state is updated but not rendered on screen.
    private static int MAX_RENDER_SKIPS = 5;
    // private long prevStatsTime;
    private long gameStartTime;
    private long curRenderTime;
    private long rendersSkipped = 0L;
    private long period; // period between rendering in nanosecs
    private long fps;
    private long frameCounter;
    private long lastFpsTime;

    public void init() {
        fontSize = getWidth() / 17;

        if (getGraphicsConfiguration().getBufferCapabilities().isPageFlipping()) {    
            try { // no pageflipping available with opengl
                BufferCapabilities cap = new BufferCapabilities(new ImageCapabilities(true),new ImageCapabilities(true),BufferCapabilities.FlipContents.BACKGROUND);
                // ExtendedBufferCapabilities is supposed to do a vsync
                ExtendedBufferCapabilities ebc = new ExtendedBufferCapabilities(cap,ExtendedBufferCapabilities.VSyncType.VSYNC_ON);
                createBufferStrategy(2,ebc);
            } catch (AWTException e) {
                e.printStackTrace();
            }

        } else {
            createBufferStrategy(2);
        }

        System.out.println(getDeviceConfigurationString(getGraphicsConfiguration()));

        message = "This is a test.   ";
        leftEdge = 0;
        theFont = new Font("Helvetica",Font.PLAIN,fontSize);
        bgColor = getBackground();
        fgColor = getForeground();
        winWidth = getSize().width - 1;
        winHeight = getSize().height;
        yPositionScroll = (int) (winHeight * position);

        initScrollImage();
    }

    /**
     * Draw the entire text to a buffered image to copy it to the screen later.
     */
    private void initScrollImage() {
        Graphics2D og = (Graphics2D) getBufferStrategy().getDrawGraphics();
        fontMetrics = og.getFontMetrics(theFont);
        Rectangle2D rect = fontMetrics.getStringBounds(message,og);
        img = new BufferedImage((int) rect.getWidth(),(int) rect.getHeight(),BufferedImage.TYPE_INT_ARGB);

        // At each frame,we get a reference on the rendering buffer graphics2d.
        // To handle concurrency,we 'cut' it into graphics context for each cube.
        graphicsScroll = img.createGraphics();
        graphicsScroll.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
        graphicsScroll.setBackground(Color.BLACK);

        graphicsScroll.setfont(theFont);
        graphicsScroll.setColor(bgColor);
        graphicsScroll.fillRect(0,img.getWidth(),img.getHeight()); // clear offScreen Image.
        graphicsScroll.setColor(fgColor);
        msgWidth = fontMetrics.stringWidth(message);

        graphicsScroll.setColor(Color.white);
        graphicsScroll.drawString(message,1,img.getHeight() - 10);

        // for better readability in front of an image draw an outline arround the text
        graphicsScroll.setColor(Color.black);
        graphicsScroll.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
        Font font = new Font("Helvetica",fontSize);
        graphicsScroll.translate(1,img.getHeight() - 10);
        FontRenderContext frc = graphicsScroll.getFontRenderContext();
        GlyphVector gv = font.createGlyphVector(frc,message);
        graphicsScroll.draw(gv.getOutline());
    }

    public void start() {
        scrollTimer = new Timer(1000 / targetFps,this);
        scrollTimer.setRepeats(true);
        scrollTimer.setCoalesce(true);
        scrollTimer.start();        
    }

    public void startGamingloop() {
        // loop initialization
        long beforeTime,afterTime,timeDiff,sleepTime;
        long overSleepTime = 0L;
        int noDelays = 0;
        long excess = 0L;
        gameStartTime = System.nanoTime();
        // prevStatsTime = gameStartTime;
        beforeTime = gameStartTime;

        period = (1000L * NANO_IN_MILLI) / targetFps; // rendering FPS (nanosecs/targetFPS)
        System.out.println("FPS: " + targetFps + ",vsync=");
        System.out.println("FPS period: " + period);

        // gaming loop http://www.javagaming.org/index.php/topic,19971.0.html
        while (true) {
            // **2) execute physics
            updateLeftEdge();

            // **1) execute drawing
            drawScroll();

            // Synchronise with the display hardware.
            // Flip the buffer
            if (!getBufferStrategy().contentsLost()) {
                getBufferStrategy().show();
            }
            if (isVsync) {
                Toolkit.getDefaultToolkit().sync();
            }

            afterTime = System.nanoTime();
            curRenderTime = afterTime;
            calculateFramesPerSecond();

            timeDiff = afterTime - beforeTime;
            sleepTime = (period - timeDiff) - overSleepTime;
            if (sleepTime > 0) { // time left in cycle
                // System.out.println("sleepTime: " + (sleepTime/NANO_IN_MILLI));
                try {
                    Thread.sleep(sleepTime / NANO_IN_MILLI);// nano->ms
                } catch (InterruptedException ex) {
                }
                overSleepTime = (System.nanoTime() - afterTime) - sleepTime;
            } else { // sleepTime <= 0;
                System.out.println("Rendering too slow");
                // this cycle took longer than period
                excess -= sleepTime;
                // store excess time value
                overSleepTime = 0L;
                if (++noDelays >= NO_DELAYS_PER_YIELD) {
                    Thread.yield();
                    // give another thread a chance to run
                    noDelays = 0;
                }
            }

            beforeTime = System.nanoTime();

            /*
             * If the rendering is taking too long,then update the game state without rendering it,to get the UPS nearer to the required frame rate.
             */
            int skips = 0;
            while ((excess > period) && (skips < MAX_RENDER_SKIPS)) {
                // update state but don’t render
                System.out.println("Skip renderFPS,run updateFPS");
                excess -= period;
                updateLeftEdge();
                skips++;
            }
            rendersSkipped += skips;
        }
    }

    private void calculateFramesPerSecond() {
        if (curRenderTime - lastFpsTime >= NANO_IN_MILLI * 1000) {
            fps = frameCounter;
            frameCounter = 0;
            lastFpsTime = curRenderTime;
        }
        frameCounter++;
    }

    public void setRunning(boolean isRunning) {
        this.isRunning = isRunning;
    }

    @Override
    public void actionPerformed(actionEvent e) {
        render();
    }

    private void render() {
        if (!isFullScreen) {
            repaint(0,yPositionScroll,winWidth,yPositionScroll + fontMetrics.getascent());
        } else {
            getBufferStrategy().show();
        }
        if (isVsync) {
            Toolkit.getDefaultToolkit().sync();
        }
        updateLeftEdge();
        drawScroll();
    }

    /**
     * Draws (part) of the prerendered image with text to a position in the screen defined by an increasing 
     * variable "leftEdge".
     * 
     * @return time drawing took. 
     */
    private long drawScroll() {
        long beforeDrawText = System.nanoTime();

        if (winWidth - leftEdge + msgWidth < 0) { // Current scroll entirely off-screen.
            leftEdge = 0;
        }
        int x = winWidth - leftEdge;
        int sourceWidth = Math.min(leftEdge,img.getWidth());
        Graphics2D og = (Graphics2D) getBufferStrategy().getDrawGraphics();
        try { // copy the pre drawn scroll to the screen
            og.drawImage(img.getSubimage(0,sourceWidth,img.getHeight()),x,null);
        } catch (Exception e) {
            System.out.println(e.getMessage() + " " + x + " " + sourceWidth);
        }
        long afterDrawText = System.nanoTime();
        return afterDrawText - beforeDrawText;
    }

    public static void main(String[] args) {
        MyScroll scroll = new MyScroll();

        System.setProperty("sun.java2d.opengl",String.valueOf(scroll.isOpenGl())); // enable opengl
        System.setProperty("sun.java2d.renderer.verbose","true");
        String renderer = "undefined";
        try {
            renderer = sun.java2d.pipe.RenderingEngine.getInstance().getclass().getName();
            System.out.println("Renderer " + renderer);
        } catch (Throwable th) {
            // may fail with JDK9 jigsaw (jake)
            if (false) {
                System.err.println("Unable to get RenderingEngine.getInstance()");
                th.printStackTrace();
            }
        }

        scroll.setBackground(Color.green);
        scroll.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
        GraphicsDevice[] screens = env.getScreenDevices();
        // I want the external monitor attached to my notebook
        GraphicsDevice device = screens[screens.length - 1]; 
        boolean isFullScreenSupported = device.isFullScreenSupported();

        scroll.setfullScreen(isFullScreenSupported);
        scroll.setUndecorated(isFullScreenSupported);
        scroll.setResizable(!isFullScreenSupported);

        if (isFullScreenSupported) {
            device.setfullScreenWindow(scroll);
            scroll.setIgnoreRepaint(true);
            scroll.validate();
        } else {
            // Windowed mode
            Rectangle r = GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds();
            scroll.setSize(r.width,r.height);
            scroll.pack();
            scroll.setExtendedState(JFrame.MAXIMIZED_BOTH);
            scroll.setVisible(true);
        }

        // exit on pressing escape
        scroll.addKeyListener(new KeyAdapter() {
            @Override
            public void keypressed(KeyEvent e) {
                if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
                    scroll.setRunning(false);
                    if(scroll.getScrollTimer() != null) {
                        scroll.getScrollTimer().stop();
                    }
                    system.exit(0);
                }
            }
        });

        scroll.setVisible(true);
        scroll.init();
        if (scroll.isUseGamingLoop()) {
            scroll.startGamingloop();
        } else {
            scroll.start();
        }

    }

    private void updateLeftEdge() {
        leftEdge += speed;
    }

    public Timer getScrollTimer() {
        return scrollTimer;
    }

    public void setfullScreen(boolean isFullScreen) {
        this.isFullScreen = isFullScreen;
    }

    public void setTargetFps(int targetFps) {
        this.targetFps = targetFps;
    }

    public void setOpenGl(boolean isOpenGl) {
        this.isOpenGl = isOpenGl;
    }

    public void setVsync(boolean isVsync) {
        this.isVsync = isVsync;
    }

    public void setUseGamingLoop(boolean useGamingLoop) {
        this.useGamingLoop = useGamingLoop;
    }

    public void setSpeed(int speed) {
        this.speed = speed;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    private String getDeviceConfigurationString(GraphicsConfiguration gc){
        return "Bounds: " + gc.getBounds() + "\n" + 
                "Buffer Capabilities: " + gc.getBufferCapabilities() + "\n" +
                "   Back Buffer Capabilities: " + gc.getBufferCapabilities().getBackBufferCapabilities() + "\n" +
                "      accelerated: " + gc.getBufferCapabilities().getBackBufferCapabilities().isaccelerated() + "\n" + 
                "      True Volatile: " + gc.getBufferCapabilities().getBackBufferCapabilities().isTrueVolatile() + "\n" +
                "   Flip Contents: " + gc.getBufferCapabilities().getFlipContents() + "\n" +
                "   Front Buffer Capabilities: " + gc.getBufferCapabilities().getFrontBufferCapabilities() + "\n" +
                "      accelerated: " + gc.getBufferCapabilities().getFrontBufferCapabilities().isaccelerated() + "\n" +
                "      True Volatile: " + gc.getBufferCapabilities().getFrontBufferCapabilities().isTrueVolatile() + "\n" +
                "   Is Full Screen Required: " + gc.getBufferCapabilities().isFullScreenRequired() + "\n" +
                "   Is MultiBuffer Available: " + gc.getBufferCapabilities().isMultiBufferAvailable() + "\n" +
                "   Is Page Flipping: " + gc.getBufferCapabilities().isPageFlipping() + "\n" +
                "Device: " + gc.getDevice() + "\n" +
                "   Available accelerated Memory: " + gc.getDevice().getavailableacceleratedMemory() + "\n" +
                "   ID String: " + gc.getDevice().getIDstring() + "\n" +
                "   Type: " + gc.getDevice().getType() + "\n" +
                "   Display Mode: " + gc.getDevice().getDisplayMode() + "\n" +              
                "Image Capabilities: " + gc.getImageCapabilities() + "\n" + 
                "      accelerated: " + gc.getImageCapabilities().isaccelerated() + "\n" + 
                "      True Volatile: " + gc.getImageCapabilities().isTrueVolatile() + "\n";        
    }

    public boolean isOpenGl() {
        return isOpenGl;
    }

    public boolean isUseGamingLoop() {
        return useGamingLoop;
    }    
}

图形功能的输出:

Renderer sun.java2d.pisces.PiscesRenderingEngine
Bounds: java.awt.Rectangle[x=3839,y=0,width=3840,height=2160]
Buffer Capabilities: sun.awt.X11GraphicsConfig$XDBECapabilities@68de145
   Back Buffer Capabilities: java.awt.ImageCapabilities@27fa135a
      accelerated: false
      True Volatile: false
   Flip Contents: undefined
   Front Buffer Capabilities: java.awt.ImageCapabilities@27fa135a
      accelerated: false
      True Volatile: false
   Is Full Screen Required: false
   Is MultiBuffer Available: false
   Is Page Flipping: true
Device: X11GraphicsDevice[screen=1]
   Available accelerated Memory: -1
   ID String: :0.1
   Type: 0
   Display Mode: java.awt.DisplayMode@1769
Image Capabilities: java.awt.ImageCapabilities@27fa135a
      accelerated: false
      True Volatile: false

编辑2: 我现在在javafx中编写了相同的代码,结果也一样,一旦我一次移动了3个以上的像素,它就会结结巴巴。

我正在Intel i9 9900K,Nvidia GeForce RTX 2060 Mobile,Ubuntu,OpenJdk 14上运行。

    package scrolling;

import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;

import javax.swing.Timer;

import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.stage.Screen;
import javafx.stage.Stage;

/**
 * A smooth scroll with green background to be used with a video cutter.
 * 
 * https://stackoverflow.com/questions/51478675/error-javafx-runtime-components-are-missing-and-are-required-to-run-this-appli
 * https://stackoverflow.com/questions/18547362/javafx-and-openjdk
 * 
 */
public class MyScroll extends Application {

    private boolean useGamingLoop = false;
    private int speed = 3;
    private String message;

    private transient double leftEdge; // Offset from window's right edge to left edge
    private Color bgColor;
    private Color fgColor;
    private double winWidth;
    private int winHeight;
    private double position = 0.77;
    private int yPositionScroll;
    private Image img;
    private int msgWidth = 0;
    private Timer scrollTimer;
    private boolean isRunning;
    private ImageView imageView;
    long lastUpdateTime;
    long lastIntervall;
    long nextIntervall;
    String ADAPTIVE_PULSE_PROP = "com.sun.scenario.animation.adaptivepulse";

    int frame = 0;
    long timeOflastFrameSwitch = 0; 
    @Override
    public void start(final Stage stage) {
        message = "This is a test.   ";
        leftEdge = 0;
        bgColor = Color.green;
        fgColor = Color.white;
        winWidth = (int)Screen.getPrimary().getBounds().getWidth();
        winHeight = (int)Screen.getPrimary().getBounds().getHeight();
        yPositionScroll = (int) (winHeight * position);

        initScrollImage(stage);

        stage.setfullScreenExitHint("");
        stage.setalwaysOnTop(true);


        new AnimationTimer() {
            @Override
            public void handle(long now) {
                 nextIntervall = now - lastUpdateTime;
                 System.out.println(lastIntervall - nextIntervall);
                 lastUpdateTime = System.nanoTime();
                 drawScroll(stage);
                 lastIntervall = nextIntervall;
            }
        }.start();

        //Creating a Group object  
        Group root = new Group(imageView);  
        //Creating a scene object 
        Scene scene = new Scene(root);  
        //Adding scene to the stage 
        stage.setScene(scene);
        stage.show();
        stage.setfullScreen(true);        
    }


    /**
     * Draw the entire text to an imageview and add to the scene.
     */
    private void initScrollImage(Stage stage) {
        int fontSize = (int)winWidth / 17;

        Font theFont = new Font("Helvetica",fontSize);
        BufferedImage tempImg = new BufferedImage((int)winWidth,winHeight,BufferedImage.TYPE_INT_ARGB);
        FontMetrics fontMetrics = tempImg.getGraphics().getFontMetrics(theFont);
        Rectangle2D rect = fontMetrics.getStringBounds(message,tempImg.getGraphics());
        msgWidth = fontMetrics.stringWidth(message);

        BufferedImage bufferedImage = new BufferedImage((int) rect.getWidth(),BufferedImage.TYPE_INT_ARGB);

        Graphics2D graphicsScroll = bufferedImage.createGraphics();
        graphicsScroll.setRenderingHint(RenderingHints.KEY_ANTIALIASING,bufferedImage.getWidth(),bufferedImage.getHeight()); // set background color
        graphicsScroll.setColor(fgColor);
        graphicsScroll.drawString(message,bufferedImage.getHeight() - 10);

        // for better readability in front of an image draw an outline arround the text
        graphicsScroll.setColor(Color.black);
        graphicsScroll.setRenderingHint(RenderingHints.KEY_ANTIALIASING,bufferedImage.getHeight() - 10);
        FontRenderContext frc = graphicsScroll.getFontRenderContext();
        GlyphVector gv = font.createGlyphVector(frc,message);
        graphicsScroll.draw(gv.getOutline());

        img = SwingFXUtils.toFXImage(bufferedImage,null);

        imageView = new ImageView(img); 
        imageView.setSmooth(false);
        imageView.setCache(true);

        //Setting the preserve ratio of the image view 
        imageView.setPreserveRatio(true);  

        tempImg.flush();
    }


    /**
     * Draws (part) of the prerendered image with text to a position in the screen defined by an increasing 
     * variable "leftEdge".
     * 
     * @return time drawing took. 
     */
    private void drawScroll(Stage stage) {
        leftEdge += speed;
        if (winWidth - leftEdge + msgWidth < 0) { // Current scroll entirely off-screen.
            leftEdge = 0;
        }
//        imageView.relocate(winWidth - leftEdge,yPositionScroll);
        imageView.setX(winWidth - leftEdge);
    }

    public static void main(String[] args) {
//      System.setProperty("sun.java2d.opengl","true");
        System.setProperty("prism.vsync","true");
//      System.setProperty("com.sun.scenario.animation.adaptivepulse","true");
        System.setProperty("com.sun.scenario.animation.vsync","true");

        launch(args);
    }

    public Timer getScrollTimer() {
        return scrollTimer;
    }

    public void setSpeed(int speed) {
        this.speed = speed;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public boolean isUseGamingLoop() {
        return useGamingLoop;
    }


    public void setRunning(boolean isRunning) {
        this.isRunning = isRunning;
    }    
}
iCMS 回答:具有java2d或javaf口吃棒的字幕(ticker,文本滚动)

我正在Windows 10上运行它。我认为它不能在Unix系统上很好地工作。

Marquee GUI 1

...

Marquee GUI 2

该代码由5个包中的9个类组成。程序包名称在代码中。

字幕类

package com.ggl.marquee.controller;

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

import javax.swing.JTextField;

import com.ggl.marquee.model.MarqueeModel;
import com.ggl.marquee.view.MarqueeFrame;

public class CreateMarqueeActionListener implements ActionListener {

    private JTextField field;

    private MarqueeFrame frame;

    private MarqueeModel model;

    public CreateMarqueeActionListener(MarqueeFrame frame,MarqueeModel model,JTextField field) {
        this.frame = frame;
        this.model = model;
        this.field = field;
    }

    @Override
    public void actionPerformed(ActionEvent event) {
        model.stopDtpRunnable();
        model.resetPixels();

        String s = field.getText().trim();
        if (s.equals("")) {
            frame.repaintMarqueePanel();
            return;
        }

        s = " " + s + "    ";
        model.setTextPixels(model.getDefaultFont().getTextPixels(s));
        frame.repaintMarqueePanel();
    }

}

CreateMarqueeActionListener类

package com.ggl.marquee.controller;

import javax.swing.DefaultListSelectionModel;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

import com.ggl.marquee.model.MarqueeFont;
import com.ggl.marquee.model.MarqueeModel;

public class FontSelectionListener implements ListSelectionListener {

    private MarqueeModel model;

    public FontSelectionListener(MarqueeModel model) {
        this.model = model;
    }

    @Override
    public void valueChanged(ListSelectionEvent event) {
        DefaultListSelectionModel selectionModel = (DefaultListSelectionModel) event
                .getSource();
        if (!event.getValueIsAdjusting()) {
            int index = selectionModel.getMinSelectionIndex();
            if (index >= 0) {
                MarqueeFont font = model.getDefaultListModel().get(index);
                model.setDefaultFont(font);
            }
        }
    }

}

FontSelectionListener类

package com.ggl.marquee.model;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class FontGenerator {

    private static final boolean DEBUG = false;

    private Font font;

    private FontHeights fontHeights;

    private Map<Character,MarqueeCharacter> characterMap;

    public FontGenerator(Font font) {
        this.font = font;
        this.characterMap = new HashMap<Character,MarqueeCharacter>();
    }

    public void execute() {
        int width = 50;
        BufferedImage bi = generateCharacterImage(width,"B");
        int[] result1 = getCharacterHeight(bi);
        bi = generateCharacterImage(width,"g");
        int[] result2 = getCharacterHeight(bi);
        fontHeights = new FontHeights(result1[0],result1[1],result2[1]);

        if (DEBUG) System.out.println(fontHeights.getAscender() + ","
                + fontHeights.getBaseline() + ","
                + fontHeights.getDescender());

        for (int x = 32; x < 127; x++) {
            char c = (char) x;

            StringBuilder builder = new StringBuilder(3);
            builder.append('H');
            builder.append(c);
            builder.append('H');

            bi = generateCharacterImage(width,builder.toString());
            int[][] pixels = convertTo2D(bi);
            MarqueeCharacter mc = getCharacterPixels(pixels);

            if (DEBUG) {
                System.out.println(builder.toString() + " " +
                        mc.getWidth() + "x" + mc.getHeight());
            }

            characterMap.put(c,mc);
        }

    }

    private BufferedImage generateCharacterImage(int width,String string) {
        BufferedImage bi = new BufferedImage(
                width,width,BufferedImage.TYPE_INT_RGB);
        Graphics g = bi.getGraphics();
        g.setFont(font);

        g.setColor(Color.WHITE);
        g.fillRect(0,width);

        g.setColor(Color.BLACK);
        g.drawString(string,width / 2);

        return bi;
    }

    private int[] getCharacterHeight(BufferedImage bi) {
        int[][] pixels = convertTo2D(bi);
        int minHeight = bi.getHeight();
        int maxHeight = 0;

        for (int i = 0; i < pixels.length; i++) {
            for (int j = 0; j < pixels[i].length; j++) {
                if (pixels[i][j] < -1) {
                    minHeight = Math.min(i,minHeight);
                    maxHeight = Math.max(i,maxHeight);
                }
            }
        }

        int[] result = new int[2];
        result[0] = minHeight;
        result[1] = maxHeight;
        return result;
    }

    private MarqueeCharacter getCharacterPixels(int[][] pixels) {
        List<Boolean[]> list = new ArrayList<Boolean[]>();
        int startRow = fontHeights.getAscender();
        int endRow = fontHeights.getDescender();
        int height = fontHeights.getCharacterHeight();
        int startColumn = getCharacterColumnStart(pixels);
        int endColumn = getCharacterColumnEnd(pixels);

        for (int i = startColumn; i <= endColumn; i++) {
            Boolean[] characterColumn = new Boolean[height];
            int k = 0;
            for (int j = startRow; j <= endRow; j++) {
                if (pixels[j][i] < -1) characterColumn[k] = true;
                else characterColumn[k] = false;
                k++;
            }
            list.add(characterColumn);
        }

        MarqueeCharacter mc = new MarqueeCharacter(list.size(),height);

        for (int i = 0; i < list.size(); i++) {
            Boolean[] characterColumn = list.get(i);
            mc.setColumn(characterColumn);
        }

        return mc;
    }

    private int getCharacterColumnStart(int[][] pixels) {
        int start = fontHeights.getAscender();
        int end = fontHeights.getBaseline();
        int letterEndFlag = 0;
        int column = 1;

        while (letterEndFlag < 1) {
            boolean pixelDetected = false;
            for (int i = start; i <= end; i++) {
                if (pixels[i][column] < -1) {
                    pixelDetected = true;
                }
            }

            column++;

            // End of first letter
            if ((letterEndFlag == 0) && !pixelDetected) letterEndFlag = 1;
        }

        return column;
    }

    private int getCharacterColumnEnd(int[][] pixels) {
        int start = fontHeights.getAscender();
        int end = fontHeights.getBaseline();
        int height = fontHeights.getCharacterHeight2();
        int letterEndFlag = 0;
        int column = pixels.length - 1;

        while (letterEndFlag < 4) {
            int pixelCount = 0;
            for (int i = start; i <= end; i++) {
                if (pixels[i][column] < -1) {
                    pixelCount++;
                }
            }

            column--;

            // End of first letter
            if (pixelCount >= height) letterEndFlag++;
            // Start of first letter
//          if ((letterEndFlag == 0) && (pixelCount > 0)) letterEndFlag = 1;
        }

        return column;
    }

    private int[][] convertTo2D(BufferedImage image) {
        final int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer())
                .getData();
        final int width = image.getWidth();
        final int height = image.getHeight();

        int[][] result = new int[height][width];

        for (int pixel = 0,row = 0,col = 0; pixel < pixels.length; pixel++) {
            result[row][col] = pixels[pixel];
            col++;
            if (col == width) {
                col = 0;
                row++;
            }
        }

        return result;
    }

    public MarqueeCharacter getCharacter(Character c) {
        MarqueeCharacter mc = characterMap.get(c);
        return (mc == null) ? characterMap.get('?') : mc;
    }

    public int getCharacterHeight() {
        return fontHeights.getCharacterHeight();
    }
}

FontGenerator类

package com.ggl.marquee.model;

public class FontHeights {

    private final int ascender;
    private final int baseline;
    private final int descender;

    public FontHeights(int ascender,int baseline,int descender) {
        this.ascender = ascender;
        this.baseline = baseline;
        this.descender = descender;
    }

    public int getCharacterHeight() {
        return descender - ascender + 1;
    }

    public int getCharacterHeight2() {
        return baseline - ascender + 1;
    }

    public int getAscender() {
        return ascender;
    }

    public int getBaseline() {
        return baseline;
    }

    public int getDescender() {
        return descender;
    }

}

FontHeights类

package com.ggl.marquee.model;

import java.security.InvalidParameterException;

public class MarqueeCharacter {

    private static int columnCount;

    private int height;
    private int width;

    private boolean[][] pixels;

    public MarqueeCharacter(int width,int height) {
        this.width = width;
        this.height = height;
        this.pixels = new boolean[width][height];
        columnCount = 0;
    }

    public void setColumn(Boolean[] value) {
        int height = value.length;
        if (this.height != height) {
            String s = "The number of values must equal the column height - "
                    + this.height;
            throw new InvalidParameterException(s);
        }

        for (int i = 0; i < height; i++) {
            pixels[columnCount][i] = value[i];
        }

        columnCount++;
    }

    public boolean[][] getPixels() {
        return pixels;
    }

    public boolean isComplete() {
        return (width == columnCount);
    }

    public int getHeight() {
        return height;
    }

    public int getWidth() {
        return width;
    }

}

MarqueeCharacter类

package com.ggl.marquee.model;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.font.FontRenderContext;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;

public class MarqueeFont {

    private static final boolean DEBUG = false;

    private int fontHeight;

    private Font font;

    public MarqueeFont(Font font) {
        this.font = font;

        FontRenderContext frc = new FontRenderContext(null,true,true);
        Rectangle2D r2D = font.getStringBounds("HgH",frc);
        this.fontHeight = (int) Math.round(r2D.getHeight());

        if (DEBUG) {
            System.out.println(font.getFamily() + " " + fontHeight + " pixels");
        }
    }

    public boolean[][] getTextPixels(String s) {
        FontRenderContext frc = new FontRenderContext(null,true);

        Rectangle2D r2D = font.getStringBounds(s,frc);
        int rWidth = (int) Math.round(r2D.getWidth());
        int rHeight = (int) Math.round(r2D.getHeight());
        int rX = (int) Math.round(r2D.getX());
        int rY = (int) Math.round(r2D.getY());

        if (DEBUG) {
            System.out.print(s);
            System.out.print(",rWidth = " + rWidth);
            System.out.print(",rHeight = " + rHeight);
            System.out.println(",rX = " + rX + ",rY = " + rY);
        }

        BufferedImage bi = generateCharacterImage(rX,-rY,rWidth,rHeight,s);
        int[][] pixels = convertTo2D(bi);

        if (DEBUG) {
            displayPixels(pixels);
        }

        return createTextPixels(pixels);
    }

    private BufferedImage generateCharacterImage(int x,int y,int width,int height,String string) {
        BufferedImage bi = new BufferedImage(width,height,height);

        g.setColor(Color.BLACK);
        g.drawString(string,x,y);

        return bi;
    }

    private int[][] convertTo2D(BufferedImage image) {
        final int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer())
                .getData();
        final int width = image.getWidth();
        final int height = image.getHeight();

        int[][] result = new int[height][width];

        int row = 0;
        int col = 0;
        for (int pixel = 0; pixel < pixels.length; pixel++) {
            result[row][col] = pixels[pixel];
            col++;
            if (col == width) {
                col = 0;
                row++;
            }
        }

        return result;
    }

    private void displayPixels(int[][] pixels) {
        for (int i = 0; i < pixels.length; i++) {
            String s = String.format("%03d",(i + 1));
            System.out.print(s + ". ");
            for (int j = 0; j < pixels[i].length; j++) {
                if (pixels[i][j] == -1) {
                    System.out.print("  ");
                } else {
                    System.out.print("X ");
                }
            }
            System.out.println("");
        }
    }

    private boolean[][] createTextPixels(int[][] pixels) {
        // The int array pixels is in column,row order.
        // We have to flip the array and produce the output
        // in row,column order.
        if (DEBUG) {
            System.out.println(pixels[0].length + "x" + pixels.length);
        }
        boolean[][] textPixels = new boolean[pixels[0].length][pixels.length];
        for (int i = 0; i < pixels.length; i++) {
            for (int j = 0; j < pixels[i].length; j++) {
                if (pixels[i][j] == -1) {
                    textPixels[j][i] = false;
                } else {
                    textPixels[j][i] = true;
                }
            }
        }

        return textPixels;
    }

    public Font getFont() {
        return font;
    }

    public int getFontHeight() {
        return fontHeight;
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append(font.getFamily());
        builder.append(",");
        builder.append(getStyleText());
        builder.append(",");
        builder.append(font.getSize());
        builder.append(" pixels");

        return builder.toString();
    }

    private StringBuilder getStyleText() {
        StringBuilder builder = new StringBuilder();
        int style = font.getStyle();
        if (style == Font.PLAIN) {
            builder.append("normal");
        } else if (style == Font.BOLD) {
            builder.append("bold");
        } else if (style == Font.ITALIC) {
            builder.append("italic");
        } else if (style == (Font.BOLD + Font.ITALIC)) {
            builder.append("bold italic");
        } else {
            builder.append("unknown style");
        }
        return builder;
    }

}

MarqueeFont类

package com.ggl.marquee.model;

import java.awt.Font;

import javax.swing.DefaultListModel;

public class MarqueeFontFactory {

    private DefaultListModel<MarqueeFont> fontList;

    private MarqueeFont defaultFont;

    public MarqueeFontFactory() {
        this.fontList = new DefaultListModel<MarqueeFont>();
        addElements();
    }

    private void addElements() {
        this.defaultFont = new MarqueeFont(new Font("Arial",Font.BOLD,16));
        fontList.addElement(defaultFont);
        fontList.addElement(new MarqueeFont(new Font("Cambria",16)));
        fontList.addElement(new MarqueeFont(new Font("Courier New",16)));
        fontList.addElement(new MarqueeFont(new Font("Georgia",16)));
        fontList.addElement(new MarqueeFont(new Font("Lucida Calligraphy",16)));
        fontList.addElement(new MarqueeFont(new Font("Times New Roman",16)));
        fontList.addElement(new MarqueeFont(new Font("Verdana",16)));
    }

    public DefaultListModel<MarqueeFont> getFontList() {
        return fontList;
    }

    public void setDefaultFont(MarqueeFont defaultFont) {
        this.defaultFont = defaultFont;
    }

    public MarqueeFont getDefaultFont() {
        return defaultFont;
    }

    public int getCharacterHeight() {
        int maxHeight = 0;
        for (int i = 0; i < fontList.getSize(); i++) {
            MarqueeFont font = fontList.get(i);
            int height = font.getFontHeight();
            maxHeight = Math.max(height,maxHeight);
        }
        return maxHeight;
    }
}

MarqueeFontFactory类

package com.ggl.marquee.model;

import javax.swing.DefaultListModel;

import com.ggl.marquee.runnable.DisplayTextPixelsRunnable;
import com.ggl.marquee.view.MarqueeFrame;

public class MarqueeModel {

    private static final int marqueeWidth = 120;

    private boolean[][] marqueePixels;
    private boolean[][] textPixels;

    private DisplayTextPixelsRunnable dtpRunnable;

    private MarqueeFontFactory fonts;

    private MarqueeFrame frame;

    public MarqueeModel() {
        this.fonts = new MarqueeFontFactory();
        this.marqueePixels = new boolean[marqueeWidth][getMarqueeHeight()];
    }

    public void setFrame(MarqueeFrame frame) {
        this.frame = frame;
    }

    public MarqueeFontFactory getFonts() {
        return fonts;
    }

    public DefaultListModel<MarqueeFont> getDefaultListModel() {
        return fonts.getFontList();
    }

    public MarqueeFont getDefaultFont() {
        return fonts.getDefaultFont();
    }

    public void setDefaultFont(MarqueeFont defaultFont) {
        fonts.setDefaultFont(defaultFont);
    }

    public boolean[][] getMarqueePixels() {
        return marqueePixels;
    }

    public boolean getMarqueePixel(int width,int height) {
        return marqueePixels[width][height];
    }

    public int getMarqueeWidth() {
        return marqueeWidth;
    }

    public int getMarqueeHeight() {
        return fonts.getCharacterHeight();
    }

    public boolean[][] getTextPixels() {
        return textPixels;
    }

    public int getTextPixelWidth() {
        return textPixels.length;
    }

    private void startDtpRunnable() {
        dtpRunnable = new DisplayTextPixelsRunnable(frame,this);
        new Thread(dtpRunnable).start();
    }

    public void stopDtpRunnable() {
        if (dtpRunnable != null) {
            dtpRunnable.stopDisplayTextPixelsRunnable();
            dtpRunnable = null;
        }
    }

    public void setTextPixels(boolean[][] textPixels) {
        this.textPixels = textPixels;
        if (textPixels.length < getMarqueeWidth()) {
            this.marqueePixels = copyCharacterPixels(0,textPixels,marqueePixels);
        } else {
            startDtpRunnable();
        }
    }

    public void resetPixels() {
        for (int i = 0; i < getMarqueeWidth(); i++) {
            for (int j = 0; j < getMarqueeHeight(); j++) {
                marqueePixels[i][j] = false;
            }
        }
    }

    public void setAllPixels() {
        for (int i = 0; i < getMarqueeWidth(); i++) {
            for (int j = 0; j < getMarqueeHeight(); j++) {
                marqueePixels[i][j] = true;
            }
        }
    }

    public boolean[][] copyCharacterPixels(int position,boolean[][] characterPixels,boolean[][] textPixels) {
        for (int i = 0; i < characterPixels.length; i++) {
            for (int j = 0; j < characterPixels[i].length; j++) {
                textPixels[i + position][j] = characterPixels[i][j];
            }
        }

        return textPixels;
    }

    public void copyTextPixels(int position) {
        for (int i = 0; i < marqueePixels.length; i++) {
            int k = i + position;
            k %= textPixels.length;
            for (int j = 0; j < textPixels[i].length; j++) {
                marqueePixels[i][j] = textPixels[k][j];
            }
        }
    }

}

MarqueeModel类

package com.ggl.marquee.runnable;

import javax.swing.SwingUtilities;

import com.ggl.marquee.model.MarqueeModel;
import com.ggl.marquee.view.MarqueeFrame;

public class DisplayAllPixelsRunnable implements Runnable {

    private MarqueeFrame frame;

    private MarqueeModel model;

    public DisplayAllPixelsRunnable(MarqueeFrame frame,MarqueeModel model) {
        this.frame = frame;
        this.model = model;
    }

    @Override
    public void run() {
        model.setAllPixels();
        repaint();

        try {
            Thread.sleep(3000L);
        } catch (InterruptedException e) {

        }

        model.resetPixels();
        repaint();
    }

    private void repaint() {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                frame.repaintMarqueePanel();
            }
        });
    }

}

DisplayAllPixelsRunnable类

package com.ggl.marquee.runnable;

import javax.swing.SwingUtilities;

import com.ggl.marquee.model.MarqueeModel;
import com.ggl.marquee.view.MarqueeFrame;

public class DisplayTextPixelsRunnable implements Runnable {

    private static int textPixelPosition;

    private volatile boolean running;

    private MarqueeFrame frame;

    private MarqueeModel model;

    public DisplayTextPixelsRunnable(MarqueeFrame frame,MarqueeModel model) {
        this.frame = frame;
        this.model = model;
        textPixelPosition = 0;
    }

    @Override
    public void run() {
        this.running = true;
        while (running) {
            model.copyTextPixels(textPixelPosition);
            repaint();
            sleep();
            textPixelPosition++;
            textPixelPosition %= model.getTextPixelWidth();
        }
    }

    private void repaint() {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                frame.repaintMarqueePanel();
            }
        });
    }

    private void sleep() {
        try {
            Thread.sleep(50L);
        } catch (InterruptedException e) {

        }
    }

    public synchronized void stopDisplayTextPixelsRunnable() {
        this.running = false;
    }

}

DisplayTextPixelsRunnable类

package com.ggl.marquee.view;

import java.awt.BorderLayout;

import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;

import com.ggl.marquee.controller.CreateMarqueeActionListener;
import com.ggl.marquee.controller.FontSelectionListener;
import com.ggl.marquee.model.MarqueeFont;
import com.ggl.marquee.model.MarqueeModel;

public class ControlPanel {

    private JButton submitButton;

    private JPanel panel;

    private MarqueeFrame frame;

    private MarqueeModel model;

    public ControlPanel(MarqueeFrame frame,MarqueeModel model) {
        this.frame = frame;
        this.model = model;
        createPartControl();
    }

    private void createPartControl() {
        panel = new JPanel();
        panel.setLayout(new BoxLayout(panel,BoxLayout.PAGE_AXIS));

        JPanel fontPanel = new JPanel();
        fontPanel.setLayout(new BorderLayout());

        JLabel fontLabel = new JLabel("Fonts");
        fontPanel.add(fontLabel,BorderLayout.NORTH);

        JList<MarqueeFont> fontList = new JList<MarqueeFont>(
                model.getDefaultListModel());
        fontList.setSelectedValue(model.getDefaultFont(),true);
        fontList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        fontList.setVisibleRowCount(3);

        ListSelectionModel listSelectionModel = fontList.getSelectionModel();
        listSelectionModel.addListSelectionListener(new FontSelectionListener(
                model));

        JScrollPane fontScrollPane = new JScrollPane(fontList);

        fontPanel.add(fontScrollPane,BorderLayout.CENTER);

        panel.add(fontPanel);

        JPanel fieldPanel = new JPanel();

        JLabel fieldLabel = new JLabel("Marquee Text: ");
        fieldPanel.add(fieldLabel);

        JTextField field = new JTextField(30);
        fieldPanel.add(field);

        panel.add(fieldPanel);

        JPanel buttonPanel = new JPanel();

        submitButton = new JButton("Submit");
        submitButton.addActionListener(new CreateMarqueeActionListener(frame,model,field));
        buttonPanel.add(submitButton);

        panel.add(buttonPanel);
    }

    public JPanel getPanel() {
        return panel;
    }

    public JButton getSubmitButton() {
        return submitButton;
    }

}

ControlPanel类

package com.ggl.marquee.view;

import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.JPanel;

import com.ggl.marquee.model.MarqueeModel;
import com.ggl.marquee.runnable.DisplayAllPixelsRunnable;

public class MarqueeFrame {

    private ControlPanel controlPanel;

    private DisplayAllPixelsRunnable dapRunnable;

    private JFrame frame;

    private MarqueeModel model;

    private MarqueePanel marqueePanel;

    public MarqueeFrame(MarqueeModel model) {
        this.model = model;
        model.setFrame(this);
        createPartControl();
    }

    private void createPartControl() {
        frame = new JFrame();
        // frame.setIconImage(getFrameImage());
        frame.setTitle("Marquee");
        frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
        frame.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent event) {
                exitProcedure();
            }
        });

        JPanel mainPanel = new JPanel();
        mainPanel.setLayout(new BoxLayout(mainPanel,BoxLayout.PAGE_AXIS));

        marqueePanel = new MarqueePanel(model);
        mainPanel.add(marqueePanel);
        controlPanel = new ControlPanel(this,model);
        mainPanel.add(controlPanel.getPanel());

        frame.add(mainPanel);
        frame.pack();
        frame.setLocationByPlatform(true);
        frame.getRootPane().setDefaultButton(controlPanel.getSubmitButton());
        frame.setVisible(true);

        dapRunnable = new DisplayAllPixelsRunnable(this,model);
        new Thread(dapRunnable).start();
    }

    private void exitProcedure() {
        frame.dispose();
        System.exit(0);
    }

    public void repaintMarqueePanel() {
        marqueePanel.repaint();
    }

}

MarqueeFrame类

package com.ggl.marquee.view;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;

import javax.swing.JPanel;

import com.ggl.marquee.model.MarqueeModel;

public class MarqueePanel extends JPanel {

    private static final long serialVersionUID = -1677343084333836763L;

    private static final int pixelWidth = 4;
    private static final int gapWidth = 2;
    private static final int totalWidth = pixelWidth + gapWidth;
    private static final int yStart = gapWidth + totalWidth + totalWidth;

    private MarqueeModel model;

    public MarqueePanel(MarqueeModel model) {
        this.model = model;

        int width = model.getMarqueeWidth() * totalWidth + gapWidth;
        int height = model.getMarqueeHeight() * totalWidth + yStart + yStart;
        setPreferredSize(new Dimension(width,height));
    }

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

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

        int x = gapWidth;
        int y = yStart;

        for (int i = 0; i < model.getMarqueeWidth(); i++) {
            for (int j = 0; j < model.getMarqueeHeight(); j++) {
                if (model.getMarqueePixel(i,j)) {
                    g.setColor(Color.PINK);
                } else {
                    g.setColor(Color.BLACK);
                }

                g.fillRect(x,y,pixelWidth,pixelWidth);
                y += totalWidth;
            }
            y = yStart;
            x += totalWidth;
        }
    }

}

MarqueePanel类

var queue = new ConcurrentQueue<int>();

queue.Enqueue(1);
queue.Enqueue(2);

if (queue.TryPeek(out int firstValue))
    Console.WriteLine("Peek: " + firstValue);

if (queue.TryDequeue(out int result))
    Console.WriteLine("Dequeue: " + result);
,

我对@Gilbert Le Blanc发布的同一示例(Marquee)进行了一些更改。

首先,在选取框示例中,使用Thread.sleep()方法代替了带有TimerTask的更现代的方法。对于Thread.sleep()和TimerTask之间有多少区别,我不能太强调。

Thread.sleep()不正确。错误的程度取决于基础操作系统及其计时器和调度程序。我经历过并行进行垃圾收集会导致睡眠过多。 来源:src

这导致了一个重要的问题

绘画功能被称为不一致,而不是30fps的预期固定时间(大约40ms),这会产生一些模板问题。
资料来源:我

这可以通过TimerTask方法部分解决

在至少一个主要操作系统(Windows)上,计时器的阻塞比休眠具有更好的精度和可靠性。
来源:GameDev.net

当我重写相同的示例但使用timerTask时,我注意到了巨大的进步,它肯定比使用您提供的示例(2D Swing)更流畅;

我对其进行了如下比较,Marquee示例的速度与您进行2D挥杆测试的速度大致相同,即10,因此请尝试使用int speed = 10;和上面提供的速度进行测试,看看是否快门或多或少。

或者,您可以尝试使用TimerTask运行本机的Marquee Example和新的Marquee Example,并且应该看到一个重要的主要区别。

我认为用这种方法实现缓冲策略是获得真正流畅的滚动体验的方法...否则,总会有OpenGL

我更改的课程:

DisplayTextPixelsRunnable 现在为 DisplayTextPixelsTimerTask

package com.ggl.marquee.runnable;

import javax.swing.SwingUtilities;

import com.ggl.marquee.model.MarqueeModel;
import com.ggl.marquee.view.MarqueeFrame;

import java.util.TimerTask;

public class DisplayTextPixelsTimerTask extends TimerTask {

    private static int textPixelPosition;

    private final MarqueeFrame frame;

    private final MarqueeModel model;

    public DisplayTextPixelsTimerTask(MarqueeFrame frame,MarqueeModel model) {
        this.frame = frame;
        this.model = model;
        textPixelPosition = 0;
    }

    @Override
    public void run() {
        model.copyTextPixels(textPixelPosition);
        repaint();
        textPixelPosition++;
        textPixelPosition %= model.getTextPixelWidth();
    }

    private void repaint() {
        SwingUtilities.invokeLater(frame::repaintMarqueePanel);
    }

}

MarqueeModel

package com.ggl.marquee.model;

import javax.swing.DefaultListModel;

import com.ggl.marquee.runnable.DisplayTextPixelsTimerTask;
import com.ggl.marquee.view.MarqueeFrame;

import java.util.Timer;
import java.util.TimerTask;

public class MarqueeModel {

    private static final int marqueeWidth = 120;

    private static final long FPS_TARGET = 30;
    private static final long DELAY_TIME = (long) (1000.0d / FPS_TARGET);

    private boolean[][] marqueePixels;
    private boolean[][] textPixels;

    private TimerTask dtpRunnable;

    private MarqueeFontFactory fonts;

    private MarqueeFrame frame;

    public MarqueeModel() {
        this.fonts = new MarqueeFontFactory();
        this.marqueePixels = new boolean[marqueeWidth][getMarqueeHeight()];
    }

    public void setFrame(MarqueeFrame frame) {
        this.frame = frame;
    }

    public MarqueeFontFactory getFonts() {
        return fonts;
    }

    public DefaultListModel<MarqueeFont> getDefaultListModel() {
        return fonts.getFontList();
    }

    public MarqueeFont getDefaultFont() {
        return fonts.getDefaultFont();
    }

    public void setDefaultFont(MarqueeFont defaultFont) {
        fonts.setDefaultFont(defaultFont);
    }

    public boolean[][] getMarqueePixels() {
        return marqueePixels;
    }

    public boolean getMarqueePixel(int width,int height) {
        return marqueePixels[width][height];
    }

    public int getMarqueeWidth() {
        return marqueeWidth;
    }

    public int getMarqueeHeight() {
        return fonts.getCharacterHeight();
    }

    public boolean[][] getTextPixels() {
        return textPixels;
    }

    public int getTextPixelWidth() {
        return textPixels.length;
    }

    private void startDtpRunnable() {
        dtpRunnable = new DisplayTextPixelsTimerTask(frame,this);
        //running timer task as daemon thread
        Timer timer = new Timer(true);
        timer.scheduleAtFixedRate(dtpRunnable,DELAY_TIME);
    }

    public void stopDtpRunnable() {
        if (dtpRunnable != null) {
            dtpRunnable.cancel();
            dtpRunnable = null;
        }
    }

    public void setTextPixels(boolean[][] textPixels) {
        this.textPixels = textPixels;
        if (textPixels.length < getMarqueeWidth()) {
            this.marqueePixels = copyCharacterPixels(0,boolean[][] textPixels) {
        for (int i = 0; i < characterPixels.length; i++) {
            for (int j = 0; j < characterPixels[i].length; j++) {
                textPixels[i + position][j] = characterPixels[i][j];
            }
        }

        return textPixels;
    }

    public void copyTextPixels(int position) {
        for (int i = 0; i < marqueePixels.length; i++) {
            int k = i + position;
            k %= textPixels.length;
            for (int j = 0; j < textPixels[i].length; j++) {
                marqueePixels[i][j] = textPixels[k][j];
            }
        }
    }

}

我认为除了仅使用Java之外,还可以做更多的事情。
老实说,Marquee示例是我所见过的最流畅的示例。

本文链接:https://www.f2er.com/2276572.html

大家都在问