How to Make a Custom Balloon Burst Animation Widget in Flutter

In this article, we learn an animation concept for a balloon burst animation. This may look like a simple animation, but the concept behind it is at an advanced level. By using this, you can create your own animations for celebrations, birthdays, and order-completed pages in shopping apps, etc.

The main purpose of this article is to provide the source code for this balloon burst animation so that you can use it in your own application.


Dart Files for the Balloon Burst Animation Widget

The source code includes the following files:

  • balloon.dart
  • balloon_item.dart
  • particle.dart
  • particle_controller.dart
  • particle_painter.dart
  • balloon_burst_widget.dart

How to Use the Balloon Burst Animation Widget in Your App

Copy all the files mentioned above from the source code and paste them into your own project. Then use the widget class “BalloonBurstWidget” from the balloon_burst_widget.dart file.
After that, pass a list of your own images to this widget.

For testing purposes, you can also copy all the images from the source code assets folder.



Download Source Code

Want more ready-to-use source code for your projects? Explore our Source Codes page, where you’ll find examples from various programming languages and frameworks that can help you bring your ideas to life.

How to Use Jetpack Compose and Flutter to Make a Custom Loading Animation for Mobile App

Loading indicators are an important part of making a mobile experience look and feel smooth and professional. A lot of developers use built-in progress bars or spinners, but custom animations can make your app look different, help with branding, and make it feel like it’s flowing.

This guide will show you how to use Jetpack Compose (for Android) and Flutter to make a more complex custom loading animation for mobile apps. The goal is to make a fun-to-watch multi-ring spinner animation that spins in different directions, at different speeds, and with arcs of different shapes. Your UI will look fresh and new with this.

⭐ Why It’s Important to Have a Custom Loading Animation

A good loading animation will give you

  • More interaction with users
  • Different looks for each brand
  • There are no issues when you switch screens.
  • It feels like a premium app.

People believe they don’t have to wait as long.

You can make complicated animated parts with very little code using Jetpack Compose and Flutter, two modern UI frameworks.

Jetpack Compose for Android lets you make your own loading animation.

Developers can make UI in a declarative way with Jetpack Compose and use powerful APIs to add animations. We want to make a spinner with a lot of rings that are stacked on top of each other and spin by themselves.

1. Model for Setting Up a Ring

First, we discuss the features of each animated ring:

  • Radius

  • Color

  • Stroke width

  • Opacity

  • Arc length

  • Direction of rotation

  • Rotation speed

These properties allow full customization for each ring in the spinner.

RingConfig Model

 
data class RingConfig(
    val id: String,
    val radius: Float,
    val color: Color,
    val strokeWidth: Float,
    val opacity: Float,
    val arcLength: Float,
    val direction: Int,     // +1 or -1
    val speed: Float        // seconds per rotation
)
 
2. Creating the Custom Loading Animation

For smooth animations that never end, Compose has rememberInfiniteTransition.

Each ring has its own float animation that shows how it turns:

  • Independent rotation for each ring

  • Control over speed and direction

  • Smooth, continuous animation

3. Drawing on a Canvas

There are dashed arcs and a rotating effect on the rings drawn on Canvas.

This setup allows:

  • Changeable radius
  • Stroke width that can be changed
  • Dividing arcs into parts
  • Each layer can move by itself.

These pieces work together to make a loading animation that looks great and works well with Android apps that are up to date.

Spinner Composable Function



@Composable
fun Loading(
    rings: List,
    size: Dp = 400.dp
) {
    //  All animations must be created here, NOT inside Canvas
    val transition = rememberInfiniteTransition()

    // Pre-calc animations for each ring
    val rotations = rings.map { ring ->
        transition.animateFloat(
            initialValue = 0f,
            targetValue = ring.direction * 360f,
            animationSpec = infiniteRepeatable(
                animation = tween(
                    durationMillis = (ring.speed * 1000).toInt(),
                    easing = LinearEasing
                ),
                repeatMode = RepeatMode.Restart
            )
        )
    }

    Box(
        modifier = Modifier.size(size),
    ) {
        Canvas(modifier = Modifier.fillMaxSize()) {

            val center = Offset(this.size.width / 2, this.size.height / 2)

            rings.forEachIndexed { index, ring ->
                val rotation = rotations[index].value

                val circumference = 2f * PI.toFloat() * ring.radius
                val dashArray = circumference * ring.arcLength
                val dashGap = circumference - dashArray

                rotate(rotation, pivot = center) {
                    drawCircle(
                        color = ring.color.copy(alpha = ring.opacity),
                        radius = ring.radius,
                        center = center,
                        style = Stroke(
                            width = ring.strokeWidth,
                            cap = StrokeCap.Round,
                            pathEffect = PathEffect.dashPathEffect(
                                floatArrayOf(dashArray, dashGap),
                                phase = 0f
                            )
                        )
                    )
                }
            }
        }
    }
}

 

How to use this Loading Composable Function

MainActivity.kt

     
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            LoaderTheme {
                CustomSpinLoading()
            }
        }
    }
}

@Composable
fun CustomSpinLoading() {
    val rings = listOf(
        RingConfig(
            id = "a",
            radius = 50f,
            color = Color.Black,
            strokeWidth = 14f,
            opacity = 1f,
            arcLength = 0.6f,
            direction = -1,
            speed = 6f
        ),
        RingConfig(
            id = "b",
            radius = 80f,
            color = Color.Red,
            strokeWidth = 14f,
            opacity = 1f,
            arcLength = 0.8f,
            direction = 1,
            speed = 8f
        ),
        RingConfig(
            id = "c",
            radius = 120f,
            color = Color.Blue,
            strokeWidth = 14f,
            opacity = 0.9f,
            arcLength = 0.6f,
            direction = -1,
            speed = 9f
        ),
        RingConfig(
            id = "d",
            radius = 160f,
            color = Color.Green,
            strokeWidth = 14f,
            opacity = 0.8f,
            arcLength = 0.5f,
            direction = 1,
            speed = 9f
        ),

        RingConfig(
            id = "e",
            radius = 200f,
            color = Color.Cyan,
            strokeWidth = 14f,
            opacity = 0.8f,
            arcLength = 0.4f,
            direction = -1,
            speed = 8f
        )
    )

    Loading(rings = rings, size = 400.dp)
}
  

 

🟩 Flutter Loading Animation That You Can Change

Flutter has its own animation system that is based on controllers and custom painters, but it works in a similar way to declarative programming.

Check out the code below:

RingConfig.dart Model Class

 
class RingConfig {
final String id;
final double radius;
final Color color;
final double strokeWidth;
final double opacity;
final double arcLength;
final int direction; // +1 or -1
final double speed; // seconds per rotation

RingConfig({
required this.id,
required this.radius,
required this.color,
required this.strokeWidth,
required this.opacity,
required this.arcLength,
required this.direction,
required this.speed,
});
}

 
Drawing with CustomPainter

 
import 'dart:math';
import 'package:flutter/cupertino.dart';
import 'RingConfig.dart';

class LoadingPainter extends CustomPainter {
final List<RingConfig> rings;
final List<Animation<double>> rotations;

LoadingPainter(this.rings, this.rotations);

@override
void paint(Canvas canvas, Size size) {
final center = Offset(size.width / 2, size.height / 2);

for (int i = 0; i < rings.length; i++) {
final ring = rings[i];
final rotation = rotations[i].value;

final paint = Paint()
..color = ring.color.withValues()
..style = PaintingStyle.stroke
..strokeWidth = ring.strokeWidth
..strokeCap = StrokeCap.round;

final circumference = 2 * pi * ring.radius;
final dashLength = circumference * ring.arcLength;
final dashGap = circumference - dashLength;

final circlePath = Path()
..addOval(Rect.fromCircle(center: center, radius: ring.radius));

final dashedPath = _dashPath(circlePath, dashLength, dashGap);

canvas.save();
canvas.translate(center.dx, center.dy);
canvas.rotate(rotation * pi / 180);
canvas.translate(-center.dx, -center.dy);

canvas.drawPath(dashedPath, paint);

canvas.restore();
}
}

@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}

