如何用FlingAnimation实现WindowManager悬浮布局的Y轴动力学平移?
解决WindowManager悬浮View的Y轴Fling动效问题
你这是踩了WindowManager悬浮View的典型坑——它的位置逻辑和普通View完全不一样,直接用系统自带的SCROLL_Y或TRANSLATION_Y肯定达不到预期效果。下面给你一套能直接跑的解决方案:
核心问题拆解
SCROLL_Y是用来控制可滚动容器(比如ScrollView)的内部滚动偏移,和悬浮View的全局屏幕位置半毛钱关系都没有;TRANSLATION_Y是在View原有布局位置上叠加的偏移,但悬浮View的"原有位置"就是WindowManager.LayoutParams.y,叠加后会导致位置计算彻底混乱;- 要实现悬浮View的Y轴Fling,必须直接对
LayoutParams.y做动画,这就需要我们自定义动画属性。
具体实现步骤
1. 自定义动画属性,直接操作params.y
先创建一个专门用来更新悬浮View Y轴位置的自定义属性,绑定到LayoutParams.y上:
private WindowManager.LayoutParams floatingParams; private WindowManager windowManager; private View floatingView; // 初始化时保存必要的引用(从你调用WindowManager.addView时获取) public void initFloatingView(View view, WindowManager wm, WindowManager.LayoutParams lp) { this.floatingView = view; this.windowManager = wm; this.floatingParams = lp; } // 自定义Y轴位置动画属性 private final DynamicAnimation.ViewProperty Y_POSITION = new DynamicAnimation.ViewProperty("yPosition") { @Override public void setValue(View view, float value) { floatingParams.y = Math.round(value); windowManager.updateViewLayout(floatingView, floatingParams); } @Override public float getValue(View view) { return floatingParams.y; } };
2. 修正触摸事件逻辑,获取Fling所需的速度
在ACTION_MOVE里你已经在更新位置了,关键是在触摸结束时,用VelocityTracker拿到触摸速度,作为Fling动画的起始速度:
private float initialTouchY; private int initialY; private final VelocityTracker velocityTracker = VelocityTracker.obtain(); @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: initialTouchY = event.getRawY(); initialY = floatingParams.y; velocityTracker.clear(); velocityTracker.addMovement(event); // 触摸开始时必须停止之前的动画,避免拖拽和动画冲突 DynamicAnimation.animate(floatingView, Y_POSITION).cancel(); return true; case MotionEvent.ACTION_MOVE: velocityTracker.addMovement(event); floatingParams.y = initialY + (int) (event.getRawY() - initialTouchY); windowManager.updateViewLayout(floatingView, floatingParams); // 保留你自己的isMovingInYAxis逻辑 if (isMovingInYAxis((int) initialTouchY, (int) event.getRawY())) { // ...你的其他业务逻辑 } return true; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: velocityTracker.computeCurrentVelocity(1000); // 速度单位:像素/秒 float flingVelocityY = velocityTracker.getYVelocity(); // 启动Fling动画 startYFlingAnimation(flingVelocityY); velocityTracker.recycle(); return true; default: return false; } }
3. 实现符合预期的Fling动画
现在用自定义的Y_POSITION属性创建Fling动画,记得设置屏幕边界,防止View被拖出屏幕:
private void startYFlingAnimation(float startVelocity) { // 获取屏幕高度,计算合理的边界值 DisplayMetrics metrics = floatingView.getResources().getDisplayMetrics(); int screenHeight = metrics.heightPixels; int viewHeight = floatingView.getHeight(); FlingAnimation fling = new FlingAnimation(floatingView, Y_POSITION); fling.setStartVelocity(startVelocity) // 最小Y值:View顶部不能超出屏幕 .setMinValue(0) // 最大Y值:View底部不能超出屏幕 .setMaxValue(screenHeight - viewHeight) .setFriction(0.2f); // 摩擦系数,越小滑得越远,可根据需求调整 fling.start(); }
额外注意事项
- 触摸开始时一定要取消正在运行的动画,不然拖拽和动画会打架;
- 必须设置
minValue和maxValue,不然View可能会被滑出屏幕看不见; - 如果之前用过
TRANSLATION_Y,记得把它重置为0,避免和params.y的位置叠加; VelocityTracker要记得回收,避免内存泄漏。
按照这个方案修改后,你的悬浮View应该就能实现自然的Y轴Fling动效了!
内容的提问来源于stack exchange,提问作者redrom
相关产品推荐
相关产品推荐

