首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >使用JComponents实现滑动效果菜单

使用JComponents实现滑动效果菜单
EN

Stack Overflow用户
提问于 2018-06-02 07:05:52
回答 1查看 90关注 0票数 1

我正在尝试用JLabels和定时器来做一些滑动效果。我只想使用两个计时器(IN和OUT)来管理多个组件的效果。问题是,只有当我不能快速地从一个JLabel移动到另一个,并且我不知道如何管理它时,一切才能正常工作。

Gif showing the problem

下面是我的代码:

代码语言:javascript
复制
public class Sliders extends JFrame {

private JPanel contentPane;
JLabel label,label_1;
static RainDrop frame;
 javax.swing.Timer  in,out;
/**
 * Launch the application.
 */
public static void main(String[] args) {
    EventQueue.invokeLater(new Runnable() {
        public void run() {
            try {
                frame = new RainDrop();
                frame.setVisible(true);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    });
}

/**
 * Create the frame.
 */
public Sliders() {
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    setBounds(100, 100, 450, 300);
    contentPane = new JPanel();
    contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
    setContentPane(contentPane);
    contentPane.setLayout(null);

    label = new JLabel("");
    label.addMouseListener(new MouseAdapter() {
        @Override
        public void mouseEntered(MouseEvent a1) {
            setIN(2,0,label);
                System.out.println("ENTRATO");
                checkloop_out_mag(-270,label);

        }
        @Override
        public void mouseExited(MouseEvent a2) {
            in.stop();
            setOUT(-2,0,label);
            System.out.println("USCITO");                       
        }
    });
    label.setBackground(Color.ORANGE);
    label.setOpaque(true);
    label.setBounds(-270, 0, 337, 44);
    contentPane.add(label);

    label_1 = new JLabel("");
    label_1.addMouseListener(new MouseAdapter() {
        @Override
        public void mouseEntered(MouseEvent b1) {
                setIN(2,44,label_1);
                System.out.println("ENTRATO");
                checkloop_out_mag(-270,label_1);
        }
        @Override
        public void mouseExited(MouseEvent b2) {
            in.stop();
            setOUT(-2,44,label_1);
            System.out.println("USCITO");                   
        }
    });
    label_1.setOpaque(true);
    label_1.setBackground(Color.GREEN);
    label_1.setBounds(-270, 44, 337, 44);
    contentPane.add(label_1);
}



public void setIN(int x,int y,JLabel label) {


    in = new javax.swing.Timer(2, new ActionListener() {


                @Override
                public void actionPerformed(ActionEvent e) {



                     label.setLocation(label.getBounds().x+x,y);

                     System.out.println("SPOSTO");

                     System.out.println("CONTROLLO");
                     checkloop_in_magequals(0,label); 

                }
            });
            in.setRepeats(true);
            in.start();



}

public void setOUT(int x,int y,JLabel label) {


    out = new javax.swing.Timer(2, new ActionListener() {


                @Override
                public void actionPerformed(ActionEvent e) {



                     label.setLocation(label.getBounds().x+x,y);

                     System.out.println("SPOSTO");

                     System.out.println("CONTROLLO");
                       checkloop_out_equals(-270,label);


                }
            });
            out.setRepeats(true);
            out.start();



}

public void checkloop_out_equals(int z,JLabel label) {
     if (label.getBounds().x==z){
            out.stop();
            System.out.println("STOP");
     }
}

public void checkloop_out_mag(int z,JLabel label) {
     if (label.getBounds().x>z){
            out.stop();
            System.out.println("STOP");
     }
}


public void checkloop_in_magequals(int z,JLabel label) {
 if (label.getBounds().x>=z){
        in.stop();
        System.out.println("STOP");
     }
 }
}

有没有办法只用两个计时器就能修复代码?或者我需要为每个JComponent使用两个计时器?

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2018-06-02 09:10:08

首先,使用动画框架,如TridentThe Timing FrameworkThe Universal Tween Engine,它们提供了大量的功能,否则需要大量的代码才能完成。

动画是一个复杂的主题,所以我只会介绍一些基础知识。动画是随着时间的推移而变化的幻觉(告诉你它将是基本的)。

你的问题的外壳可以归结为两件基本的事情:

  1. 您需要一个中央“时钟”,它可以提供“滴答”和(一个相对的)定期间隔
  2. 一个足够不可知,足够解耦的API,它不关心它是“什么”动画,只关心,给定一个持续时间和范围,它可以计算每个“滴答”

所需的属性

一个重要的概念是,动画应该在一段时间内播放,而不是在不同的值之间播放。这样做的主要原因是灵活性。为了改变速度,改变动画的持续时间要容易得多,而且它允许系统相对简单地“丢弃”帧。还记得我说过“动画是随时间变化的幻觉”--这就是我要说的。

很简单..。

让我们从一些基础开始..。

代码语言:javascript
复制
public class Range<T> {
    private T from;
    private T to;

    public Range(T from, T to) {
        this.from = from;
        this.to = to;
    }

    public T getFrom() {
        return from;
    }

    public T getTo() {
        return to;
    }

    @Override
    public String toString() {
        return "From " + getFrom() + " to " + getTo();
    }
    
}

Range描述了可以用“开始”(from)值和“目标”(to)值来度量的东西。这允许使用提供许多重要的值,但最值得注意的是,我们要执行的动画的“范围”。

在过去,我对各种值都使用过这样的概念,包括Point__、Rectangle甚至Color

代码语言:javascript
复制
public interface AnimationPropertiesListener<T> {
    public void stateChanged(AnimationProperties<T> animator);
}

public interface AnimationProperties<T> {
    public Range<T> getRange();
    public T getValue();
    public boolean tick();
    
    public void setDuration(Duration duration);
    public Duration getDuration();
}

public abstract class AbstractAnimationProperties<T> implements AnimationProperties<T> {

    private Range<T> range;     
    private LocalDateTime startTime;
    private Duration duration = Duration.ofSeconds(5);
    private T value;
    private AnimationPropertiesListener<T> listener;

    public AbstractAnimationProperties(Range<T> range, AnimationPropertiesListener<T> listener) {
        this.range = range;
        this.value = range.getFrom();

        this.listener = listener;
    }

    public void setDuration(Duration duration) {
        this.duration = duration;
    }

    public Duration getDuration() {
        return duration;
    }

    public Range<T> getRange() {
        return range;
    }

    @Override
    public T getValue() {
        return value;
    }

    @Override
    public boolean tick() {
        if (startTime == null) {
            startTime = LocalDateTime.now();
        }
        Duration duration = getDuration();
        Duration runningTime = Duration.between(startTime, LocalDateTime.now());
        Duration timeRemaining = duration.minus(runningTime);
        if (timeRemaining.isNegative()) {
            runningTime = duration;
        }
        double progress = (runningTime.toMillis() / (double) duration.toMillis());
        value = calculateValue(progress);

        listener.stateChanged(this);
        
        return progress >= 1.0;
    }

    public abstract T calculateValue(double progress);

}

“动画属性”是我们试图实现的基本功能。它负责计算动画想要运行的时间量,即动画已经运行的时间量,并提供一种方法来计算指定动画Range值的结果值。(注意,calculateValue可能是Range的一个属性,但我只是把它放在原来的位置)

好的,这一切都很好,但是我们实际上需要一个中央时钟来提供所有的滴答并通知属性

代码语言:javascript
复制
public enum Animator {

    INSTANCE;

    private Timer timer;

    private List<AnimationProperties> properies;

    private Animator() {
        properies = new ArrayList<>(5);
        timer = new Timer(5, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                Iterator<AnimationProperties> it = properies.iterator();
                while (it.hasNext()) {
                    AnimationProperties ap = it.next();
                    if (ap.tick()) {
                        it.remove();
                    }
                }
                if (properies.isEmpty()) {
                    timer.stop();
                }
            }
        });
    }

