简体   繁体   中英

Set JButton to the location of another JButton

I'm trying to move a JButton to the location of another one but the button I want to move moves to a wrong point. My idea is that this happens because I use multiple JPanels. I tried: getLocationOnScreen , getBounds and getLocation , but none of them worked, how to solve it? When an user selects a card on the table or from a player by clicking this card the the target is set, the sender is set by clicking a card from the top panel. playerCardSpotTarget and playerCardSpotSender are both of type Card. When I try to move the for example eight of diamonds this card moves to a point behind the eight and nine of clubs.

在此处输入图片说明

Code:

This events belong to the blue cards on the table and the cards for the players(I have to change the name of the event, I know).

private void PlayerOneMouseClicked(java.awt.event.MouseEvent evt){
     playerCardSpotTarget=(Card)evt.getSource();        

    if(playerCardSpotTarget.isBorderPainted()){
        playerCardSpotTarget.setBorderPainted(false);
    }
    else{
        playerCardSpotTarget.setBorderPainted(true);
    }
}

This event belongs to the cards in the top panel.

private void MouseClicked(java.awt.event.MouseEvent evt) {                              
     playerCardSpotSender=(Card)evt.getSource();

     System.out.println(playerCardSpotSender.suit+" "+playerCardSpotSender.kind);


     if (playerCardSpotTarget != null && playerCardSpotTarget.isBorderPainted()) {            

       playerCardSpotSender.setLocation(playerCardSpotTarget.getLocation());
       System.out.println(playerCardSpotTarget.getLocationOnScreen());
       System.out.println(playerCardSpotSender.getLocationOnScreen());

     }
}

Layout for the center panel in the JFrame (BorderLayout.CENTER)

   JPanel centerPanelNorth;
   JPanel centerPanelCenter;
   JPanel centerPanelEast;
   JPanel centerPanelSouth;
   JPanel centerPanelWest;
   JLabel tablePicture;
   JPanel centerPanel;


   centerPanel=new JPanel(new BorderLayout()); 
   tablePicture = new JLabel(new ImageIcon(this.getClass().getResource(Constants.POKERTABLE_ICON)));
   centerPanelNorth=new JPanel();
   centerPanelEast=new JPanel();
   centerPanelSouth=new JPanel();
   centerPanelWest=new JPanel();

   centerPanelCenter=new JPanel();

   centerPanel.add(centerPanelCenter,BorderLayout.CENTER);

   centerPanelCenter.add(tablePicture);  

   //add
   tablePicture.add(boardCard1);
   tablePicture.add(boardCard2);
   tablePicture.add(boardCard3);
   tablePicture.setLayout(new GridBagLayout());

   //PLAYER NORTH
   centerPanel.add(centerPanelNorth,BorderLayout.NORTH);
   centerPanelNorth.add(playerOneCardOne);
   centerPanelNorth.add(playerOneCardTwo);

   //PLAYER EAST
   centerPanel.add(centerPanelEast,BorderLayout.EAST);
   centerPanelEast.setLayout(new BoxLayout(centerPanelEast,BoxLayout.X_AXIS));
   centerPanelEast.add(playerTwoCardOne);
   centerPanelEast.add(playerTwoCardTwo);

   //PLAYER SOUTH
   centerPanel.add(centerPanelSouth,BorderLayout.SOUTH);
   centerPanelSouth.add(playerThreeCardOne);
   centerPanelSouth.add(playerThreeCardTwo);

   //PLAYER WEST
   centerPanel.add(centerPanelWest,BorderLayout.WEST);
   centerPanelWest.setLayout(new BoxLayout(centerPanelWest,BoxLayout.X_AXIS));
   centerPanelWest.add(playerFourCardOne);
   centerPanelWest.add(playerFourCardTwo);

Card.java

public class Card extends JButton{
    int suit;
    int kind;
    boolean known;
    String iconPath;
    Integer boardPosition;
}

Animating the button movement isn't actually the hardest problem, the hardest problem is trying to move the data about in away in which you can manage it and how to connect the source component with the target...

感动我

To start with, you need a means by which you can move a component across container boundaries. While there are probably a few ways to do this, the simplest is to probably use the glass pane of the frame

public class AnimationPane extends JPanel {

    public AnimationPane() {
      setOpaque(false);
      setLayout(null);
    }

}

