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.
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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