    public void add(AnimationProperties ap) {
        properies.add(ap);
        timer.start();
    }

    public void remove(AnimationProperties ap) {
        properies.remove(ap);
        if (properies.isEmpty()) {
            timer.stop();
        }
    }

}

好的,这是一个非常简单的实现。它只是一个运行速度非常快的Swing Timer。当有AnimationProperties可用时,它继续循环,直到所有AnimationProperties tick方法返回true (已完成),此时它停止(因此它不会执行,除非在后台进行操作)

好吧,但这对我们有什么帮助呢?

基本上,我们要做的就是计算组件在一段时间内从给定值到给定值的新width。由于width被定义为int,因此我们可以基于上面的抽象/泛型类创建一系列具体的类,例如...

代码语言:javascript
复制
public class IntRange extends Range<Integer> {
    
    public IntRange(Integer from, Integer to) {
        super(from, to);
    }
    
    public Integer getDistance() {
        return getTo() - getFrom();
    }
}

public class IntAnimationProperties extends AbstractAnimationProperties<Integer> {

    public IntAnimationProperties(IntRange animationRange, IntRange maxRange, Duration duration, AnimationPropertiesListener<Integer> listener) {
        super(animationRange, listener);
        
        int maxDistance = maxRange.getDistance();
        int aniDistance = animationRange.getDistance();

        double progress = Math.min(100, Math.max(0, Math.abs(aniDistance/ (double)maxDistance)));
        Duration remainingDuration = Duration.ofMillis((long)(duration.toMillis() * progress));
        setDuration(remainingDuration);
    }

