2

我需要一个屏幕上的虚拟操纵杆解决方案来控制外部设备 - 推车。我的程序是用 Java/Swing 编写的,它可以在带有电阻式触摸屏的 Windows 坚固型平板电脑上运行。Swing 中没有 xy 滑块控件,无法通过 Google 找到它,所以可能我需要从头开始编写它。现在的问题是:编写此类控件的最佳方法是什么?我正在考虑放置两个 JSlider 控件,一个是水平的,另一个是垂直的,并制作自定义拇指,但我担心这会给我带来麻烦,因为它实际上是一个 hack。也许只是从头开始写?也许有一个现有的解决方案?我将通过拇指拖动和触摸最终拇指位置来使用它。

4

4 回答 4

5

例如

在此处输入图像描述在此处输入图像描述

import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class SimpleJoystickDemo extends JFrame {

    //I think that orginally made by @HFOE    

    private int displayWidth = 340;
    private int displayHeight = 550;
    private final Point position;

    public SimpleJoystickDemo() {
        super("SimpleJoystickDemo");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setSize(displayWidth, displayHeight);
        setLocationRelativeTo(null);
        position = new Point();
        SimpleJoystick myJoystick = new SimpleJoystick(150, position, 100);
        add(myJoystick, BorderLayout.PAGE_END);
        Drawing drawing = new Drawing(position);
        add(drawing);
    }

    public static void main(final String[] args) {
        Runnable gui = new Runnable() {
            @Override
            public void run() {
                new SimpleJoystickDemo().setVisible(true);
            }
        };
        SwingUtilities.invokeLater(gui);
    }

    private class Drawing extends JPanel {

        private static final long serialVersionUID = 1L;
        private final Point position;

        public Drawing(Point position) {
            this.position = position;
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2 = (Graphics2D) g;
            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                    RenderingHints.VALUE_ANTIALIAS_ON);
            g2.setColor(Color.RED);
            g2.fillOval(160 + position.x, 160 - position.y, 15, 15);
        }
    }
}

class SimpleJoystick extends JPanel {

    private static final long serialVersionUID = 1L;
    //Maximum value for full horiz or vert position where centered is 0:
    private int joyOutputRange;
    private float joySize;     //joystick icon size
    private float joyWidth, joyHeight;
    private float joyCenterX, joyCenterY;  //Joystick displayed Center
    //Display positions for text feedback values:
    private int textHorizPos, textVertPos;
    private int fontSpace = 12;
    private float curJoyAngle;    //Current joystick angle
    private float curJoySize;     //Current joystick size
    private boolean isMouseTracking;
    private boolean leftMouseButton;
    private int mouseX, mouseY;
    private Stroke lineStroke = new BasicStroke(10, BasicStroke.CAP_ROUND,
            BasicStroke.JOIN_ROUND);
    private final Point position;

    public SimpleJoystick(final int joyOutputRange,
            final Point position, final int joySize) {
        this.joyOutputRange = joyOutputRange;
        this.position = position;
        this.joySize = joySize;
        joyWidth = joySize;
        joyHeight = joyWidth;
        setPreferredSize(new Dimension((int) joyWidth + 250,
                (int) joyHeight + 80));
        joyCenterX = getPreferredSize().width / 2;
        joyCenterY = getPreferredSize().height / 2;
        this.joySize = joyWidth / 2;
        setBackground(new Color(226, 226, 226));
        MouseAdapter mouseAdapter = new MouseAdapter() {
            @Override
            public void mouseMoved(final MouseEvent e) {
                mouseCheck(e);
            }

            @Override
            public void mousePressed(final MouseEvent e) {
                leftMouseButton = SwingUtilities.isLeftMouseButton(e);
                mouseCheck(e);
            }
        };
        addMouseMotionListener(mouseAdapter);
        addMouseListener(mouseAdapter);
    }

    private void mouseCheck(final MouseEvent e) {
        mouseX = e.getX();
        mouseY = e.getY();
        float dx = mouseX - joyCenterX;
        float dy = mouseY - joyCenterY;
        if (leftMouseButton) {
            isMouseTracking = true;
        } else {
            isMouseTracking = false;
        }
        if (isMouseTracking) {
            curJoyAngle = (float) Math.atan2(dy, dx);
            curJoySize = (float) Point.distance(mouseX, mouseY,
                    joyCenterX, joyCenterY);
        } else {
            curJoySize = 0;
        }
        if (curJoySize > joySize) {
            curJoySize = joySize;
        }
        position.x = (int) (joyOutputRange * (Math.cos(curJoyAngle)
                * curJoySize) / joySize);
        position.y = (int) (joyOutputRange * (-(Math.sin(curJoyAngle)
                * curJoySize) / joySize));
        SwingUtilities.getRoot(SimpleJoystick.this).repaint();
    }

