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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

animationAndroidKotlinWindowManagerDrag-and-DropCustom View
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

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.