如何在Flutter自定义Tile中实现滑动高亮动画?
自定义Tile滑动高亮动画实现方案
根据你描述的滑动跟随高亮效果,以下是不同平台的可行实现建议:
Android 平台实现
- 核心逻辑:通过自定义View的触摸监听+动态渐变Shader绘制高亮层,配合动画实现平滑过渡
- 具体步骤:
- 给自定义Tile(ViewGroup/View)添加
OnTouchListener,捕获ACTION_DOWN/ACTION_MOVE/ACTION_UP事件,记录触摸坐标 - 自定义绘制逻辑,使用
LinearGradient创建透明到半白的渐变Shader,根据触摸位置动态调整渐变的起止点 - 在Tile的
onDraw方法中,根据触摸状态绘制高亮层;手指离开时,用ValueAnimator实现高亮淡出动画 - 关键代码片段:
tileView.setOnTouchListener { v, event -> when (event.action) { MotionEvent.ACTION_DOWN -> startHighlight(event.x, event.y) MotionEvent.ACTION_MOVE -> updateHighlight(event.x, event.y) MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> endHighlight() } true } // 自定义绘制高亮的方法 private fun drawHighlight(canvas: Canvas, touchX: Float) { val gradient = LinearGradient( touchX - 200, 0f, touchX + 200, 0f, intArrayOf(Color.TRANSPARENT, Color.parseColor("#66FFFFFF"), Color.TRANSPARENT), floatArrayOf(0f, 0.5f, 1f), Shader.TileMode.CLAMP ) paint.shader = gradient canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), paint) }
- 给自定义Tile(ViewGroup/View)添加
iOS 平台实现
- 核心逻辑:利用CALayer创建渐变高亮遮罩,通过触摸事件控制遮罩层的位置与动画
- 具体步骤:
- 自定义Tile继承自UIView,重写
touchesBegan/touchesMoved/touchesEnded方法获取触摸点 - 创建
CAGradientLayer作为高亮层,设置渐变颜色为[UIColor.clear, UIColor.white.withAlphaComponent(0.3), UIColor.clear],初始隐藏 - 触摸移动时实时更新高亮层的
position和startPoint/endPoint,用UIViewPropertyAnimator实现平滑过渡;手指离开时执行淡出动画并移除层 - 关键代码片段:
private var highlightLayer: CAGradientLayer? override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { guard let touch = touches.first else { return } let point = touch.location(in: self) setupHighlightLayer(at: point) } override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) { guard let touch = touches.first, let layer = highlightLayer else { return } let point = touch.location(in: self) layer.position = CGPoint(x: point.x, y: bounds.midY) } private func setupHighlightLayer(at point: CGPoint) { highlightLayer = CAGradientLayer() highlightLayer?.colors = [UIColor.clear.cgColor, UIColor.white.withAlphaComponent(0.3).cgColor, UIColor.clear.cgColor] highlightLayer?.startPoint = CGPoint(x: 0, y: 0.5) highlightLayer?.endPoint = CGPoint(x: 1, y: 0.5) highlightLayer?.frame = bounds highlightLayer?.position = CGPoint(x: point.x, y: bounds.midY) layer.addSublayer(highlightLayer!) }
- 自定义Tile继承自UIView,重写
Flutter 平台实现
- 核心逻辑:用GestureDetector监听触摸,配合CustomPaint绘制动态高亮层,结合AnimationController控制动画
- 具体步骤:
- 用
GestureDetector包裹Tile组件,监听onPanDown/onPanUpdate/onPanEnd事件,记录触摸位置 - 在Stack布局中,Tile上方叠加
CustomPaint组件,通过自定义Painter绘制跟随触摸点的渐变高亮 - 手指离开时,用
AnimationController驱动高亮层的透明度从0.3降到0,实现淡出效果 - 关键代码片段:
class HighlightTile extends StatefulWidget { @override State<HighlightTile> createState() => _HighlightTileState(); } class _HighlightTileState extends State<HighlightTile> with SingleTickerProviderStateMixin { Offset? _touchPos; late AnimationController _animController; late Animation<double> _opacityAnim; @override void initState() { super.initState(); _animController = AnimationController(vsync: this, duration: Duration(milliseconds: 300)); _opacityAnim = Tween<double>(begin: 0.3, end: 0).animate(_animController); } @override Widget build(BuildContext context) { return GestureDetector( onPanDown: (details) => setState(() => _touchPos = details.localPosition), onPanUpdate: (details) => setState(() => _touchPos = details.localPosition), onPanEnd: (details) { _animController.forward().then((_) { setState(() => _touchPos = null); _animController.reset(); }); }, child: Stack( children: [ // 你的Tile内容组件 Container(width: 200, height: 100, color: Colors.blueGrey), if (_touchPos != null) AnimatedBuilder( animation: _opacityAnim, builder: (context, child) => CustomPaint( painter: HighlightPainter(_touchPos!, opacity: _opacityAnim.value), size: Size.infinite, ), ) ], ), ); } } class HighlightPainter extends CustomPainter { final Offset touchPos; final double opacity; HighlightPainter(this.touchPos, {required this.opacity}); @override void paint(Canvas canvas, Size size) { final paint = Paint() ..shader = LinearGradient( colors: [ Colors.transparent, Colors.white.withOpacity(opacity), Colors.transparent ], begin: Offset(touchPos.dx - 150, touchPos.dy), end: Offset(touchPos.dx + 150, touchPos.dy), ).createShader(Offset.zero & size); canvas.drawRect(Offset.zero & size, paint); } @override bool shouldRepaint(covariant HighlightPainter oldDelegate) { return oldDelegate.touchPos != touchPos || oldDelegate.opacity != opacity; } }
- 用
通用优化建议
- 高亮透明度控制在0.2~0.4之间,避免遮挡Tile原有内容
- 触摸位置更新时使用动画过渡,避免高亮层跳变
- 限制高亮层范围在Tile内部,防止超出边界
内容的提问来源于stack exchange,提问作者Mayur Sankhla
相关产品推荐
相关产品推荐