    @Override
    protected void paintComponent(final Graphics g) {
        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D) g;
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);
        g2.setColor(Color.LIGHT_GRAY);
        g2.fillOval((int) (joyCenterX - joyWidth / 2),
                (int) (joyCenterY - joyHeight / 2),
                (int) joyWidth, (int) joyHeight);
        //rotate and draw joystick line segment:
        Graphics2D g3 = (Graphics2D) g2.create();
        g3.translate(joyCenterX, joyCenterY);
        g3.rotate(curJoyAngle);
        g3.setColor(Color.GRAY);
        g3.setStroke(lineStroke);
        g3.drawLine(0, 0, (int) curJoySize, 0);
        g3.dispose();
        //
        g2.setColor(Color.GRAY);
        g2.fillOval((int) joyCenterX - 10, (int) joyCenterY - 10, 20, 20);
        textHorizPos = 50;
        textVertPos = (int) (joyCenterY - 50);
        g2.drawString("Horizont:", textHorizPos, textVertPos);
        textHorizPos += (4 * fontSpace);
        g2.drawString(String.valueOf(position.x), textHorizPos, textVertPos);
        textHorizPos = 50;
        textVertPos += 12;
        g2.drawString("Vertical :", textHorizPos, textVertPos);
        textHorizPos += (4 * fontSpace);
        g2.drawString(String.valueOf(position.y), textHorizPos, textVertPos);
    }
}
于 2013-05-08T12:08:21.270 回答
2

来自@tutejszy 的重新编写的代码。删除了警告并添加了缺少的 PointChangeEvent。

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

public class VJoystick extends JPanel implements ChangeListener 
{    
    private JLabel lblPosition;
    private static final long serialVersionUID = 1L;
    public VJoystick() 
    {
        setLayout(new BorderLayout(0, 0));
        SimpleJoystick myJoystick = new SimpleJoystick(150);
        myJoystick.setPreferredSize(new Dimension(100,100));
        myJoystick.addChangeListener(this);
        add(myJoystick, BorderLayout.CENTER);

        lblPosition = new JLabel("position");
        add(lblPosition, BorderLayout.SOUTH);
    }

    @Override
    public void stateChanged(ChangeEvent ev) {
        Point p = null;
        try {
            p = ((PointChangeEvent)ev).p;
        } catch (Exception e) {
            return;
        }
        lblPosition.setText("x="+p.x+" y="+p.y);
    }
}

class SimpleJoystick extends JPanel 
{
    private static final long serialVersionUID = 1L;
    /** Maximum value for full horiz or vert position where centered is 0 */
    private int joyOutputRange;
    /** max x and y value, in pixels */
    private int joyRadius; 
    /** Joystick displayed Center, in pixels */
    private int joyCenterX, joyCenterY; 
    /** joystick output position scaled to given joyOutputRange */
    private Point position = new Point();
    /** joystick x axis value in pixels */
    private int dx = 0;
    /** joystick y axis value in pixels */
    private int dy = 0;

    /**
     * @param joyOutputRange
     */
    public SimpleJoystick(final int joyOutputRange) {
        this.joyOutputRange = joyOutputRange;
        setBackground(new Color(226, 226, 226));
        MouseAdapter mouseAdapter = new MouseAdapter() {

            @Override
            public void mousePressed(final MouseEvent e) {
                if (SwingUtilities.isLeftMouseButton(e) && cursorChanged(e.getX(), e.getY())) {
                    SwingUtilities.getRoot(SimpleJoystick.this).repaint();
                    fireStateChanged();
                }
            }

            @Override
            public void mouseDragged(MouseEvent e) {
                if (SwingUtilities.isLeftMouseButton(e) && cursorChanged(e.getX(), e.getY())) {
                    SwingUtilities.getRoot(SimpleJoystick.this).repaint();
                    fireStateChanged();
                }
            }
        };
        addMouseMotionListener(mouseAdapter);
        addMouseListener(mouseAdapter);
    }

    private boolean cursorChanged(int mouseX, int mouseY) {
        if (joyRadius == 0) return false;

        dx = mouseX - joyCenterX;
        dy = mouseY - joyCenterY;
        if (dx > joyRadius) dx = joyRadius;
        if (dy > joyRadius) dy = joyRadius;
        if (dx < -joyRadius) dx = -joyRadius;
        if (dy < -joyRadius) dy = -joyRadius;

        position.x = joyOutputRange * dx / joyRadius;
        position.y = -joyOutputRange * dy / joyRadius;

        return true;
    }

