UI Groundwork for the Vortex Experience
A Step-by-Step Guide to Building a BLE LED Controller
Introduction
Welcome to Vortex App Guide, a step-by-step tutorial on how to build a Bluetooth Low Energy (BLE) LED Controller App from scratch. Whether you’re a complete beginner or an experienced developer, this guide will walk you through every step, explaining how each part of the app works. By the end of this guide, you will have a fully functional Android App that can":
Scan and Connect to BLE devices
Control LED brightness, colors, and animations
Save and apply custom colors
Handle multiple devices efficiently
This guide is designed to be as beginner-friendly as possible while also providing advanced insights. If you’re completely new to Android development, we strongly recommend reading the official documentation on the Android Developers website to familiarize yourself with Android Studio, XML layouts, and Java basics, before diving into this project.
Why this Guide?
We don’t just show you the code - we explain it in detail, line by line.
You’ll build everything from scratch, ensuring you fully understand how the app works.
The project is modular, allowing you to add your own features later.
A newer version of this project is available on our Github: [InsGitname], where you can see the full original app with the latest updates and commits.
How This Guide Is Structured
Setting Up Your Project - Install Android Studio, create a new project, and configure dependencies.
Building the User Interface - Start by designing the app layout files ( XML) before writing Java code.
Implementing Bluetooth Scanning - Learn how to scan and connect to BLE devices.
Controlling LED Colors and Brightness - Send commands to an LED strip using BLE.
Implementing Animations - Allows users to select LED animations.
Optimizing the User Experience - Navigation, UI improvements, and accessibility.
Testing And Debugging - Ensuring your app runs smoothly.
Final Steps - Suggestions for improvements and deploying your app.
Each section will first guide you through the UI design, followed by implementing the logic step by step. This ensures you understand why we’re creating certain variables and assigning them to layout elements.
Getting Started: Creating a new Android Studio Project
Before we dive into building the app, let’s set up a new project in Android Studio:
Step 1: Install Android Studio
If you haven’t already, download and install Android Studio from the official Android Developer site. Follow the installation steps according to your operating system.
Step 2: Create a New Project
Open Android Studio and select New Project.
Choose Empty Activity and click Next.
Name Your project.
Choose Java as the programming language.
Set the Minimum SDK to API23 (Android 6.0 Marshmallow) or higher.
Click Finish and wait for the project to load.
Step 3: Configure Dependencies
To enable BLE support and material components, open build.gradle (Module: app) and add the following dependencies:
implementation 'com.github.skydoves:colorpickerview:2.3.0'
implementation 'com.google.android.material:material:1.12.0'
implementation 'com.github.bumptech.glide:glide:4.16.0'
Sync the project to apply the changes.
Step 4: Update AndroidManifest.xml
Your app needs permissions to use Bluetooth. Open AndroidManifest.xml and add the following lines inside the <manifest> tag:
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
Additionally, declare that the app requires BLE support:
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true" />
Once the project is set up, we’ll start by designing the UI for the home screen.
Building the Main Screen Layout ( activity_main.xml )
Now that we have set up the project, it’s time to create the home screen layout. This screen will allow users to:
View the connection status of their BLE device.
Navigate to different parts of the app (Device Selection, Static Color, Animations).
Adjust LED brightness using SeekBar .
Since the UI is crucial in making an app user-friendly, we’ll design activity_main.xml before implementing the logic in MainActivity.java .
Step 1: Locate or Create the File
In Android Studio, open res/layout folder
Find activity_main.xml . If it doesn’t exist, right-click on layout > New > Layout Resource File and name it activity_main.xml .
Open activity_main.xml in Design or Code view.
Step 2: Designing the UI (XML Code)
We will now go through each part of the layout in detail to explain its purpose.
1. Root Layout ( Relative Layout )
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/backgroundColor">
This is the container that holds all the UI elements.
match_parent ensures that it fills the entire screen.
@color/backgroundColor sets the background color defined in colors.xml .
2. Header Title ( TextView )
<TextView
android:id="@+id/headerTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/app_name"
android:textSize="24sp"
android:textStyle="bold"
android:textColor="@color/AccentColor"
android:layout_marginTop="24dp"
android:layout_marginStart="16dp"/>
displays the app’s name.
textSize=’24sp’ sets the text size.
id=”@+id/headerTitle” sets the name of the resource.
layout_width=”wrap_content” / layout_height=”wrap_content” sets the size to match the size of the contents inside.
text=”@string/app_name” sets the displayed text to a string resource located in strings.xml .
textStyle=”bold” sets the style of the text.
layout_marginTop=”24dp” / layout_marginStart=”16dp” sets the minimal amount of pixels os the resource from the top/Start.
3. Device Status Text ( TextView )
<TextView
android:id="@+id/statusText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/device_not_connected"
android:textSize="16sp"
android:textColor="@color/primaryTextColor"
android:layout_below="@id/headerTitle"
android:layout_marginTop="8dp"
android:layout_marginStart="16dp"/>
Displays whether a device is connected or disconnected.
”@string/device_not_connected” in this case is a default text for the resource.
layout_below=”@id/headerTitle” sets the TextView to always be below headerTitle.
4. Navigation Buttons (Animations, Static Color)
<Button
android:id="@+id/staticColorBtn"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_below="@id/statusText"
android:marginTop="16dp"
android:marginHorizontal="16dp"
android:text="@string/static_color_button"
android:textColor="@color/primaryTextColor"
android:background="@drawable/rounded_button"/>
This button allows user to do a certain thing, like switching activities once clicked.
These buttons have a fixed height so all the buttons look the same.
background=”@drawable/rounded_button” sets the look of the button to a certain resource.
marginHorizontal=”16dp” adds 16dp of spacing on both sides of the element.
5. ImageButton Inside a FrameLayout (Select Device)
<FrameLayout
android:id="@+id/selectDeviceFrame"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="true"
android:focusable="true"
android:background="@drawable/button_background_with_outline"
android:layout_alignParentEnd="true"
android:layout_alignTop="@id/headerTitle"
android:layout_marginEnd="16dp">
<ImageButton
android:id="@+id/selectDeviceBtn"
android:layout_width="70dp"
android:layout_height="45dp"
android:layout_gravity="center"
android:contentDescription="@string/device_icon_desc"
android:src="@drawable/ic_device"
android:background="@null"/>
</FrameLayout>
Why is it inside a FrameLayout ?
1. Positioning & Overlay Flexibility
FrameLayout
allows the ImageButton to be easily positioned relative to other UI elements.It makes it easier to overlay multiple elements, should we need to add icons, badges, or animated effects later.
2. Alignment to Parent Edge
The FrameLayout is aligned to the top-right corner ( alignParentEnd=”true” ).
This ensures the ImageButton is consistently positioned.
3. Scalability & Customization
Wrapping the ImageButton in FrameLayout makes it easier to add extra elements like notification badges in future updates.
If we want to expand its touch area, we can modify the FrameLayout without affecting other views.
Components
clickable=”true” Enables click interaction.
focusable=”true” Allows the button to receive focus.
layout_alignParentEnd=”true” aligns the FrameLayout with the end to the parent component.
layout_alignTop=”@id/headerTitle” aligns the component with the top of headerTitle.
layout_gravity=”center” Centers the ImageButton inside the FrameLayout .
src=”@drawable/ic_device” Loads the ic_device inside the button.
6. Brightness Control ( SeekBar )
Another crucial component in activity_main.xml is the SeekBar, which allows users to adjust the LED brightness.
<SeekBar
android:id="@+id/brightnessSeekBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="@+id/bottomNavigation"
android:layout_marginBottom="16dp"
android:marginHorizontal="16dp"
android:progress="128"
android:valueFrom="48"
android:valueTo="60"
android:thumbTint="@color/AccentColor"
android:progressTint="@color/AccentColor"/>
progrss=”128” Sets the default brightness to 50%.
thumbTint=”@color/AccentColor” Sets the slider thumb color.
progressTint=”@color/primaryTextColor” Sets the progress color.
valueFrom=”48” / valueTo=”60” Sets the max/min values of the SeekBar .
7. Botom Navigation ( BottomNavigationView )
The BottomNavigationView provides an easy way to switch between app screens.
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottomNavigation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:background="@color/SecondaryColor"
app:menu="@menu/bottom_navigation_menu"
app:itemIconTint="@color/AccentColor"
app:itemTextColor="@color/primaryTextColor"/>
app:itemIconTint=”@color/AccentColor” Sets the icon colors.
app:itemTextColor="@color/navItemColor"
Sets the text color.app:menu="@menu/bottom_navigation_menu"
References the menu resource file that defines navigation items.
Summary of the Work
In this section, we designed the entire UI layout for the main screen of the Vortex App using RelativeLayout . Here’s what we created:
Header: A title TextView at the top to display the app name.
Connection Status: A dynamic status TextView just below the header to reflect whether a BLE device is connected.
Device Selector: a FrameLayout with an ImageButton styled and aligned to the right, letting users choose a device.
Buttons: two custom buttons letting us access LED animations and static color selection.
Brightness Control: A SeekBar allowing users to adjust LED intensity.
Bottom Navigation Bar: A modern BottomNavigationView for easy screen navigation.
Each UI element was explained in detail with clear styling and layout logic. You now have a functional, consistent, and responsive design for the main screen.
Full activity_main code
▶ Reveal the full code
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/backgroundColor">
<!-- Header Title -->
<TextView
android:id="@+id/headerTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/app_name"
android:textSize="24sp"
android:textStyle="bold"
android:textColor="@color/AccentColor"
android:layout_marginTop="24dp"
android:layout_marginStart="16dp"/>
<!-- Select Device Button (Top-Right Corner) -->
<FrameLayout
android:id="@+id/selectDeviceFrame"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="true"
android:focusable="true"
android:background="@drawable/button_background_with_outline"
android:layout_alignParentEnd="true"
android:layout_alignTop="@id/headerTitle"
android:layout_marginEnd="16dp">
<ImageButton
android:id="@+id/selectDeviceBtn"
android:layout_width="70dp"
android:layout_height="45dp"
android:layout_gravity="center"
android:contentDescription="@string/device_icon_desc"
android:src="@drawable/ic_device"
android:background="@null"/>
</FrameLayout>
<!-- Connection Status -->
<TextView
android:id="@+id/statusText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/device_not_connected"
android:textSize="16sp"
android:textColor="@color/primaryTextColor"
android:layout_below="@id/headerTitle"
android:layout_marginTop="8dp"
android:layout_marginStart="16dp"/>
<!-- Animations Button -->
<Button
android:id="@+id/animationsBtn"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_below="@id/statusText"
android:layout_marginTop="16dp"
android:layout_marginHorizontal="16dp"
android:text="@string/animationsButton"
android:textColor="@color/primaryTextColor"
android:background="@drawable/rounded_button"/>
<!-- Static Color Button -->
<Button
android:id="@+id/staticColorBtn"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_below="@id/animationsBtn"
android:layout_marginTop="12dp"
android:layout_marginHorizontal="16dp"
android:text="@string/static_color_button"
android:textColor="@color/primaryTextColor"
android:background="@drawable/rounded_button"/>
<!-- Brightness SeekBar -->
<SeekBar
android:id="@+id/brightnessSeekBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="@+id/bottomNavigation"
android:layout_marginBottom="16dp"
android:layout_marginHorizontal="16dp"
android:progress="128"
android:valueFrom="48"
android:valueTo="60"
android:thumbTint="@color/AccentColor"
android:progressTint="@color/AccentColor"/>
<!-- Bottom Navigation -->
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottomNavigation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:background="@color/SecondaryColor"
app:menu="@menu/bottom_navigation_menu"
app:itemIconTint="@color/AccentColor"
app:itemTextColor="@color/primaryTextColor"/>
</RelativeLayout>