Mobile Development 19 min read

Implementing a Drag‑and‑Bounce Custom View with Explosion Effect in Android (Kotlin)

This tutorial explains how to build an Android custom view that lets a large circle be dragged, scales a smaller circle based on distance, snaps back with a bounce animation when released inside a boundary, or plays an explosion sequence when released outside, using Kotlin, Bézier curves, and WindowManager integration.

Sohu Tech Products
Sohu Tech Products
Sohu Tech Products
Implementing a Drag‑and‑Bounce Custom View with Explosion Effect in Android (Kotlin)

Introduction: The article demonstrates how to create a draggable and elastic custom view in Android using Kotlin, showing the required environment (Android Studio 4.1.3, Kotlin 1.5.0, Gradle 6.5).

Basic drawing: It defines a class TempView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : View(context, attrs, defStyleAttr) { ... } that draws a large red circle, a smaller circle, and an auxiliary transparent circle, and explains the purpose of each.

Touch handling: The override fun onTouchEvent(event: MotionEvent): Boolean { when(event.action) { MotionEvent.ACTION_DOWN -> { isMove = bigPointF.contains(PointF(event.x, event.y), BIG_RADIUS) } MotionEvent.ACTION_MOVE -> { if(isMove) { bigPointF.x = event.x; bigPointF.y = event.y } } MotionEvent.ACTION_UP -> { /* handled later */ } } invalidate(); return true } method detects touch actions, using a custom fun PointF.contains(b: PointF, bPadding: Float = 0f): Boolean { val isX = this.x <= b.x + bPadding && this.x >= b.x - bPadding; val isY = this.y <= b.y + bPadding && this.y >= b.y - bPadding; return isX && isY } to determine if the touch is inside the large circle.

Geometry calculations: The distance between circles is computed with the Pythagorean theorem in private fun distance(): Float { val current = bigPointF - smallPointF; return sqrt(current.x.toDouble().pow(2.0) + current.y.toDouble().pow(2.0)).toFloat() } , a ratio is limited to the golden‑section value 0.618, and the small‑circle radius is adjusted proportionally: val smallRadius = SMALL_RADIUS - SMALL_RADIUS * ratio .

Bezier curve: Four points (P1‑P4) and a control point are calculated to draw a smooth quadratic Bézier path connecting the circles in private fun drawBezier(canvas: Canvas, smallRadius: Float, bigRadius: Float) { val current = bigPointF - smallPointF; val BF = current.y.toDouble(); val FD = current.x.toDouble(); val BDF = atan(BF / FD); val p1X = smallPointF.x + smallRadius * sin(BDF); val p1Y = smallPointF.y - smallRadius * cos(BDF); /* ... compute p2, p3, p4 ... */ val controlPointX = current.x / 2 + smallPointF.x; val controlPointY = current.y / 2 + smallPointF.y; val path = Path(); path.moveTo(p1X.toFloat(), p1Y.toFloat()); path.quadTo(controlPointX, controlPointY, p2X.toFloat(), p2Y.toFloat()); path.lineTo(p4X.toFloat(), p4Y.toFloat()); path.quadTo(controlPointX, controlPointY, p3X.toFloat(), p3Y.toFloat()); path.close(); canvas.drawPath(path, paint) } .

Animations: A private fun bigAnimator(): ValueAnimator = ObjectAnimator.ofObject(this, "bigPointF", PointFEvaluator(), PointF(width/2f, height/2f)).apply { duration = 400; interpolator = OvershootInterpolator(3f) } provides a bounce‑back effect, while private val explodeAnimator by lazy { ObjectAnimator.ofInt(this, "explodeIndex", 19, -1).apply { duration = 1000 } } cycles through 20 bitmap frames to simulate an explosion.

WindowManager integration: For drag‑outside‑view scenarios, the view is added to the WindowManager with layout parameters that exclude the status bar. The status‑bar height is obtained via fun Context.statusBarHeight(): Int { var height = 0; val resourceId = resources.getIdentifier("status_bar_height", "dimen", "android"); if(resourceId > 0) height = resources.getDimensionPixelSize(resourceId); return height } , and the layout params are created with WindowManager.LayoutParams(screenWidth, screenHeight, WindowManager.LayoutParams.TYPE_APPLICATION, WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN, PixelFormat.TRANSPARENT) .

Bitmap handling: The original view’s bitmap is captured with fun View.getBackgroundBitMap(): Bitmap = this.apply { buildDrawingCache() }.drawingCache , transferred to the drag view via dragView.upDataBitMap(bitMap, bitMap.width.toFloat()) , and cleared after the interaction.

Final composition: The article combines all pieces—drawing, touch logic, geometry, Bézier, animations, window management, and bitmap copying—into a complete draggable, bounce‑back, and explode effect, also showing usage inside a RecyclerView where the parent view requests requestDisallowInterceptTouchEvent(true) to ensure proper event handling.

animationAndroidKotlinWindowManagerDrag and DropCustom ViewBezier Curve
Sohu Tech Products
Written by

Sohu Tech Products

A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.