Path _dashPath(Path source, double dashLength, double dashGap) {
final Path dest = Path();
double distance = 0.0;

for (final metric in source.computeMetrics()) {
while (distance < metric.length) {
final next = distance + dashLength;
dest.addPath(metric.extractPath(distance, next), Offset.zero);
distance = next + dashGap;
}
distance = 0.0;
}
return dest;
}

 
Custom Loading Widget


import 'package:flutter/cupertino.dart';
import 'RingConfig.dart';
import 'LoadingPainter.dart';
import 'package:flutter/material.dart';


class Loading extends StatefulWidget {
  final List<RingConfig> rings;
  final double size;

  const Loading({super.key, required this.rings, this.size = 400});

  @override
  State<Loading> createState() => _LoadingState();
}

class _LoadingState extends State<Loading> with TickerProviderStateMixin {
  late List<AnimationController> controllers;
  late List<Animation<double>> rotations;

  @override
  void initState() {
    super.initState();

    controllers = widget.rings.map((ring) {
      return AnimationController(
        duration: Duration(milliseconds: (ring.speed * 1000).toInt()),
        vsync: this,
      )..repeat();
    }).toList();

    rotations = List.generate(
      widget.rings.length,
      (i) => Tween<double>(
        begin: 0,
        end: widget.rings[i].direction * 360,
      ).animate(controllers[i]),
    );
  }

  @override
  void dispose() {
    for (var controller in controllers) {
      controller.dispose();
    }
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      width: widget.size,
      height: widget.size,
      child: AnimatedBuilder(
        animation: Listenable.merge(controllers),
        builder: (_, __) {
          return CustomPaint(painter: LoadingPainter(widget.rings, rotations));
        },
      ),
    );
  }
}

 

How to use this Loading widget in Flutter?

main.dart


import 'package:flutter/material.dart';

import 'RingConfig.dart';
import 'Loading.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Custom Loading',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      ),
      home: const MyHomePage(title: 'Loading Animation'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    final rings = [
      RingConfig(
        id: "a",
        radius: 25,
        color: Colors.black,
        strokeWidth: 8,
        opacity: 1,
        arcLength: 0.6,
        direction: -1,
        speed: 6,
      ),
      RingConfig(
        id: "b",
        radius: 40,
        color: Colors.red,
        strokeWidth: 8,
        opacity: 1,
        arcLength: 0.8,
        direction: 1,
        speed: 8,
      ),
      RingConfig(
        id: "c",
        radius: 60,
        color: Colors.blue,
        strokeWidth: 8,
        opacity: 0.9,
        arcLength: 0.6,
        direction: -1,
        speed: 9,
      ),
      RingConfig(
        id: "d",
        radius: 80,
        color: Colors.green,
        strokeWidth: 8,
        opacity: 0.8,
        arcLength: 0.5,
        direction: 1,
        speed: 9,
      ),
      RingConfig(
        id: "e",
        radius: 100,
        color: Colors.cyan,
        strokeWidth: 8,
        opacity: 0.8,
        arcLength: 0.4,
        direction: -1,
        speed: 8,
      ),
    ];

    return Scaffold(
      backgroundColor: Colors.white,
      body: Center(child: Loading(rings: rings, size: 400)),
    );
  }
}



Download Source Code

Final Thoughts

You can make a loading animation just for mobile apps (Jetpack Compose and Flutter), and it can be very rewarding. Both frameworks give developers everything they need to make motion graphics that are expressive without having to use animated assets or libraries from other companies.

This multi-ring spinner is a great example of:

  • Design of animations that can be changed
  • Framework-level APIs for drawing
  • Animation loops that go on forever and work well
  • Consistency between platforms
    A well-designed loader can make your user interface a lot better, whether you’re making apps just for Android or for more than one platform.

How to Build a Real-Time Blood Pressure App Android UI Using Jetpack Compose

Modern health applications—whether you’re building a blood pressure app Android, an ECG app Android, or a heart rate monitor Android—require interfaces that are clean, smooth, and capable of updating in real time. With Jetpack Compose, Android developers can design advanced animated dashboards using only a few composable functions.

In this guide, we’ll walk through a complete real-time vital monitoring UI built entirely with Canvas, animations, and Jetpack Compose.
The final setup includes:

  • A pressure gauge (mmHg) for blood pressure visualization
  • A real-time ECG waveform renderer
  • A vital simulator that generates dynamic heartbeat, SpO₂, and pressure values

If you’re creating a health monitoring interface, these components provide a great foundation.

1. The Blood Pressure Gauge’s (PressureGauge) user interface

The PressureGauge composable makes a round gauge that is fully animated and looks like the dials on professional sphygmomanometers used in clinics.
Important parts of the gauge

  •  The needle moves smoothly.
  •  It ticks every 10 mmHg.
  •  Major ticks every 20 mmHg.
  •  A soft radial gradient background.
  • A 270° sweep (–135° to +135°), just like a medical pressure meter.

The needle moves smoothly toward the new pressure value when you use animateFloatAsState(). This makes it feel like a real machine, which is great for the UI of an Android blood pressure app.

PressureGauge Code

  
@Composable
fun PressureGauge(
    pressure: Float,
    maxPressure: Float = 300f,
    modifier: Modifier = Modifier
) {
    val animatedPressure by animateFloatAsState(
        targetValue = pressure,
        animationSpec = tween(800, easing = LinearOutSlowInEasing)
    )

    val size = 260.dp
    val center = size / 2

    Box(
        modifier = modifier
            .size(size)
            .padding(12.dp),
        contentAlignment = Alignment.Center
    ) {
        Canvas(modifier = Modifier.fillMaxSize()) {

            val w = this.size.width
            val h = this.size.height
            val cx = w / 2
            val cy = h / 2

            val rotation = -135f + (animatedPressure.coerceAtMost(maxPressure) / maxPressure) * 270f

            // BACKGROUND GRADIENT
            drawCircle(
                brush = Brush.radialGradient(
                    colors = listOf(Color.White, Color(0xFFE2E8F0))
                ),
                radius = w * 0.45f,
                center = Offset(cx, cy)
            )

            // TICKS
            for (i in 0..maxPressure.toInt() step 10) {
                val angle = -135f + (i / maxPressure) * 270f
                val rad = Math.toRadians((angle - 90).toDouble())

                val major = i % 20 == 0
                val length = if (major) 18f else 10f
                val width = if (major) 2f else 1f

                val rOuter = w * 0.4f
                val rInner = rOuter - length

                val x1 = cx + rOuter * cos(rad).toFloat()
                val y1 = cy + rOuter * sin(rad).toFloat()
                val x2 = cx + rInner * cos(rad).toFloat()
                val y2 = cy + rInner * sin(rad).toFloat()

                drawLine(
                    color = if (major) Color(0xFF334155) else Color(0xFF94A3B8),
                    start = Offset(x1, y1), end = Offset(x2, y2),
                    strokeWidth = width
                )
            }

            // NEEDLE
            rotate(rotation, pivot = Offset(cx, cy)) {
                drawLine(
                    start = Offset(cx, cy),
                    end = Offset(cx, cy - w * 0.35f),
                    color = Color(0xFFEF4444),
                    strokeWidth = 6f,
                    cap = StrokeCap.Round
                )
                drawCircle(
                    color = Color(0xFFEF4444),
                    center = Offset(cx, cy),
                    radius = 10f
                )
            }
        }
    }

    Column(horizontalAlignment = Alignment.CenterHorizontally) {
        Text(
            text = animatedPressure.toInt().toString(),
            fontSize = 34.sp,
            fontWeight = FontWeight.Bold,
            color = Color(0xFF1E293B)
        )
        Text(
            text = "mmHg",
            color = Color(0xFF64748B),
            fontSize = 12.sp
        )
    }
}
 
2. PulseECG – A real-time renderer for ECG waveforms

The waveform is the most important visual part of an ECG app for Android. The PulseECG composable makes it look like an ECG trace is moving by constantly moving a list of points.
How it works
Keeps a list of 120 points that keeps changing
Changes values based on two conditions:
active: ECG is working
beating: heartbeats
Makes a dark grid that looks like a medical one
Draws a glowing red ECG path to make it look real
It looks a lot like real ECG monitor output and can easily take real medical data.