    @Override
    protected void paintComponent(final Graphics g) {
        super.paintComponent(g);
        int joyWidth = getSize().width;
        int joyHeight = getSize().height;
        joyRadius = Math.min(joyWidth, joyHeight) / 2;
        if (joyRadius == 0) return;

        joyCenterX = joyWidth / 2;
        joyCenterY = joyHeight / 2;
        Graphics2D g2 = (Graphics2D) g;
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

        int diameter; 

        //background
        g2.setColor(Color.LIGHT_GRAY);
        diameter = joyRadius*2;
        g2.fillOval(joyCenterX - diameter/2 , joyCenterY - diameter/2, diameter, diameter);

        g2.setColor(Color.RED);
        diameter = 40;
        g2.fillOval(joyCenterX + dx - diameter/2 , joyCenterY + dy - diameter/2, diameter, diameter);

        //thumb
        g2.setColor(Color.GRAY);
        diameter = 20;
        g2.fillOval(joyCenterX - diameter/2 , joyCenterY - diameter/2, diameter, diameter);
    }

    void addChangeListener(ChangeListener listener) {
        listenerList.add(ChangeListener.class, listener);
    }

    void removeChangeListener(ChangeListener listener) {
        listenerList.remove(ChangeListener.class, listener);
    }

    protected void fireStateChanged() {
        ChangeEvent e = new PointChangeEvent(this, position);
        Object[] listeners = listenerList.getListenerList();
        for (int i = listeners.length - 2; i >= 0; i -= 2) {
            if (listeners[i] == ChangeListener.class) {
                ((ChangeListener) listeners[i + 1]).stateChanged(e);
            }
        }
    }        
}

class PointChangeEvent extends ChangeEvent
{
    private static final long serialVersionUID = 1L;
    public Point p;

    public PointChangeEvent(Object source, Point p)
    {
        super(source);
        this.p=p;
    }
}
于 2014-06-06T21:22:14.953 回答
1

这是来自@mKorbel 的重新编写的代码,它基于事件且更面向摇摆,没有浮动:

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

class JoyDemo extends JPanel implements ChangeListener {    
    private JLabel lblPosition;

    public JoyDemo() {
        setLayout(new BorderLayout(0, 0));
        SimpleJoystick myJoystick = new SimpleJoystick(150);
        myJoystick.setPreferredSize(new Dimension(100,100));
        myJoystick.addChangeListener(this);
        add(myJoystick, BorderLayout.CENTER);

        lblPosition = new JLabel("position");
        add(lblPosition, BorderLayout.SOUTH);
    }

    @Override
    public void stateChanged(ChangeEvent ev) {
        Point p = null;
        try {
            p = ((PointChangeEvent)ev).p;
        } catch (Exception e) {
            return;
        }
        lblPosition.setText("x="+p.x+" y="+p.y);
    }
}

class SimpleJoystick extends JPanel {
    /** Maximum value for full horiz or vert position where centered is 0 */
    private int joyOutputRange;
    /** max x and y value, in pixels */
    private int joyRadius; 
    /** Joystick displayed Center, in pixels */
    private int joyCenterX, joyCenterY; 
    /** joystick output position scaled to given joyOutputRange */
    private Point position = new Point();
    /** joystick x axis value in pixels */
    private int dx = 0;
    /** joystick y axis value in pixels */
    private int dy = 0;

    /**
     * @param joyOutputRange
     */
    public SimpleJoystick(final int joyOutputRange) {
        this.joyOutputRange = joyOutputRange;
        setBackground(new Color(226, 226, 226));
        MouseAdapter mouseAdapter = new MouseAdapter() {

            @Override
            public void mousePressed(final MouseEvent e) {
                if (SwingUtilities.isLeftMouseButton(e) && cursorChanged(e.getX(), e.getY())) {
                    SwingUtilities.getRoot(SimpleJoystick.this).repaint();
                    fireStateChanged();
                }
            }

            @Override
            public void mouseDragged(MouseEvent e) {
                if (SwingUtilities.isLeftMouseButton(e) && cursorChanged(e.getX(), e.getY())) {
                    SwingUtilities.getRoot(SimpleJoystick.this).repaint();
                    fireStateChanged();
                }
            }
        };
        addMouseMotionListener(mouseAdapter);
        addMouseListener(mouseAdapter);
    }