This is nothing special, it's just a JPanel which is transparent and has no layout manager, normally, not recommended, but in the case, we're going to take control..

Now, we need some way to animate the movement...

  public enum Animator {

    INSTANCE;

    private List<IAnimatable> animatables;

    private Timer timer;

    private Animator() {
      animatables = new ArrayList<>(25);
      timer = new Timer(40, new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent e) {
          IAnimatable[] anins = animatables.toArray(new IAnimatable[animatables.size()]);
          for (IAnimatable animatable : anins) {
            animatable.update();
          }
        }
      });
      timer.start();
    }

    public void addAnimatable(IAnimatable animatable) {
      animatables.add(animatable);
    }

    public void removeAnimatable(IAnimatable animatable) {
      animatables.remove(animatable);
    }

  }

  public interface IAnimatable {

    public void update();

  }

  public interface IMoveAnimatable extends IAnimatable{

    public JComponent getSourceComponent();

    public IImportable getImportable();

  }

So the Animator is the core "engine", it's basically a Swing Timer which simply calls update on any IAnimatable s it might be managing. The intention with this approach is you can have a number of animations running, but it won't degrade the system (greatly) as you only have a single update/timer point.

Now, normally I'd just use something like the Timing Framework or the Trident Framework or even the Universal Tween Engine

The IAnimatable interfaces just define the basic contracts that provide functionality for the animation.

We need to define some kind of contract the defines objects which can take part in the animation process and receive information, or the "target"

public interface IImportable {
    public JComponent getView();
    public void importValue(String value);
}

public abstract class AbstractImportable extends JPanel implements IImportable {

    @Override
    public JComponent getView() {
        return this;
    }

}

Now it occurs to me that we could tap into the pre-existing Transferable API, which would allow you to also implement drag-n-drop (and even copy/cut and paste), this would be used to define a lookup mechanism where you match a given data type with potential targets based on the DataFlavor ... but I'll leave you to investigate how that might work... The core mechanism basically removes the source component from it's current container, adds it to the AnimationPane , moves the source component across the AnimationPane and then imports the data into the target...

The problem is, you need to translate the location of component from it's current context to the AnimationPane .

A components location is relative to it's parents context. It's relatively easy to do with SwingUtilities.convertPoint(Component, Point, Component)

We calculate the origin point of the source component and the target point, relative to the AnimationPane . We then, on each call to update , calculate the progress of the animation. Instead of using a "delta" movement, we calculate the different between the time we started and a predefined duration (1 second in this case), this generally produces a more flexible animation

  public class DefaultAnimatable implements IMoveAnimatable {

    public static final double PLAY_TIME = 1000d;

    private Long startTime;
    private JComponent sourceComponent;
    private IImportable importable;
    private JComponent animationSurface;

    private Point originPoint, destinationPoint;

    private String value;

    public DefaultAnimatable(JComponent animationSurface, JComponent sourceComponent, IImportable importable, String value) {
      this.sourceComponent = sourceComponent;
      this.importable = importable;
      this.animationSurface = animationSurface;
      this.value = value;
    }

    public String getValue() {
      return value;
    }

    public JComponent getAnimationSurface() {
      return animationSurface;
    }

    @Override
    public JComponent getSourceComponent() {
      return sourceComponent;
    }

    @Override
    public IImportable getImportable() {
      return importable;
    }

    @Override
    public void update() {
      if (startTime == null) {
        System.out.println("Start");
        IImportable importable = getImportable();
        JComponent target = importable.getView();

        originPoint = SwingUtilities.convertPoint(getSourceComponent().getParent(), getSourceComponent().getLocation(), getAnimationSurface());
        destinationPoint = SwingUtilities.convertPoint(target.getParent(), target.getLocation(), getAnimationSurface());

        destinationPoint.x = destinationPoint.x + ((target.getWidth() - getSourceComponent().getWidth()) / 2);
        destinationPoint.y = destinationPoint.y + ((target.getHeight() - getSourceComponent().getHeight()) / 2);

        Container parent = getSourceComponent().getParent();

        getAnimationSurface().add(getSourceComponent());
        getSourceComponent().setLocation(originPoint);

        parent.invalidate();
        parent.validate();
        parent.repaint();

        startTime = System.currentTimeMillis();
      }
      long duration = System.currentTimeMillis() - startTime;
      double progress = Math.min(duration / PLAY_TIME, 1d);

      Point location = new Point();
      location.x = progress(originPoint.x, destinationPoint.x, progress);
      location.y = progress(originPoint.y, destinationPoint.y, progress);

      getSourceComponent().setLocation(location);
      getAnimationSurface().repaint();

      if (progress == 1d) {
        getAnimationSurface().remove(getSourceComponent());
        Animator.INSTANCE.removeAnimatable(this);
        animationCompleted();
      }
    }

    public int progress(int startValue, int endValue, double fraction) {

      int value = 0;
      int distance = endValue - startValue;
      value = (int) Math.round((double) distance * fraction);
      value += startValue;

      return value;

    }

    protected void animationCompleted() {
      getImportable().importValue(getValue());
    }

  }