PulseECG Code

  
@Composable
fun PulseECG(
    active: Boolean,
    beating: Boolean,
    modifier: Modifier = Modifier
) {
    var points by remember { mutableStateOf(List(120) { 50f }) }
    var tick by remember { mutableStateOf(0) }

    LaunchedEffect(active, beating) {
        while (true) {
            delay(40)

            val new = points.toMutableList()
            new.removeAt(0)

            val updated = when {
                !active -> 50f
                !beating -> 50f + (Math.random().toFloat() - 0.5f) * 8f
                else -> when (tick % 12) {
                    0 -> 50f
                    1 -> 30f
                    2 -> 95f
                    3 -> 10f
                    4 -> 60f
                    else -> 50f
                }
            }
            new.add(updated)
            points = new

            if (beating) tick++
        }
    }

    Canvas(
        modifier = modifier
            .fillMaxWidth()
            .height(110.dp)
            .background(Color(0xFF0F172A))
            .padding(4.dp)
    ) {
        val w = size.width
        val h = size.height

        // GRID
        drawRect(Color(0xFF1E293B))
        for (x in 0 until w.toInt() step 20)
            drawLine(Color(0xFF334155), Offset(x.toFloat(), 0f), Offset(x.toFloat(), h))
        for (y in 0 until h.toInt() step 20)
            drawLine(Color(0xFF334155), Offset(0f, y.toFloat()), Offset(w, y.toFloat()))

        // ECG line
        val path = Path()
        points.forEachIndexed { i, v ->
            val x = (i / points.size.toFloat()) * w
            val y = h - (v / 100f) * h
            if (i == 0) path.moveTo(x, y) else path.lineTo(x, y)
        }

        // Glow
        drawPath(
            path = path,
            color = Color(0xFFEF4444).copy(alpha = 0.4f),
            style = Stroke(width = 8f)
        )

        drawPath(
            path = path,
            color = Color(0xFFEF4444),
            style = Stroke(width = 3f)
        )

    }
}
 
3. VitalSimulator – Generating Real-Time Readings

For demo purposes—or previewing UI—you can use VitalSimulator, a small composable that generates random:

  • Blood pressure
  • Heart rate (BPM)
  • SpO₂ oxygen level

This is especially useful while building a heart rate monitor Android interface.

VitalSimulator Code

 
@Composable
fun VitalSimulator(): Triple<Float, Float, Float> {
    var pressure by remember { mutableStateOf(120f) }
    var bpm by remember { mutableStateOf(75f) }
    var spo2 by remember { mutableStateOf(97f) }

    LaunchedEffect(Unit) {
        while (true) {
            delay(1500)
            pressure = (110..135).random().toFloat()
            bpm = (72..90).random().toFloat()
            spo2 = (96..99).random().toFloat()
        }
    }

    return Triple(pressure, bpm, spo2)
}
Putting It All Together

By combining these composables, you can build a polished dashboard showing:

  • Animated blood pressure
  • Real-time ECG waveform
  • BPM and SpO₂ levels

This is an excellent base for medical-style dashboards or consumer wellness applications.




Download Source Code

Final Thoughts

Jetpack Compose makes it possible to build real-time health monitoring dashboards with only a few clean, efficient composables. Whether you’re working on a blood pressure app Android, an ECG app Android, or a heart rate monitor Android, these components give you a professional, animated, and scalable UI foundation.

Bottle Juice Fill Animation with HTML, CSS & JavaScript

Animations make websites more engaging, and small micro-interactions can add a big impact to the user experience. One fun example is a Bottle Juice Fill Animation — a smooth effect that visually fills a bottle with juice using only HTML, CSS, and JavaScript.

✨ Why This Animation Stands Out

  • Lightweight: No frameworks or libraries required.
  • Interactive: The fill level can be adjusted dynamically with JavaScript.
  • Eye-catching: A perfect micro-animation for portfolios, landing pages, or product sites.
  • Modern: Works in all major browsers.

💻 How It Works

  • HTML provides the bottle structure.
  • CSS creates the bottle shape and smooth filling effect.
  • JavaScript controls the fill percentage and triggers the animation.

This combination results in a physics-inspired, satisfying animation that looks great in modern UI design.




Download Source Code

🚀 Use Cases

  • Product showcase pages (e.g., beverages or liquids)
  • Portfolio projects to impress clients/employers
  • Learning resource for CSS & JS beginners
  • Just for fun — a relaxing, satisfying animation!

🔗 Get more awesome source codes at: Source Codes

Flip Coin Source Code Android Java

If you are searching for a Flip Coin Source Code Android example, this guide will walk you through building a realistic coin flip animation in Android using Java. This example includes sound effects, both sides of the coin visible during flipping, and a clean user interface built with ConstraintLayout.

Why Use This Flip Coin Source Code Android Example?

This project is perfect for beginners and intermediate developers who want to learn about animations, MediaPlayer usage, and responsive layouts. Whether you are creating a decision-making app, a fun game, or just experimenting with Android animations, this Flip Coin Source Code Android will save you time and effort.

Flip Coin Source Code Android – MainActivity.java


public class MainActivity extends AppCompatActivity {

    ImageView coinImage;
    Button btnShowSides, btnFlip;
    int side = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
            Insets systemBars = insets.getInsets(Type.systemBars());
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
            return insets;
        });
        // Set status bar color to black
        getWindow().setStatusBarColor(ContextCompat.getColor(this, R.color.black));

        coinImage = findViewById(R.id.coinImage);
        btnShowSides = findViewById(R.id.btnShowSides);
        btnFlip = findViewById(R.id.btnFlip);
        coinImage.setImageResource(R.drawable.head); //  Set initial image
        btnFlip.setEnabled(true);

        btnShowSides.setOnClickListener(view -> {
            showBothSidesInstant();

        });

        btnFlip.setOnClickListener(view -> flipCoinRealistically());
    }


    private void showBothSidesInstant() {
        if (side == 0) {
            coinImage.setImageResource(R.drawable.tail);
            side = 1;
        } else {
            coinImage.setImageResource(R.drawable.head);
            side = 0;
        }
    }

    private void flipCoinRealistically() {
        boolean isHeads = new Random().nextBoolean();

        // Optional: play coin flip sound
        MediaPlayer mp = MediaPlayer.create(this, R.raw.coin_sound);
        mp.start();
        mp.setOnCompletionListener(MediaPlayer::release);

        // Track which side is showing
        final boolean[] showingHeads = {true};

        // Listener to change image mid-flip
        ValueAnimator flipAnimator = ValueAnimator.ofFloat(0f, 1f);
        flipAnimator.setDuration(5000); // total time
        flipAnimator.setInterpolator(new LinearInterpolator());
        flipAnimator.addUpdateListener(animation -> {
            float progress = (float) animation.getAnimatedValue();
            float totalRotation = progress * 4320f; // 12 full flips (10 fast + 2 slow)
            coinImage.setRotationX(totalRotation);

            // At every 180°, swap image
            if ((int) (totalRotation / 180) % 2 == 0 && !showingHeads[0]) {
                coinImage.setImageResource(R.drawable.head);
                showingHeads[0] = true;
            } else if ((int) (totalRotation / 180) % 2 != 0 && showingHeads[0]) {
                coinImage.setImageResource(R.drawable.tail);
                showingHeads[0] = false;
            }
        });

        // When done, show final result
        flipAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                coinImage.setRotationX(0f);
                coinImage.setImageResource(isHeads ? R.drawable.head : R.drawable.tail);
            }
        });

        flipAnimator.start();
    }
    
}

Flip Coin Source Code Android – XML Layout (activity_main.xml)


<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#121212"
    android:padding="24dp">

    <ImageView
        android:id="@+id/coinImage"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:layout_marginTop="80dp"
        android:contentDescription="Coin Image"
        android:scaleType="centerInside"
        android:src="@drawable/head"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/btnShowSides"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="40dp"
        android:backgroundTint="#2196F3"
        android:text="Show Both Sides"
        android:textColor="#FFFFFF"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/coinImage" />

    <Button
        android:id="@+id/btnFlip"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:backgroundTint="#4CAF50"
        android:enabled="false"
        android:text="Flip Coin"
        android:textColor="#FFFFFF"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/btnShowSides" />