    private boolean cursorChanged(int mouseX, int mouseY) {
        if (joyRadius == 0) return false;

        dx = mouseX - joyCenterX;
        dy = mouseY - joyCenterY;
        if (dx > joyRadius) dx = joyRadius;
        if (dy > joyRadius) dy = joyRadius;
        if (dx < -joyRadius) dx = -joyRadius;
        if (dy < -joyRadius) dy = -joyRadius;

        position.x = joyOutputRange * dx / joyRadius;
        position.y = -joyOutputRange * dy / joyRadius;

        return true;
    }

    @Override
    protected void paintComponent(final Graphics g) {
        super.paintComponent(g);
        int joyWidth = getSize().width;
        int joyHeight = getSize().height;
        joyRadius = Math.min(joyWidth, joyHeight) / 2;
        if (joyRadius == 0) return;

        joyCenterX = joyWidth / 2;
        joyCenterY = joyHeight / 2;
        Graphics2D g2 = (Graphics2D) g;
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

        int diameter; 

        //background
        g2.setColor(Color.LIGHT_GRAY);
        diameter = joyRadius*2;
        g2.fillOval(joyCenterX - diameter/2 , joyCenterY - diameter/2, diameter, diameter);

        g2.setColor(Color.RED);
        diameter = 40;
        g2.fillOval(joyCenterX + dx - diameter/2 , joyCenterY + dy - diameter/2, diameter, diameter);

        //thumb
        g2.setColor(Color.GRAY);
        diameter = 20;
        g2.fillOval(joyCenterX - diameter/2 , joyCenterY - diameter/2, diameter, diameter);
    }

    void addChangeListener(ChangeListener listener) {
        listenerList.add(ChangeListener.class, listener);
    }

    void removeChangeListener(ChangeListener listener) {
        listenerList.remove(ChangeListener.class, listener);
    }

    protected void fireStateChanged() {
        ChangeEvent e = new PointChangeEvent(this, position);
        Object[] listeners = listenerList.getListenerList();
        for (int i = listeners.length - 2; i >= 0; i -= 2) {
            if (listeners[i] == ChangeListener.class) {
                ((ChangeListener) listeners[i + 1]).stateChanged(e);
            }
        }
    }
}
于 2013-05-08T18:31:41.977 回答
0

在此处输入图像描述 在此处输入图像描述 在此处输入图像描述

这是我的解决方案。请注意,我修改了@Dark.Rider 的答案,谁修改了@tutejszy 的答案,谁修改了@mKorbel 的答案......

我做了2个文件。一个叫 Joystick.java

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeListener;
import javax.swing.event.SwingPropertyChangeSupport;
import javax.swing.SwingUtilities;

public class Joystick extends javax.swing.JPanel {

    private final int outputMax;
    private final int thumbDiameter;
    private final int thumbRadius;
    private final int panelWidth;
    private final int arrowRadius;
    private final int BORDER_THICKNESS = 2;

    private final Point thumbPos = new Point();
    protected SwingPropertyChangeSupport propertySupporter = new SwingPropertyChangeSupport(this);


    /**
     * @param output_max The maximum value to scale output to. If this value was
     * 5 and the joystick thumb was dragged to the top-left corner, the output 
     * would be (-5,5)
     * @param panel_width how big the JPanel will be. The sizes of the joystick's
     * visual components are proportional to this value
     */
    public Joystick(int output_max, int panel_width) {

        assert output_max > 0;
        assert panel_width > 0;

        outputMax = output_max;
        panelWidth = panel_width;
        thumbDiameter = panel_width/4;
        thumbRadius = thumbDiameter/2;
        arrowRadius = panel_width/24;

        MouseAdapter mouseAdapter = new MouseAdapter() {

            private void repaintAndTriggerListeners(){
                SwingUtilities.getRoot(Joystick.this).repaint();
                propertySupporter.firePropertyChange(null, null, getOutputPos());
            }

            @Override
            public void mousePressed(final MouseEvent e) {
                if (SwingUtilities.isLeftMouseButton(e)) {
                    updateThumbPos(e.getX(), e.getY());
                    repaintAndTriggerListeners();
                }
            }

            @Override
            public void mouseDragged(MouseEvent e) {
                if (SwingUtilities.isLeftMouseButton(e)) {
                    updateThumbPos(e.getX(), e.getY());
                    repaintAndTriggerListeners();
                }
            }

            @Override
            public void mouseReleased(MouseEvent e) {
                if (SwingUtilities.isLeftMouseButton(e)) {
                    centerThumbPad();
                    repaintAndTriggerListeners();
                }
            }
        };
        addMouseMotionListener(mouseAdapter);
        addMouseListener(mouseAdapter);
        setPreferredSize(new java.awt.Dimension(panel_width, panel_width));
        setOpaque(false);

        centerThumbPad();
    }

    private void centerThumbPad(){
        thumbPos.x = panelWidth/2;
        thumbPos.y = panelWidth/2;
    }

