Android开发:Thread内ImageView动画运行时X轴坐标不更新
问题根因
你遇到的X坐标不更新问题由三个错误共同导致:
- 你使用的
ltrAnim、rtlAnim属于传统视图动画(View Animation)体系,这类动画只会在绘制阶段临时调整画布偏移制造移动效果,不会真实修改View的布局属性,不管动画把View画到哪里,getX()、getLeft()这类方法拿到的永远是动画启动前的原始坐标,不会跟随绘制位置变化。 - 你只在
run()方法的最开始执行了一次enemyX = enemy.getX(),后续循环逻辑里完全没有更新这个变量,哪怕坐标本身会变,你存的enemyX也永远是初始值。 - 你直接在自定义子线程里调用
startAnimation()操作UI,违反Android主线程UI模型,轻则动画渲染异常,重则直接触发CalledFromWrongThreadException崩溃。
修复方案
方案1:改用属性动画(推荐,适配实时坐标需求)
属性动画会真实修改View的对应布局属性,任何时候调用enemy.getX()都能拿到实时位置,不需要手动维护坐标变量,也不需要自己开线程做时序调度,示例代码:
// 删掉原有自定义EnemyMoveThread逻辑,不要自行开子线程调度UI动画 // 左右往返动画直接通过属性动画组合实现 float leftBound = 0f; // 替换为你的X轴左边界值 float rightBound = 300f; // 替换为你的X轴右边界值 long singleAnimDuration = 600L; // 单次移动时长和原逻辑的sleep时长对齐 // 向右移动属性动画,会真实修改View的x属性 ObjectAnimator moveRight = ObjectAnimator.ofFloat(enemy, "x", enemy.getX(), rightBound); moveRight.setDuration(singleAnimDuration); // 向左移动属性动画 ObjectAnimator moveLeft = ObjectAnimator.ofFloat(enemy, "x", rightBound, leftBound); moveLeft.setDuration(singleAnimDuration); // 组合为循环往返逻辑 AnimatorSet loopAnim = new AnimatorSet(); loopAnim.playSequentially(moveRight, moveLeft); loopAnim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { // 未触发结束条件则循环播放 if (!disqualified()) { loopAnim.start(); } } }); // 启动动画 loopAnim.start();
如果要同时实现下落效果,额外加一个Y轴方向的属性动画,和X轴动画一起放进AnimatorSet调用playTogether()同步播放即可,不需要额外写线程逻辑。
方案2:兼容原有视图动画的改法
如果不想替换已写好的视图动画资源,需要手动补全属性同步和主线程切换逻辑:
// 给两个动画加结束监听,手动同步View的真实位置 ltrAnim.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) {} @Override public void onAnimationRepeat(Animation animation) {} @Override public void onAnimationEnd(Animation animation) { enemy.setX(rightBound); enemyX = rightBound; } }); rtlAnim.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) {} @Override public void onAnimationRepeat(Animation animation) {} @Override public void onAnimationEnd(Animation animation) { enemy.setX(leftBound); enemyX = leftBound; } }); // 所有UI操作必须切到主线程执行,不要直接在子线程调用startAnimation new Handler(Looper.getMainLooper()).post(() -> enemy.startAnimation(ltrAnim));
注意:这个方案只能拿到动画起止点的坐标,动画移动过程中的中间坐标无法实时获取,如果需要做碰撞检测这类依赖实时位置的逻辑,必须用方案1的属性动画实现。
额外注意事项
- 不要用
Thread+sleep的方式做动画时序控制,Android原生动画框架已经处理好了主线程调度、生命周期同步问题,自己写子线程很容易出现内存泄漏、页面销毁后线程仍在运行的异常 - 如果需要在动画过程中实时获取坐标,给属性动画加
addUpdateListener回调即可,在回调里取enemy.getX()就是当前实时位置
内容的提问来源于stack exchange,提问作者yonimal777
相关产品推荐
相关产品推荐