</androidx.constraintlayout.widget.ConstraintLayout>


How to Implement Flip Coin Source Code Android

  1. Copy the Java code into your MainActivity.java file.
  2. Replace the XML layout with the one provided above.
  3. Add head.png and tail.png images in your drawable folder.
  4. Add a coin_sound.mp3 in your res/raw folder.
  5. Run the app to enjoy realistic coin flipping.

Learn More About Android Development

For more details on animations and UI components, check out the Official Android Developer Documentation. You can also explore my Blog for more examples.

This Flip Coin Source Code Android project is easy to customize and can be integrated into decision-making apps, games, or entertainment tools.

Spin Wheel App Source Code – Java, Jetpack Compose and Flutter

Looking to build a fun and engaging spin-the-wheel app for giveaways, lucky draws, games, or random selections? This post covers a complete mini wheel spinner app built with:

  • ✅ Android Java using Custom Views
  • ✅ Android Jetpack Compose
  • ✅ Cross-platform Flutter (Android, iOS, and Web)

It’s lightweight, beautifully animated, and perfect for learners and developers. Whether you’re creating a decision-maker app or adding gamification to your product — this is your go-to solution.




Download Source Code

🚀 Key Features

  • 🎯 Smooth spinning animation with auto-colored segments
  • 🧠 Callback on spin result (Java/Compose)
  • 📦 3 complete projects: Android Java, Jetpack Compose & Flutter
  • 🔁 Reusable spinner logic for games, quizzes, and lucky draw
  • 📱 Supports Android & Web (via Flutter)

📁 What’s Inside the ZIP

This repository includes a ZIP file with three fully working source code projects:

  • Android Java: Classic implementation using custom `View`
  • Jetpack Compose: Modern declarative UI with Compose
  • Flutter: Cross-platform app using Dart

⚙️ How to Use

  1. Download the ZIP using the button above
  2. Unzip and open the desired project in Android Studio or VS Code
  3. Click Run and enjoy your animated spinner!

🔗 Get more awesome source codes at: Source Codes


Tags: android java spin wheel, flutter lucky draw app, wheel spinner compose, source code android spinner, alsaeeddev, gamification app, random picker app

File Manager Pro – Android App Source Code

Looking for a production-ready Android file manager app source code written in Java? Look no further! File Manager Pro is a clean, fully functional, open-source file explorer built using modern development practices. It’s perfect for developers who want to learn, customize, or extend a real-world Android file manager project.


🚀 Key Features

  • ✅ Browse files and folders with a responsive UI
  • ✅ Toggle show/hide hidden files support
  • ✅ Multi-select with bottom menu actions: Copy, Move, Delete
  • ✅ List View (Recycler View)
  • ✅ File type icons and image previews
  • ✅ Works on Android 7+

🎯 Built Using

  • ✔️ Java (Android Studio)
  • ✔️ MVVM Architecture
  • ✔️ RecyclerView, ViewModel, LiveData
  • ✔️ XML-based custom layouts


Download Source Code

🌐 About the Developer

Al Saeed – Android App developer and blogger at Al Saeed. Passionate about open-source and clean architecture development.

🔗 Explore more free Android app source codes: View Source Codes

🔗 Learn Jetpack Compose UI development at: Official Android Documentation


🔖 Tags:

Android File Manager App, File Explorer Android Java, Open Source Android App, Java Android Source Code, Android File Browser GitHub, File Manager MVVM, Android Studio Project Free Download


Stunning Custom Analog Clock in Android – Java & Kotlin

Want to design a custom analog clock in Android? In this post, you’ll learn how to create one using both Java and Kotlin. Whether you’re building a utility app, dashboard UI, or simply improving your UI skills, this is the perfect project to implement a working analog clock in Android.

🎯 What’s Inside This Project?

You’ll get the complete source code for both languages, canvas-based drawing, second-by-second ticking hands, and a beautiful rounded design. Let’s dive into development!

✅ What You’ll Learn

  • How to draw a custom analog clock using Canvas
  • Java and Kotlin implementations side by side
  • Center dot, circular frame, and dynamic ticking hands
  • Modern, responsive UI layout for all screen sizes

This article provides two versions: AnalogClockView.java and AnalogClockViewKotlin.kt. You can use either based on your language preference.

📄 XML Layout

To use the custom clock in your activity, add this to your activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <www.alsaeeddev.clock.AnalogClockView
        android:id="@+id/analogClock"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_centerInParent="true" />

</RelativeLayout>

📦 Java Code: AnalogClockView.java

 
package www.alsaeeddev.clock;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;

import java.util.Calendar;

public class AnalogClockView extends View {

    private Paint paint;
    private int width, height, radius;
    private int padding = 0;
    private int numeralSpacing = 30;
    private int handTruncation, hourHandTruncation;
    private final int[] numbers = {1,2,3,4,5,6,7,8,9,10,11,12};
    private Rect textBounds = new Rect();

    public AnalogClockView(Context context) {
        super(context);
        init();
    }

    public AnalogClockView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init() {
        paint = new Paint();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        width = getWidth();
        height = getHeight();
        int min = Math.min(width, height);
        radius = min / 2 - 40;

        canvas.drawColor(Color.parseColor("#928dab")); // Background

        // Draw clock background
        paint.reset();
        paint.setColor(Color.parseColor("#b3f6f8"));
        paint.setStyle(Paint.Style.FILL);
        canvas.drawCircle((float) width /2, (float) height /2, radius + 30, paint);

        paint.setStyle(Paint.Style.STROKE);
        paint.setColor(Color.parseColor("#9e7419"));
        paint.setStrokeWidth(8f);
        canvas.drawCircle((float) width /2, (float) height /2, radius + 30, paint);

        // Draw center dot
        paint.setStyle(Paint.Style.FILL);
        paint.setColor(Color.BLACK);
        canvas.drawCircle((float) width /2, (float) height /2, 12, paint);

        // Draw clock numbers
        paint.setTextSize(40);
        paint.setColor(Color.BLACK);
        for (int number : numbers) {
            String tmp = String.valueOf(number);
            paint.getTextBounds(tmp, 0, tmp.length(), textBounds);
            double angle = Math.PI / 6 * (number - 3);
            int x = (int)((double) width /2 + Math.cos(angle) * (radius - numeralSpacing) - (double) textBounds.width() /2);
            int y = (int)((double) height /2 + Math.sin(angle) * (radius - numeralSpacing) + (double) textBounds.height() /2);
            canvas.drawText(tmp, x, y, paint);
        }

        // Get time
        Calendar calendar = Calendar.getInstance();
        int hour = calendar.get(Calendar.HOUR);
        int minute = calendar.get(Calendar.MINUTE);
        int second = calendar.get(Calendar.SECOND);

        drawHand(canvas, (hour + minute / 60.0) * 5f, true, false); // Hour hand
        drawHand(canvas, minute, false, false);                     // Minute hand
        drawHand(canvas, second, false, true);                      // Second hand

        postInvalidateDelayed(1000);
        invalidate();
    }



// draw the clock hands
    private void drawHand(Canvas canvas, double loc, boolean isHour, boolean isSecond) {
        double angle = Math.PI * loc / 30 - Math.PI / 2;
        int handRadius = isHour ? radius - 120 : radius - 60;
        if (isSecond) handRadius = radius - 40;

        paint.setColor(isSecond ? Color.parseColor("#e74c3c") : Color.BLACK);
        paint.setStrokeWidth(isSecond ? 4f : isHour ? 8f : 6f);
        canvas.drawLine((float) width /2, (float) height /2,
                (float)((double) width /2 + Math.cos(angle) * handRadius),
                (float)((double) height /2 + Math.sin(angle) * handRadius),
                paint);
    }
}

🧑‍💻 Kotlin Code: AnalogClockViewKotlin.kt

 
package www.alsaeeddev.clock

import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Rect
import android.util.AttributeSet
import android.view.View
import java.util.Calendar
import kotlin.math.cos
import kotlin.math.min
import kotlin.math.sin

