使用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
相关产品推荐
相关产品推荐