    /**
     * update both thumbPos
     * @param mouseX the x position of cursor that has clicked in the joystick panel
     * @param mouseY the y position of cursor that has clicked in the joystick panel
     * @return 
     */
    private void updateThumbPos(int mouseX, int mouseY) {
        // if the cursor is clicked out of bounds, we'll modify the position
        // to be the closest point where we can draw the thumb pad completely
        if (mouseX < thumbRadius)
            mouseX = thumbRadius;
        else if(mouseX > panelWidth - thumbRadius)
            mouseX = panelWidth - thumbRadius;

        if (mouseY < thumbRadius)
            mouseY = thumbRadius;
        else if(mouseY > panelWidth - thumbRadius)
            mouseY = panelWidth - thumbRadius;

        thumbPos.x = mouseX;
        thumbPos.y = mouseY;
    }

    /**
     * @return the scaled position of the joystick thumb pad
     */
    Point getOutputPos(){
        Point result = new Point();
        result.x = outputMax * (thumbPos.x - panelWidth/2) / (panelWidth/2-thumbDiameter/2);
        result.y = -outputMax * (thumbPos.y - panelWidth/2) / (panelWidth/2-thumbDiameter/2);
        return result;
    }

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

        //joystick background border
        g.setColor(Color.BLACK);
        g.fillOval(thumbRadius, thumbRadius, panelWidth-thumbDiameter, panelWidth-thumbDiameter);

        //joystick background color
        g.setColor(Color.GRAY);
        g.fillOval(thumbRadius+BORDER_THICKNESS, thumbRadius+BORDER_THICKNESS, panelWidth-thumbDiameter-BORDER_THICKNESS*2, panelWidth-thumbDiameter-BORDER_THICKNESS*2);

        //joystick background arrows
        g.setColor(Color.BLACK);
        int[] left_x = {thumbDiameter-arrowRadius,thumbDiameter+arrowRadius,thumbDiameter+arrowRadius};
        int[] left_y = {panelWidth/2,panelWidth/2+arrowRadius,panelWidth/2-arrowRadius};
        g.fillPolygon(left_x, left_y,3);
        int[] right_x = {panelWidth-thumbDiameter+arrowRadius,panelWidth-thumbDiameter-arrowRadius,panelWidth-thumbDiameter-arrowRadius};
        int[] right_y = {panelWidth/2,panelWidth/2+arrowRadius,panelWidth/2-arrowRadius};
        g.fillPolygon(right_x, right_y,3);
        int[] up_x = left_y;
        int[] up_y = left_x;
        g.fillPolygon(up_x, up_y,3);  
        int[] down_x = right_y;
        int[] down_y = right_x;
        g.fillPolygon(down_x, down_y,3);  

        //thumb pad border
        g.setColor(Color.BLACK);
        g.fillOval(thumbPos.x - thumbRadius - BORDER_THICKNESS, thumbPos.y - thumbRadius - BORDER_THICKNESS, thumbRadius*2+BORDER_THICKNESS*2, thumbRadius*2+BORDER_THICKNESS*2);

        //thumb pad color
        g.setColor(Color.GRAY);
        g.fillOval(thumbPos.x - thumbRadius, thumbPos.y - thumbRadius, thumbRadius*2, thumbRadius*2);
    }

    @Override
    public void addPropertyChangeListener(PropertyChangeListener listener) {
        propertySupporter.addPropertyChangeListener(listener);
    }

    @Override
    public void removePropertyChangeListener(PropertyChangeListener listener) {
        propertySupporter.removePropertyChangeListener(listener);
    }
}

还有一个叫 JoystickOutputContainer.java(用来制作示例图片)

import java.awt.BorderLayout;
import java.awt.Point;
import java.beans.PropertyChangeListener;
import javax.swing.JLabel;
import javax.swing.JPanel;

public class JoystickOutputContainer extends JPanel {

    private Joystick myJoystick = new Joystick(100, 140);
    private JLabel lblPosition = new JLabel("x=0 y=0");

    public JoystickOutputContainer() {
        setLayout(new BorderLayout(0, 0));
        add(myJoystick, BorderLayout.CENTER);
        add(lblPosition, BorderLayout.SOUTH);
        myJoystick.addPropertyChangeListener(updateConsoleListener);
    }

    private final PropertyChangeListener updateConsoleListener = (evt) -> {
        updateConsoleCallback( (Point) evt.getNewValue() );
    };

    private void updateConsoleCallback(Point p){
        lblPosition.setText("x="+p.x+" y="+p.y);
    }

}

于 2020-03-24T14:01:27.710 回答