class AnalogClockViewKotlin @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null
) : View(context, attrs) {

    private val paint = Paint()
    private val numbers = (1..12).toList()
    private val textBounds = Rect()
    private var widthCenter = 0
    private var heightCenter = 0
    private var radius = 0

    init {
        paint.isAntiAlias = true
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)

        widthCenter = width / 2
        heightCenter = height / 2
        radius = min(widthCenter, heightCenter) - 60

        drawBackground(canvas)
        drawCenterDot(canvas)
        drawNumbers(canvas)
        drawHands(canvas)

        postInvalidateDelayed(1000)
        invalidate()
    }

    private fun drawBackground(canvas: Canvas) {
        paint.reset()
        paint.isAntiAlias = true

        // Clock circle fill
        paint.color = Color.parseColor("#b3f6f8")
        paint.style = Paint.Style.FILL
        canvas.drawCircle(widthCenter.toFloat(), heightCenter.toFloat(), radius + 30f, paint)

        // Clock border
        paint.style = Paint.Style.STROKE
        paint.strokeWidth = 8f
        paint.color = Color.parseColor("#9e7419")
        canvas.drawCircle(widthCenter.toFloat(), heightCenter.toFloat(), radius + 30f, paint)
    }

    private fun drawCenterDot(canvas: Canvas) {
        paint.style = Paint.Style.FILL
        paint.color = Color.BLACK
        canvas.drawCircle(widthCenter.toFloat(), heightCenter.toFloat(), 12f, paint)
    }

    private fun drawNumbers(canvas: Canvas) {
        paint.textSize = 40f
        paint.color = Color.BLACK
        for (number in numbers) {
            val numStr = number.toString()
            paint.getTextBounds(numStr, 0, numStr.length, textBounds)
            val angle = Math.PI / 6 * (number - 3)
            val x = (widthCenter + cos(angle) * (radius - 30) - textBounds.width() / 2).toFloat()
            val y = (heightCenter + sin(angle) * (radius - 30) + textBounds.height() / 2).toFloat()
            canvas.drawText(numStr, x, y, paint)
        }
    }

    private fun drawHands(canvas: Canvas) {
        val calendar = Calendar.getInstance()
        val hour = calendar.get(Calendar.HOUR)
        val minute = calendar.get(Calendar.MINUTE)
        val second = calendar.get(Calendar.SECOND)

        drawHand(canvas, (hour + minute / 60f) * 5, isHour = true, isSecond = false)
        drawHand(canvas, minute.toFloat(), isHour = false, isSecond = false)
        drawHand(canvas, second.toFloat(), isHour = false, isSecond = true)
    }

    private fun drawHand(canvas: Canvas, loc: Float, isHour: Boolean, isSecond: Boolean) {
        val angle = Math.PI * loc / 30 - Math.PI / 2
        val handLength = when {
            isHour -> radius - 120
            isSecond -> radius - 30
            else -> radius - 60
        }

        paint.color = if (isSecond) Color.parseColor("#e74c3c") else Color.BLACK
        paint.strokeWidth = when {
            isHour -> 8f
            isSecond -> 4f
            else -> 6f
        }
        paint.style = Paint.Style.STROKE

        canvas.drawLine(
            widthCenter.toFloat(), heightCenter.toFloat(),
            (widthCenter + cos(angle) * handLength).toFloat(),
            (heightCenter + sin(angle) * handLength).toFloat(),
            paint
        )
    }
}



✅ Conclusion

Creating a custom analog clock in Android is a great way to explore Canvas drawing, custom views, and elegant UI development. Whether you choose Java or Kotlin, the outcome is a clean and dynamic clock component ready for your next project.

We used the Paint class extensively to draw shapes and text on the canvas. To learn more about its capabilities, see the official documentation here:
🔗 Android Developers

Don’t forget to share and follow Al Saeed for more hands-on Android tutorials.

Free React Portfolio Website Template Using AI

Are you a developer, designer, freelancer, or student looking to create a standout personal portfolio website without starting from scratch? This Free React Portfolio Website Template, generated using the latest AI technologies, helps you launch your professional online presence effortlessly.

Whether you’re showcasing your skills, side projects, or freelance services, this template gives you a modern, responsive, and SEO-friendly starting point — built with React + Vite for lightning-fast performance.


🎯 Why Use This Free React Portfolio Website Template?

Here are the top reasons why this React portfolio template stands out:

Modern & Clean UI Design – Built with a sleek layout to attract and engage visitors
Powered by React & Vite – Ensures optimal build speed and performance
Responsive Design – Looks stunning on mobile, tablet, and desktop
Highly Customizable – Edit styles, sections, and content with ease
SEO Optimized – Fast load times, semantic HTML, and clean codebase
Open-Source & Free – Use it freely for your personal or professional portfolio

This Free React Portfolio Website Template is your shortcut to building a beautiful and professional portfolio in no time.


🔧 Technologies Used

  • React.js – Component-based frontend library

  • Vite – Next-generation frontend tooling

  • CSS Modules / Tailwind CSS (optional depending on customization)


Free Source Code

Click the button below to download the complete source code:

👉 Download the Source Code (ZIP)

Or explore the GitHub repository here:
🔗 View on GitHub

This Free React Portfolio Website Template is designed to save time while delivering a professional-grade user experience.


🖼️ Preview


🔗 Click here to Live Demo

💡 Use Cases for This Template

  • Developer or designer portfolio

  • Freelancer landing page

  • Student project showcase

  • Personal website or resume

  • Frontend project for job applications


⚙️ How to Get Started

  1. Download or clone the repo from GitHub

  2. Run npm install to install dependencies

  3. Run npm run dev to start local development

  4. Customize sections like Home, About, Projects, Skills, Contact

  5. Use npm run build to generate your production build

  6. Deploy to GitHub Pages, Netlify, or Vercel


📈 SEO Benefits of This Template

Using this Free React Portfolio Website Template ensures your site follows technical SEO best practices:

  • ⚡ Fast page load speed

  • 📱 Mobile-first responsive design

  • 🔗 Clean URL structure and metadata support

  • ✅ Google-friendly performance

This increases your chances of being discovered by recruiters, clients, and collaborators online.


🧠 Learn More

Want more tools and templates like this?
📚 Explore More on Our Blog

Media Player in Android Studio Using Kotlin | Media Player Source Code

If you’re looking to create a simple media player in Android Studio using Kotlin, this article will guide you through the core of your project: the MainActivity. This is where permissions are handled, songs are loaded from device storage, and user interactions are managed.

Overview of Media Player MainActivity

The MainActivity is responsible for:

  • Checking and requesting runtime permissions
  • Fetching music files from the device storage
  • Binding to the MusicService for background playback
  • Handling user interactions like play, pause, next, and previous

Kotlin Code Breakdown for Media Player

The class implements AppCompatActivity and ServiceConnection to establish communication with the background music service. It uses ActivityMainBinding for UI binding and coroutines to prevent blocking the main thread while querying songs from MediaStore.

Permission Handling for Media Player

For Android 13+ (Tiramisu), it requests READ_MEDIA_AUDIO. For earlier versions, READ_EXTERNAL_STORAGE is used.

Loading Songs

The app queries MediaStore.Audio.Media using a coroutine and populates the songsList if the files exist.

Binding Media Player to MusicService

On successful service connection, the list is passed to the service and the UI is updated with the currently playing song title.

Media Player Controls and Playback

The play/pause, next, and previous buttons update the state using MusicService. The UI is automatically refreshed using updateControls().

Other Media Player App Classes

To complete the media player, create the following Kotlin classes and paste their code accordingly:

  • MusicService.kt – Manages playback in the background
  • AudioModel.kt – Data class representing a song
  • MusicAdapter.kt – Binds song data to RecyclerView
  • MusicPlayerActivity.kt – Full screen music controls

