关于Java7u40中java:javax.swing.Timer减速

javax.swing.Timer slowdown in Java7u40

同时调用 javax.swing.Timer#start(),

7u25 没问题。

enter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import java.awt.*;
import java.awt.event.*;

import javax.swing.*;

public class TimerProblem extends JComponent {

        int red = 0;

        TimerProblem(final long startMs) {
                setPreferredSize(new Dimension(10, 10));

                Timer t = new Timer(16, new ActionListener() {

                        @Override
                        public void actionPerformed(ActionEvent e) {
                                red = (int)(System.currentTimeMillis() - startMs) % 255;
                                repaint();
                        }

                });
                t.setInitialDelay(1000);
                t.start();
        }

        @Override
        protected void paintComponent(Graphics g) {
                g.setColor(new Color(red, 255 - red, 0));
                g.fillRect(0, 0, getWidth(), getHeight());
        }

        public static void main(String[] args) {
                JFrame f = new JFrame();
                Container c = f.getContentPane();

                c.setLayout(new GridLayout(10, 10));
                long startMs = System.currentTimeMillis();
                for (int i = 0; i < 100; i++) {
                        c.add(new TimerProblem(startMs));
                }
                f.pack();
                f.setVisible(true);
        }

}


您的示例中出现了几个问题:

  • Swing GUI 对象只能在事件调度线程上构建和操作。

  • 所有 Swing Timer 实例共享一个公共线程,该线程正在饱和。

根据目标,一些替代方案是可能的:

  • 使用单个 Timer 实例,并选择一些分数以按比例更高的速率进行更新。下面的示例随机选择 N 个组件并每 100 毫秒更新一次。

  • 使用 TexturePaint,如下所示。

image

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import javax.swing.*;

/** @see https://stackoverflow.com/a/18936444/230513 */
public class BlinkenLights {

    private static final int S = 24;
    private static final int N = 10;
    private static final Random r = new Random();
    private static final List<MyComponent> list = new ArrayList<MyComponent>();

    private static final class MyComponent extends JComponent {

        public MyComponent() {
            this.setOpaque(true);
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(S, S);
        }

        @Override
        protected void paintComponent(Graphics g) {
            g.setColor(Color.getHSBColor(r.nextFloat() / 6, 1, 1));
            g.fillRect(0, 0, getWidth(), getHeight());
        }
    }

    private static JPanel createPanel() {
        final JPanel p = new JPanel();
        p.setLayout(new GridLayout(N, N));
        for (int i = 0; i < N * N; i++) {
            MyComponent c = new MyComponent();
            p.add(c);
            list.add(c);
        }
        Timer t = new Timer(1000 / N, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                Collections.shuffle(list, r);
                for (int i = 0; i < N; i++) {
                    list.get(i).repaint();
                }
            }
        });
        t.start();
        return p;
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame f = new JFrame();
                f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                f.add(createPanel());
                f.pack();
                f.setLocationRelativeTo(null);
                f.setVisible(true);
            }
        });
    }
}


终于写了DIY重绘管理类.. :(

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
import java.awt.event.*;
import java.util.*;

import javax.swing.*;
import javax.swing.Timer;

/**
 * EffectTimer
 */

public class EffectTimer {

    /**
     * All of effect timers in instance of this class.
     */

    static class GlobalTimer implements ActionListener {

        List<EffectTimer> registeredEffects = new ArrayList<>();

        Timer timer = new Timer(16, this);

        public void start(final EffectTimer t) {
            SwingUtilities.invokeLater(new Runnable() {

                @Override
                public void run() {
                    internalStart(t);
                }

            });
        }

        void internalStart(EffectTimer t) {
            int initialDelay = Math.max(0, (int) (t.getEffectStartTime() - System.currentTimeMillis()));
            if(timer.getInitialDelay() >= initialDelay) {
                timer.setInitialDelay(initialDelay);
            }
            if(!registeredEffects.contains(t)) {
                registeredEffects.add(t);
                if(registeredEffects.size() == 1) {
                    timer.start();
                }
            }
        }

        void stop(final EffectTimer t) {
            SwingUtilities.invokeLater(new Runnable() {

                @Override
                public void run() {
                    registeredEffects.remove(t);
                    checkStop();
                }

            });
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            long now = e.getWhen();

            Iterator<EffectTimer> iter = registeredEffects.iterator();
            while(iter.hasNext()) {
                EffectTimer t = iter.next();
                long elapsedMs = now - t.getEffectStartTime();

                if(elapsedMs > 0) {
                    float p = elapsedMs / (float)t.getEffectLengthMs();
                    if(p >= 1.0f) {
                        iter.remove();
                        t.stop();
                    } else {
                        if(t.isReversed()) {
                            p = 1.0f - p;
                        }
                        t.progressChanged(p);
                    }
                }
            }

            checkStop();
        }

        void checkStop() {
            if(registeredEffects.isEmpty()) {
                timer.stop();
            }
        }

        public int getRunningTimerCount() {
            return registeredEffects.size();
        }

        public void stopAll() {
            SwingUtilities.invokeLater(new Runnable() {

                @Override
                public void run() {
                    registeredEffects.clear();
                    checkStop();
                }

            });
        }

    }

    static final GlobalTimer GTIMER = new GlobalTimer();

    int effectLengthMs = -1;

    long effectStartMs = -1;

    float progress = 0.0f;

    boolean reversed = true;

    public long getEffectStartTime() {
        return effectStartMs;
    }

    public int getEffectLengthMs() {
        return effectLengthMs;
    }

    public void start(int lengthMs) {
        start(lengthMs, System.currentTimeMillis());
    }

    public void start(int lengthMs, long startMs) {
        effectLengthMs = lengthMs;
        effectStartMs = startMs;

        reversed = false;
        progress = 0.0f;
        GTIMER.start(this);
    }

    public boolean isReversed() {
        return reversed;
    }

    public void reverse(final int lengthMs) {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                internalReverse(lengthMs);
            }

        });
    }

    void internalReverse(int lengthMs) {
        reversed = !reversed;

        effectLengthMs = lengthMs;
        int adjust = reversed ? (int)(lengthMs * (1.0f - progress)) : (int)(lengthMs * progress);
        effectStartMs = System.currentTimeMillis() - adjust;

        GTIMER.start(this);
    }

    final public void progressChanged(float p) {
        progress = p;
        run(p);
    }

    /**
     * 0.0f to 1.0f effect progress.
     * <wyn>Float.compare(progress, 1.0f) >= 0</wyn> to end progress.
     */

    protected void run(float p) {}

    public void stop() {
        progress = reversed ? 0.0f : 1.0f;
        GTIMER.stop(this);
    }

    public boolean isRunning() {
        return 0.0f < progress && progress < 1.0f;
    }

    public float getProgress() {
        return progress;
    }

    public static int getRunningTimerCount() {
        return GTIMER.getRunningTimerCount();
    }

    public static void stopAll() {
        GTIMER.stopAll();
    }

}