Okay, now this produces a linear animation, which is pretty boring, now if you have plenty of time, you could create an easement like this or just use one of the animation frameworks...

Now, we need to put it together...

  import java.awt.BorderLayout;
  import java.awt.Color;
  import java.awt.Container;
  import java.awt.Dimension;
  import java.awt.EventQueue;
  import java.awt.GridBagLayout;
  import java.awt.GridLayout;
  import java.awt.Point;
  import java.awt.event.ActionEvent;
  import java.awt.event.ActionListener;
  import java.util.ArrayList;
  import java.util.List;
  import javax.swing.JButton;
  import javax.swing.JComponent;
  import javax.swing.JFrame;
  import javax.swing.JPanel;
  import javax.swing.SwingUtilities;
  import javax.swing.Timer;
  import javax.swing.UIManager;
  import javax.swing.UnsupportedLookAndFeelException;
  import javax.swing.border.LineBorder;

  public class AnimationTest {

    public static void main(String[] args) {
      new AnimationTest();
    }

    public AnimationTest() {
      EventQueue.invokeLater(new Runnable() {
        @Override
        public void run() {
          try {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
          } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
            ex.printStackTrace();
          }

         AnimationPane animationPane = new AnimationPane();

          LeftPane leftPane = new LeftPane(animationPane);
          RightPane rightPane = new RightPane();

          leftPane.setImportabale(rightPane);
          rightPane.setImportabale(leftPane);

          JFrame frame = new JFrame("Testing");
          frame.setLayout(new GridLayout(1, 2));
          frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
          frame.add(leftPane, BorderLayout.WEST);
          frame.add(rightPane, BorderLayout.WEST);
          frame.setGlassPane(animationPane);
          animationPane.setVisible(true);
          frame.pack();
          frame.setLocationRelativeTo(null);
          frame.setVisible(true);
        }
      });
    }

    public class RightPane extends AbstractImportable {

      private IImportable source;
      private JButton imported;

      private String importedValue;

      public RightPane() {
        setLayout(new GridBagLayout());
        setBorder(new LineBorder(Color.DARK_GRAY));
      }

      public void setImportabale(IImportable source) {
        this.source = source;
      }

      @Override
      public void importValue(String value) {
        if (imported != null) {
          // May re-animate the movement back...
          remove(imported);
        }
        importedValue = value;
        imported = new JButton(">> " + value + "<<");
        add(imported);
        revalidate();
        repaint();
      }

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

    }

    public class LeftPane extends AbstractImportable {

      private IImportable importable;

      public LeftPane(AnimationPane animationPane) {
        setLayout(new GridBagLayout());
        JButton btn = new JButton("Lefty");
        btn.addActionListener(new ActionListener() {
          @Override
          public void actionPerformed(ActionEvent e) {
            DefaultAnimatable animatable = new DefaultAnimatable(animationPane, btn, importable, "Lefty");
            Animator.INSTANCE.addAnimatable(animatable);
          }
        });

        add(btn);
        setBorder(new LineBorder(Color.DARK_GRAY));
      }

      public void setImportabale(IImportable target) {
        this.importable = target;
      }

      @Override
      public void importValue(String value) {
      }

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

    }
  }

也许使用mousePressed() ,在移动卡时,将其按到目标。然后在该过程中,通过事件获取有关JButton.getLocation()的信息,然后需要解决两张卡之间的冲突问题很好。当然,这是我的建议,您应该有更好的主意!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM