You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Cljfx实现方块按键移动动画遇阻,求解决方案

如何用Cljfx实现按键触发的方块持续移动动画

你的问题核心是UI线程被阻塞:JavaFX的事件处理与UI渲染共用同一个线程,你在event-handle里直接调用Thread/sleep会卡住整个UI线程,所有渲染请求都会积压到循环结束后才一次性执行,因此看不到中间的动画过程。

Cljfx完全可以实现这类简单动画,只要避开阻塞UI线程的操作,以下是两种可行的解决方案:

方案1:异步线程配合UI线程更新

将移动逻辑放到独立线程中执行,每次更新状态后,确保在UI线程触发渲染操作:

修改后的事件处理代码

(defn move-step [dx dy times delay]
  (future
    (dotimes [_ times]
      (swap! state* (fn [s] (-> s
                                (update :x + dx)
                                (update :y + dy))))
      ; 强制在UI线程执行渲染
      (fx/on-fx-thread (renderer {:fx/type root :state @state*}))
      (Thread/sleep delay))))

(defn event-handle [event]
  (if (= :event/scene-event (:event/type event))
    (let [key-code (.getCode ^KeyEvent (:fx/event event))]
      (cond
        (= KeyCode/RIGHT key-code) (move-step 10 0 3 300)
        (= KeyCode/LEFT key-code) (move-step -10 0 3 300)
        (= KeyCode/DOWN key-code) (move-step 0 10 3 150)
        (= KeyCode/UP key-code) (move-step 0 -10 3 150)))))

方案2:使用JavaFX原生Timeline动画(推荐)

利用JavaFX的Timeline组件管理动画帧,由框架负责线程调度和帧率控制,更符合JavaFX的设计规范:

完整修改后的core.clj代码

(ns examp
  (:require [cljfx.api :as fx])
  (:import [javafx.scene.canvas Canvas]
           [javafx.scene.paint Color]
           [javafx.scene.input KeyCode KeyEvent]
           [javafx.animation Timeline KeyFrame]
           [javafx.util Duration]))

(def ^:const WIDTH 300)  ;Canvas width
(def ^:const HEIGHT 300) ;Canvas height
(declare root)
(declare renderer)

; 可变状态
(def state*
  (atom
   {:x (/ WIDTH 2)
    :y (/ HEIGHT 2)}))

; 画布组件定义
(defn canvas [{:keys [width height x y]}]
  {:fx/type :canvas
   :width width
   :height height
   :draw (fn [^Canvas canvas]
           (doto (.getGraphicsContext2D canvas)
             (.setFill Color/BLACK)                           
             (.fillRect 0 0 width height)
             (.setFill Color/GREEN)
             (.fillRect x y 10 10)))})

; 创建移动动画时间线
(defn create-move-timeline [dx dy times delay]
  (let [timeline (Timeline.)
        frame (KeyFrame. (Duration/millis delay)
                         (fn [_]
                           (swap! state* (fn [s] (-> s
                                                     (update :x + dx)
                                                     (update :y + dy))))
                           (renderer {:fx/type root :state @state*})))]
    (.setCycleCount timeline times)
    (.add (.getKeyFrames timeline) frame)
    (.play timeline)))

; 事件处理函数
(defn event-handle [event]
  (if (= :event/scene-event (:event/type event))
    (let [key-code (.getCode ^KeyEvent (:fx/event event))]
      (cond
        (= KeyCode/RIGHT key-code) (create-move-timeline 10 0 3 300)
        (= KeyCode/LEFT key-code) (create-move-timeline -10 0 3 300)
        (= KeyCode/DOWN key-code) (create-move-timeline 0 10 3 150)
        (= KeyCode/UP key-code) (create-move-timeline 0 -10 3 150)))))

; 主窗口组件
(defn root [{:keys [state]}]
  {:fx/type :stage
   :showing true
   :scene {:fx/type :scene
           :on-key-pressed {:event/type :event/scene-event}
           :root {:fx/type :v-box
                  :children [{:fx/type canvas
                              :width WIDTH
                              :height HEIGHT
                              :x (:x state)
                              :y (:y state)}]}}})

; 创建渲染器
(def renderer
  (fx/create-renderer
   :opts {:fx.opt/map-event-handler event-handle}))

; 启动应用
(renderer {:fx/type root :state @state*})

关键注意事项

  • 绝对不要在UI线程中执行阻塞操作(如Thread/sleep),否则会导致UI无响应或延迟刷新。
  • 所有UI相关的更新操作,必须确保在JavaFX的UI线程中执行,方案1用fx/on-fx-thread实现这一点,方案2由Timeline自动管理。
  • Timeline方案更适合复杂动画场景,能更好地控制帧率和动画生命周期。

内容的提问来源于stack exchange,提问作者user324885

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 08:56:17