1. MainActivity.kt


  class MainActivity : AppCompatActivity(), ServiceConnection {
    private var musicService: MusicService? = null
    private var isServiceBound = false
    private lateinit var binding: ActivityMainBinding
    private val songsList: MutableList = mutableListOf()
    private lateinit var adapter: MusicAdapter
    private var isPlaying = false

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        window.statusBarColor = Color.WHITE

        if (!checkPermission()) {
            requestPermission()

        } else {
            initializeApp()
        }

    }


    private fun initializeApp() {
        // Launch coroutine on Main thread
        CoroutineScope(Dispatchers.Main).launch {

            // Show progress bar
            binding.progressBar.visibility = View.VISIBLE

            val songs = withContext(Dispatchers.IO) {
                val songs = ArrayList()
                val projection = arrayOf(
                    MediaStore.Audio.Media.TITLE,
                    MediaStore.Audio.Media.DATA,
                    MediaStore.Audio.Media.DURATION
                )
                val selection = MediaStore.Audio.Media.IS_MUSIC + " !=0"

                contentResolver.query(
                    MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
                    projection,
                    selection,
                    null,
                    null
                )?.use { cursor ->
                    while (cursor.moveToNext()) {
                        val audioModel = AudioModel(
                            cursor.getString(1),
                            cursor.getString(0),
                            cursor.getString(2)
                        )
                        if (File(audioModel.path).exists()) {
                            songs.add(audioModel)
                        }
                    }
                }
                songs
            }

            // Hide progress bar
            binding.progressBar.visibility = View.GONE

            songsList.clear()
            songsList.addAll(songs)

            adapter = MusicAdapter(this@MainActivity, songsList) { position ->
                if (isPlaying) {
                    musicService?.musicPlay(songsList)
                    binding.songTitle.text = songsList[MyMediaPlayer.currentIndex].title
                    adapter.notifyDataSetChanged()
                } else {

                    if (songsList.isNotEmpty()) {
                        val intent = Intent(this@MainActivity, MusicPlayerActivity::class.java)
                        intent.putExtra("List", songsList as Serializable)
                        intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
                        startActivity(intent)
                    } else {
                        Toast.makeText(this@MainActivity, "No songs to play", Toast.LENGTH_SHORT)
                            .show()
                    }

                }
            }

            binding.recyclerView.layoutManager = LinearLayoutManager(this@MainActivity)
            binding.recyclerView.adapter = adapter
            binding.noSongFound.visibility = if (songsList.isEmpty()) View.VISIBLE else View.GONE

            setupControls()
        }
    }


    private fun setupControls() {
        val intent = Intent(this, MusicService::class.java)
        bindService(intent, this, Context.BIND_AUTO_CREATE)

        binding.pausePlay.setOnClickListener {
            musicService?.getMusicPlayer()?.let {
                if (it.isPlaying) {
                    it.pause()
                    binding.pausePlay.setImageResource(R.drawable.play_ic)
                } else {
                    it.start()
                    binding.pausePlay.setImageResource(R.drawable.pause_ic)
                }
            }
        }



        binding.next.setOnClickListener { musicNext() }
        binding.previous.setOnClickListener { musicPrevious() }

        binding.controls.setOnClickListener {
            val intent1 = Intent(this@MainActivity, MusicPlayerActivity::class.java)
            intent1.putExtra("IS", true)
            intent1.putExtra("List", songsList as Serializable)
            startActivity(intent1)
        }
    }


    private fun musicNext() {

        musicService?.next()
        binding.songTitle.text = songsList[MyMediaPlayer.currentIndex].title
        adapter.notifyDataSetChanged()

    }


    private fun musicPrevious() {
        musicService?.previous()
        binding.songTitle.text = songsList[MyMediaPlayer.currentIndex].title
        adapter.notifyDataSetChanged()

    }


    private fun checkPermission(): Boolean {

        val result = ContextCompat.checkSelfPermission(
            this@MainActivity,
            Manifest.permission.READ_EXTERNAL_STORAGE
        )

        if (result == PackageManager.PERMISSION_GRANTED) {
            return true

        } else {
            return false
        }
    }


    private fun requestPermission() {
        val permission = Manifest.permission.READ_EXTERNAL_STORAGE

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            // For Android 13+ (Tiramisu), use READ_MEDIA_AUDIO instead
            ActivityCompat.requestPermissions(
                this,
                arrayOf(Manifest.permission.READ_MEDIA_AUDIO),
                92
            )
        } else {

            ActivityCompat.requestPermissions(
                this,
                arrayOf(permission),
                92
            )

        }
    }


    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)

        if (requestCode == 92 && grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            Toast.makeText(this, "Permission granted", Toast.LENGTH_SHORT).show()
            initializeApp() // Or load your music files
        } else {
            Toast.makeText(this, "Permission denied", Toast.LENGTH_SHORT).show()
            requestPermission()
        }
    }


    override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
        val binder = service as MusicService.MyBinder
        musicService = binder.currentService()
        isServiceBound = true
        musicService!!.setSongLIst(songsList)

        // Update the controls based on the current state of the media player
        updateControls()
    }

    override fun onServiceDisconnected(name: ComponentName?) {
        musicService = null
        isServiceBound = false
    }


    private fun updateControls() {
        musicService?.getMusicPlayer()?.let { mediaPlayer ->
            if (mediaPlayer.isPlaying) {
                binding.controls.visibility = View.VISIBLE
                binding.songTitle.text = songsList[MyMediaPlayer.currentIndex].title
                binding.pausePlay.setImageResource(R.drawable.pause_ic)
                isPlaying = true
            } else {
                isPlaying = false
                binding.controls.visibility = View.GONE
            }

            mediaPlayer.setOnCompletionListener {
                musicNext() // Move to the next song when the current song is completed.
            }
        }

    }

    @SuppressLint("NotifyDataSetChanged")
    override fun onResume() {
        super.onResume()
        if (::adapter.isInitialized) {
            adapter.notifyDataSetChanged()
            updateControls()
        }

    }

    override fun onDestroy() {
        super.onDestroy()
        if (isServiceBound) {
            unbindService(this)
            isServiceBound = false
        }
    }
}

2. MusicService.kt


class MusicService : Service() {

    private var myBinder = MyBinder()
    private var mediaPlayer: MediaPlayer? = MyMediaPlayer.getInstance()
    private var currentSongPath: String? = null
    private var isAlreadyPlaying = false
    private var songsList: List? = null


    fun setSongLIst(list: List) {
        songsList = list

    }

    public fun getMusicPlayer(): MediaPlayer? {
        return mediaPlayer
    }


    override fun onBind(intent: Intent?): IBinder? {
        return myBinder

    }


    fun musicPlay(songList: List) {
        currentSongPath = songList[MyMediaPlayer.currentIndex].path
        mediaPlayer!!.reset()
        mediaPlayer!!.setDataSource(currentSongPath)
        mediaPlayer!!.prepare()
        mediaPlayer!!.start()

    }


    fun pause() {
        mediaPlayer?.pause()
        Log.d("MyService", "Pausing music")
    }

    fun stop() {
        mediaPlayer?.stop()
        Log.d("MyService", "Stopping music")
    }


    fun next() {
        if (MyMediaPlayer.currentIndex == songsList!!.size - 1) {
            return
        }

        MyMediaPlayer.currentIndex += 1
        mediaPlayer!!.reset()
        if (songsList != null) {
            musicPlay(songsList!!)
        }
        isAlreadyPlaying = false


    }


    fun previous() {
        if (MyMediaPlayer.currentIndex == 0) {
            return
        }
        MyMediaPlayer.currentIndex -= 1
        mediaPlayer!!.reset()
        if (songsList != null) {
            musicPlay(songsList!!)
        }
        isAlreadyPlaying = false

    }


    fun getPlayingStatus(): Boolean {
        return isAlreadyPlaying
    }


    fun isPlaying(): Boolean {
        return mediaPlayer?.isPlaying ?: false
    }

    fun getCurrentPosition(): Int {
        return mediaPlayer?.currentPosition ?: 0
    }

    fun seekTo(position: Int) {
        mediaPlayer?.seekTo(position)
    }


    override fun onDestroy() {
        super.onDestroy()
    }


    inner class MyBinder : Binder() {
        fun currentService(): MusicService {
            return this@MusicService
        }
    }

}

3. MusicAdapter.kt