    @Override
    public Integer calculateValue(double progress) {
        IntRange range = (IntRange)getRange();
        int distance = range.getDistance();
        int value = (int) Math.round((double) distance * progress);
        value += range.getFrom();
        return value;
    }

}

这里真正需要注意的是,IntAnimationProperties还需要一个“最大范围”值。这是动画预计会超出的总可用范围。这用于计算“动画范围”所在的当前“进度”值。

考虑一下当用户退出时,如果一个面板是半展开的,会发生什么?通常情况下,它必须使用整个持续时间来重新设置该一半范围的动画。

相反,此实现计算移动到所需点所需的“剩余”持续时间,在上面的示例中,这是正常持续时间的一半。

例如。

因此,基于以上内容,我们可能会得到如下结果:

代码语言:javascript
复制
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Test {

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

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

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        public TestPane() {
            setLayout(null);
            Slider slider1 = new Slider();
            slider1.setBackground(Color.BLUE);
            slider1.setLocation(0, 44);         
            add(slider1);

            Slider slider2 = new Slider();
            slider2.setBackground(Color.MAGENTA);
            slider2.setLocation(0, 88);
            add(slider2);
        }

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

    }

    public class Slider extends JPanel {

        private AnimationProperties<Integer> ap;

        private IntRange maxRange = new IntRange(44, 150);
        private Duration duration = Duration.ofSeconds(5);

        public Slider() {
            setSize(44, 44);

            addMouseListener(new MouseAdapter() {
                @Override
                public void mouseEntered(MouseEvent e) {
                    animateTo(150);
                }

                @Override
                public void mouseExited(MouseEvent e) {
                    animateTo(44);
                }

                public void animateTo(int to) {
                    if (ap != null) {
                        Animator.INSTANCE.remove(ap);
                    }
                    IntRange animationRange = new IntRange(getWidth(), to);
                    ap = new IntAnimationProperties(animationRange, maxRange, duration, new AnimationPropertiesListener<Integer>() {
                        @Override
                        public void stateChanged(AnimationProperties<Integer> animator) {
                            setSize(animator.getValue(), 44);
                            repaint();
                        }
                    });
                    Animator.INSTANCE.add(ap);
                }

            });
        }

    }

    public enum Animator {

        INSTANCE;

        private Timer timer;

        private List<AnimationProperties> properies;

        private Animator() {
            properies = new ArrayList<>(5);
            timer = new Timer(5, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    Iterator<AnimationProperties> it = properies.iterator();
                    while (it.hasNext()) {
                        AnimationProperties ap = it.next();
                        if (ap.tick()) {
                            it.remove();
                        }
                    }
                    if (properies.isEmpty()) {
                        timer.stop();
                    }
                }
            });
        }

        public void add(AnimationProperties ap) {
            properies.add(ap);
            timer.start();
        }

        public void remove(AnimationProperties ap) {
            properies.remove(ap);
            if (properies.isEmpty()) {
                timer.stop();
            }
        }

    }

    public interface AnimationProperties<T> {
        public Range<T> getRange();
        public T getValue();
        public boolean tick();

        public void setDuration(Duration duration);
        public Duration getDuration();
    }

    public interface AnimationPropertiesListener<T> {
        public void stateChanged(AnimationProperties<T> animator);
    }

    public class Range<T> {
        private T from;
        private T to;

        public Range(T from, T to) {
            this.from = from;
            this.to = to;
        }

        public T getFrom() {
            return from;
        }

        public T getTo() {
            return to;
        }

        @Override
        public String toString() {
            return "From " + getFrom() + " to " + getTo();
        }

    }

    public abstract class AbstractAnimationProperties<T> implements AnimationProperties<T> {

        private Range<T> range;

        private LocalDateTime startTime;

        private Duration duration = Duration.ofSeconds(5);

        private T value;

        private AnimationPropertiesListener<T> listener;

        public AbstractAnimationProperties(Range<T> range, AnimationPropertiesListener<T> listener) {
            this.range = range;
            this.value = range.getFrom();

            this.listener = listener;
        }

        public void setDuration(Duration duration) {
            this.duration = duration;
        }

        public Duration getDuration() {
            return duration;
        }

        public Range<T> getRange() {
            return range;
        }

        @Override
        public T getValue() {
            return value;
        }

        @Override
        public boolean tick() {
            if (startTime == null) {
                startTime = LocalDateTime.now();
            }
            Duration duration = getDuration();
            Duration runningTime = Duration.between(startTime, LocalDateTime.now());
            Duration timeRemaining = duration.minus(runningTime);
            if (timeRemaining.isNegative()) {
                runningTime = duration;
            }
            double progress = (runningTime.toMillis() / (double) duration.toMillis());
            value = calculateValue(progress);

            listener.stateChanged(this);

            return progress >= 1.0;
        }

        public abstract T calculateValue(double progress);

    }

    public class IntRange extends Range<Integer> {

        public IntRange(Integer from, Integer to) {
            super(from, to);
        }

        public Integer getDistance() {
            return getTo() - getFrom();
        }
    }

    public class IntAnimationProperties extends AbstractAnimationProperties<Integer> {

        public IntAnimationProperties(IntRange animationRange, IntRange maxRange, Duration duration, AnimationPropertiesListener<Integer> listener) {
            super(animationRange, listener);

            int maxDistance = maxRange.getDistance();
            int aniDistance = animationRange.getDistance();

            double progress = Math.min(100, Math.max(0, Math.abs(aniDistance/ (double)maxDistance)));
            Duration remainingDuration = Duration.ofMillis((long)(duration.toMillis() * progress));
            setDuration(remainingDuration);
        }

        @Override
        public Integer calculateValue(double progress) {
            IntRange range = (IntRange)getRange();
            int distance = range.getDistance();
            int value = (int) Math.round((double) distance * progress);
            value += range.getFrom();
            return value;
        }

    }

}

但那真的很慢,☹️

好的,有两个地方你改变了持续时间。AbstractAnimationProperties的默认Duration5秒,Slider的默认Duration5秒。在您的例子中,更改SliderDuration可能是您想要开始的地方。

哦,等等,你想要"easement“(慢进慢出)吗?好了,先看看How can I implement easing functions with a thread,然后再看看我之前链接的动画框架,因为他们已经这么做了

现在,如果您真的打算手动完成这项工作,您可以看看this gist,它是一个基于“时间线”的实现,支持easement

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/50651974

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档