class MusicAdapter(
    private val context: Context,
    private val songList: List,
    private val onClick: (Int) -> Unit
) : RecyclerView.Adapter() {


    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemHolder {
        val view = SongItemBinding.inflate(LayoutInflater.from(context), parent, false)
        return ItemHolder(view)
    }

    override fun getItemCount(): Int {
        return songList.size
    }


    public fun getSongLIst(): List {
        return songList
    }

    override fun onBindViewHolder(holder: ItemHolder, position: Int) {
        val audioSong = songList[position]
        holder.bind(audioSong, position)
    }


    inner class ItemHolder(private val binding: SongItemBinding) :
        RecyclerView.ViewHolder(binding.root) {


        fun bind(audioSong: AudioModel, position: Int) {

            if (MyMediaPlayer.currentIndex == position) {
                binding.tvSongTitle.setTextColor(Color.RED)
            } else {
                binding.tvSongTitle.setTextColor(Color.BLACK)
            }
            binding.tvSongTitle.text = audioSong.title
            //   binding.ivMusic.setImageResource(R.drawable.music_ic)

            binding.root.setOnClickListener {
                MyMediaPlayer.getInstance().reset()
                MyMediaPlayer.currentIndex = adapterPosition
                onClick(adapterPosition)


            }

        }

    }
}


Download Source Code

Conclusion

This Android media player in Kotlin is lightweight, efficient, and follows best practices for modern development using ViewBinding, coroutines, and service components. Add more features like notifications, shuffle, or repeat to further enhance your app.

You can also Check Out the official Android Media Player Documentation.
Want to learn more? Visit our tutorials at Blog .

Swipe and Tap Card Animation in Jetpack Compose

Creating an interactive card-based interface in Jetpack Compose is easier than ever. Today, we will explore how to implement a smooth Card Animation in Jetpack using swipe or tap gestures on cards with the CardSwipeOrTap composable. This approach is perfect for modern UI designs where users interact with content via gestures.

Main Activity Setup

We start by setting up the MainActivity that uses a Scaffold layout inside the RecyclerViewTheme. This ensures that our UI fills the entire screen while providing padding to our card interactions.

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            RecyclerViewTheme {
                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
                    CardSwipeOrTap(modifier = Modifier.padding(innerPadding))
                }
            }
        }
    }
}

Building the Card Swipe or Tap Composable

The CardSwipeOrTap composable manages the card stack. It uses a Box to overlay cards on top of each other and applies a slight rotation and vertical offset for a realistic stacked effect.

Each card is individually rendered using the CardItem composable. We use a simple CoroutineScope to delay actions and allow for smooth animations between swipe or tap events.

@Composable
fun CardSwipeOrTap(modifier: Modifier = Modifier) {
    val scope = rememberCoroutineScope()
    var cards by remember { mutableStateOf(listOf(0, 1, 2)) }
    var isAnimating by remember { mutableStateOf(false) }

    val cardColors = listOf(
        Color(0xFFFF9800), // Orange
        Color(0xFFF44336), // Red
        Color(0xFF00E676)  // Light Green
    )

    Box(
        modifier = modifier.fillMaxSize().padding(32.dp),
        contentAlignment = Alignment.Center
    ) {
        cards.reversed().forEachIndexed { index, cardIndex ->
            CardItem(
                index = cardIndex,
                color = cardColors[cardIndex % cardColors.size],
                rotation = if (index == 0) 0f else if (index == 1) -10f else 10f,
                offsetY = (index * 20).dp,
                isTop = index == cards.size - 1,
                isAnimating = isAnimating,
                onSwiped = {
                    if (!isAnimating) {
                        isAnimating = true
                        scope.launch {
                            kotlinx.coroutines.delay(300)
                            val updated = cards.toMutableList()
                            val removed = updated.removeAt(0)
                            updated.add(removed)
                            cards = updated
                            isAnimating = false
                        }
                    }
                }
            )
        }
    }
}

Animating Card Movement with CardItem

The CardItem composable handles the animation when a user swipes or taps a card. It uses animateDpAsState and animateFloatAsState to animate the card’s offset and opacity, giving the appearance of a disappearing card when an interaction occurs.

@Composable
fun CardItem(
    index: Int,
    color: Color,
    rotation: Float,
    offsetY: Dp,
    isTop: Boolean,
    isAnimating: Boolean,
    onSwiped: () -> Unit
) {
    val animatedOffsetX by animateDpAsState(
        targetValue = if (isAnimating && isTop) 500.dp else 0.dp,
        animationSpec = if (isAnimating && isTop) tween(durationMillis = 500, easing = FastOutLinearInEasing) else snap(),
        label = "OffsetXAnimation"
    )

    val animatedAlpha by animateFloatAsState(
        targetValue = if (isAnimating && isTop) 0f else 1f,
        animationSpec = if (isAnimating && isTop) tween(durationMillis = 3000) else snap(),
        label = "AlphaAnimation"
    )

    Card(
        modifier = Modifier
            .fillMaxWidth(0.7f)
            .aspectRatio(1f)
            .offset(x = animatedOffsetX, y = offsetY)
            .graphicsLayer {
                rotationZ = rotation
                alpha = animatedAlpha
            }
            .pointerInput(isTop) {
                if (isTop && !isAnimating) {
                    detectDragGestures(
                        onDragEnd = { onSwiped() },
                        onDrag = { change, _ -> change.consume() }
                    )
                }
            }
            .pointerInput(isTop) {
                if (isTop && !isAnimating) {
                    detectTapGestures(
                        onTap = { onSwiped() }
                    )
                }
            },
        elevation = CardDefaults.cardElevation(defaultElevation = 8.dp),
        shape = RoundedCornerShape(20.dp),
        colors = CardDefaults.cardColors(
            containerColor = color,
            contentColor = Color.White
        )
    ) {
        Box(
            modifier = Modifier.fillMaxSize(),
            contentAlignment = Alignment.Center
        ) {
            Text(
                text = "Card #$index",
                style = MaterialTheme.typography.titleLarge,
                color = Color.White
            )
        }
    }
}


Final Thoughts for Card Animation in Jetpack

With just a few lines of Kotlin code, you can create a stunning card swipe and tap animation in Jetpack Compose. This approach is not only visually appealing but also highly interactive, offering a smooth user experience without the complexity of a traditional RecyclerView setup. Try it out in your next Android app project!

You can also read the official Jetpack Compose animation documentation.

Extract The Text From Image Using Python

Discover how to easily Extract the text From image using python to pdf and word file. This guide shows you how to build a Python Tkinter application that extracts text from images and saves it into Word or PDF files effortlessly.

Introduction

Manually extracting text from images can be a tedious task. Thankfully, Python offers powerful libraries like pytesseract and Tkinter to automate this process. In this tutorial, you’ll learn how to Extract the text From an image to pdf and word file by creating an easy-to-use desktop application. This project is perfect for beginners exploring GUI development and OCR (Optical Character Recognition).

 

Technologies Used

  • Python – Programming language
  • Tkinter – GUI toolkit for Python
  • pytesseract – Optical Character Recognition (OCR) tool
  • Pillow – Image processing library
  • fpdf – PDF creation library
  • python-docx – Word document generation library

 

Installation Guide for Technologies Used

Before building the project to Extract the Text from an Image Using Python, make sure to install the following required libraries and tools.

1. Install Python

Make sure you have Python installed. You can download it from the official site: Download Python.

2. Install Required Python Libraries
# Install tkinter (already included with Python on most systems)
# For Linux, you can install manually:
sudo apt-get install python3-tk

# Install pytesseract
pip install pytesseract

# Install Pillow for image processing
pip install Pillow

# Install fpdf to create PDF files
pip install fpdf

# Install python-docx to create Word documents
pip install python-docx
3. Install Tesseract-OCR Engine

Since pytesseract is only a wrapper, you also need to install the Tesseract-OCR engine:

  • Windows: Download from Tesseract GitHub and install.
  • Linux (Ubuntu/Debian): sudo apt install tesseract-ocr
  • macOS (using Homebrew): brew install tesseract
4. Set Tesseract Path in Python Code
pytesseract.pytesseract.tesseract_cmd = r"C:\Program Files\Tesseract-OCR\tesseract.exe"

Adjust the path if you installed Tesseract in a different location.

5. (Optional) Create a requirements.txt

To install all packages at once, you can create a requirements.txt file with the following content:

pytesseract
Pillow
fpdf
python-docx

Then run:

pip install -r requirements.txt

Now your Python environment is fully ready to build the project and extract text from images easily!

Project Overview: Extract the text From an image to pdf and word file

This simple Python application allows users to:

  1. Select an image (formats: PNG, JPG, BMP, TIFF)
  2. Extract text using OCR
  3. View extracted text in a scrollable text box
  4. Save extracted text as a Word (.docx) file
  5. Save extracted text as a PDF (.pdf) file

How It Works

1. Selecting an Image

Click the “Select Image” button to open a file dialog where you can choose an image file. The application supports PNG, JPG, BMP, and TIFF formats.

2. Extracting Text

After selecting an image, click “Extract Text”. The app uses Tesseract OCR to scan the image and retrieve all readable text, which is displayed inside a scrollable text widget.

3. Saving as Word or PDF

Once the text is extracted, you can choose to save it as a Word document or a PDF file. The application ensures Unicode font support for multi-language text.

Key Code Snippets


Selecting an Image File
filepath = filedialog.askopenfilename(
    filetypes=[("Image Files", "*.png *.jpg *.jpeg *.bmp *.tiff")]
)
Extracting Text Using pytesseract
img = Image.open(image_path)
text = pytesseract.image_to_string(img)
Saving Text as a Word Document
doc = Document()
doc.add_paragraph(text)
doc.save(filepath)
Saving Text as a PDF Document
pdf = FPDF()
pdf.add_page()
pdf.add_font('DejaVu', '', font_path, uni=True)
pdf.set_font('DejaVu', '', 12)
for line in text.split('\n'):
    pdf.multi_cell(0, 10, line)
pdf.output(filepath)
Benefits of Automating Text Extraction
  • Saves time compared to manual typing
  • Improves data entry accuracy
  • Streamlines document management
  • Boosts productivity for businesses and individuals

Complete Code extract-text.py

import tkinter as tk
from tkinter import filedialog, messagebox, ttk
from tkinter.scrolledtext import ScrolledText
from PIL import Image
import pytesseract
from docx import Document
from fpdf import FPDF
import os
import threading

# Set path to tesseract.exe
pytesseract.pytesseract.tesseract_cmd = r"C:\Program Files\Tesseract-OCR\tesseract.exe"  # Adjust if needed

def select_image():
    filepath = filedialog.askopenfilename(
        title="Select an image file",
        filetypes=[("Image Files", "*.png *.jpg *.jpeg *.bmp *.tiff")]
    )
    if filepath:
        entry_file.delete(0, tk.END)
        entry_file.insert(0, filepath)

def extract_text_threaded():
    threading.Thread(target=extract_text).start()

def extract_text():
    image_path = entry_file.get()
    if not os.path.exists(image_path):
        messagebox.showerror("Error", "Image file does not exist!")
        return

    try:
        progress_bar.place(relx=0.5, rely=0.5, anchor="center")  # Centered inside text box
        progress_bar.start()

        img = Image.open(image_path)
        text = pytesseract.image_to_string(img)

        if not text.strip():
            messagebox.showinfo("No Text", "No text detected in the image.")
            return

        text_box.config(state=tk.NORMAL)
        text_box.delete(1.0, tk.END)
        text_box.insert(tk.END, text)
        text_box.config(state=tk.NORMAL)

    except Exception as e:
        messagebox.showerror("Error", str(e))
    finally:
        progress_bar.stop()
        progress_bar.place_forget()  # Hide progress bar

def save_as_word():
    text = text_box.get(1.0, tk.END).strip()
    if not text:
        messagebox.showwarning("Warning", "No text to save!")
        return

    filepath = filedialog.asksaveasfilename(
        defaultextension=".docx",
        filetypes=[("Word Document", "*.docx")]
    )
    if filepath:
        try:
            doc = Document()
            doc.add_paragraph(text)
            doc.save(filepath)
            messagebox.showinfo("Saved", f"Text saved as Word file:\n{filepath}")
        except Exception as e:
            messagebox.showerror("Error", str(e))

def save_as_pdf():
    text = text_box.get(1.0, tk.END).strip()
    if not text:
        messagebox.showwarning("Warning", "No text to save!")
        return

    filepath = filedialog.asksaveasfilename(
        defaultextension=".pdf",
        filetypes=[("PDF file", "*.pdf")]
    )
    if filepath:
        try:
            pdf = FPDF()
            pdf.add_page()
            
            # Add a Unicode font (DejaVuSans.ttf must be in the same folder)
            font_path = os.path.join(os.path.dirname(__file__), r"D:\Python\extract-text\DejavuSans.ttf")
            pdf.add_font('DejaVu', '', font_path, uni=True)
            pdf.set_font('DejaVu', '', 12)
            
            for line in text.split('\n'):
                pdf.multi_cell(0, 10, line)

            pdf.output(filepath)
            messagebox.showinfo("Saved", f"Text saved as PDF file:\n{filepath}")
        except Exception as e:
            messagebox.showerror("Error", str(e))


# Helper function to get correct path for icon
def resource_path(relative_path):
    try:
        # PyInstaller creates a temp folder and stores path in _MEIPASS
        base_path = sys._MEIPASS
    except Exception:
        base_path = os.path.abspath(".")
    return os.path.join(base_path, relative_path)

# --- GUI ---
root = tk.Tk()
root.title("Image to Text Extractor")
root.minsize(400, 600)  # Minimum window size

frame = tk.Frame(root, padx=10, pady=10)
frame.pack(fill=tk.BOTH, expand=True)

# Widgets
btn_select = tk.Button(frame, text="Select Image", command=select_image)
entry_file = tk.Entry(frame, width=50)
btn_extract = tk.Button(frame, text="Extract Text", command=extract_text_threaded)

text_frame = tk.Frame(frame)
text_box = ScrolledText(text_frame, width=80, height=20, wrap=tk.WORD)

progress_bar = ttk.Progressbar(text_frame, orient=tk.HORIZONTAL, mode='indeterminate', length=200)
progress_bar.place_forget()

btn_save_word = tk.Button(frame, text="Save as Word", command=save_as_word)
btn_save_pdf = tk.Button(frame, text="Save as PDF", command=save_as_pdf)

# Layout function (dynamic)
def layout_widgets(event=None):
    width = root.winfo_width()

    # Clear old layout
    for widget in frame.winfo_children():
        widget.grid_forget()
        widget.pack_forget()

    if width < 500:  # Mobile Layout
        btn_select.pack(pady=5, fill=tk.X)
        entry_file.pack(pady=5, fill=tk.X)
        btn_extract.pack(pady=5, fill=tk.X)

        text_frame.pack(pady=10, fill=tk.BOTH, expand=True)
        text_box.pack(fill=tk.BOTH, expand=True)

        btn_save_word.pack(pady=5, fill=tk.X)
        btn_save_pdf.pack(pady=5, fill=tk.X)

    else:  # Desktop Layout
        btn_select.grid(row=0, column=0, padx=5, pady=5)
        entry_file.grid(row=0, column=1, padx=5, pady=5, sticky="ew")
        btn_extract.grid(row=0, column=2, padx=5, pady=5)

        frame.grid_columnconfigure(1, weight=1)

        text_frame.grid(row=1, column=0, columnspan=3, pady=10, sticky="nsew")
        text_box.pack(fill=tk.BOTH, expand=True)

        btn_save_word.grid(row=2, column=0, pady=5)
        btn_save_pdf.grid(row=2, column=2, pady=5)

    # Make sure frame expands correctly
    frame.pack(fill=tk.BOTH, expand=True)

# Bind resize event
root.bind("", layout_widgets)

# Initialize layout
layout_widgets()

root.mainloop()


Download Source Code

Conclusion

By completing this project, you now know how to Extract the text From an image to pdf and word file using a Python Tkinter GUI application. This hands-on project introduces you to OCR technology and file conversion, which are valuable skills for automating tasks and improving workflow efficiency. You can extend this app by adding features like multi-image processing or cloud uploads to make it even more powerful!