Creating a Material UI inspired Slider

Hi everyone,

I wanted to share a reusable UI slider I created, roughly based on the Material UI guidelines. This slider is built using primitive elements like circles and rectangles, and it includes some smooth animations for a polished look. I thought it might be useful for others, so here’s the code and a detailed explanation.

Features

• A track with rounded caps for a smoother look.

• A thumb that can be dragged to select a value.

• A colored, filled track that visually represents the selected value.

• A semi-transparent overlay that appears when the thumb is pressed and dragged, providing visual feedback.

local function createSlider(options)
    -- Default options
    options = options or {}
    local width = options.width or 200
    local height = options.height or 4
    local thumbRadius = options.thumbRadius or 10
    local capRadius = height / 2
    local minValue = options.minValue or 0
    local maxValue = options.maxValue or 100
    local startValue = options.startValue or 0
    local onChange = options.onChange or function(value) end

    -- Create slider group
    local sliderGroup = display.newGroup()

    -- Create the track
    local track = display.newRect(sliderGroup, 0, 0, width, height)
    track:setFillColor(0.8, 0.8, 0.8)
    track.anchorX = 0
    track.x = -width / 2

    -- Create the filled part of the track
    local filledTrack = display.newRect(sliderGroup, 0, 0, (startValue - minValue) / (maxValue - minValue) * width, height)
    filledTrack:setFillColor(0.3, 0.6, 0.9)
    filledTrack.anchorX = 0
    filledTrack.x = -width / 2

    -- Create the caps
    local leftCap = display.newCircle(sliderGroup, -width / 2, 0, capRadius)
    leftCap:setFillColor(0.3, 0.6, 0.9) -- same color as the filled track
    local rightCap = display.newCircle(sliderGroup, width / 2, 0, capRadius)
    rightCap:setFillColor(0.8, 0.8, 0.8)

    -- Create the thumb
    local thumb = display.newCircle(sliderGroup, 0, 0, thumbRadius * 2)
    thumb.xScale = 0.5
    thumb.yScale = 0.5
    thumb:setFillColor(0.3, 0.6, 0.9)
    thumb.x = (startValue - minValue) / (maxValue - minValue) * width - width / 2

    -- Functions to show and hide the touch overlay
    local showOverlay = function(overlay)
        transition.cancel("moveOverlay")
        transition.to(overlay, {xScale = 0.5, yScale = 0.5, time = 150, tag = "moveOverlay"})
    end

    local hideOverlay = function(overlay)
        transition.cancel("moveOverlay")
        transition.to(overlay, {xScale = 0.1, yScale = 0.1, time = 150, onComplete = function() overlay.isVisible = false end, tag = "moveOverlay"})
    end

    -- Create the touch overlay
    local touchOverlay = display.newCircle(sliderGroup, thumb.x, thumb.y, thumbRadius * 4)
    touchOverlay.xScale = 0.1
    touchOverlay.yScale = 0.1
    touchOverlay:setFillColor(0.3, 0.6, 0.9, 0.3)
    touchOverlay.isVisible = false

    -- Touch event for the thumb
    local function onThumbTouch(event)
        if event.phase == "began" then
            display.getCurrentStage():setFocus(thumb)
            thumb.isFocus = true
            touchOverlay.isVisible = true
            showOverlay(touchOverlay)
        elseif event.phase == "moved" then
            if thumb.isFocus then
                local newX = event.x - sliderGroup.x
                if newX < -width / 2 then
                    newX = -width / 2
                elseif newX > width / 2 then
                    newX = width / 2
                end
                thumb.x = newX
                touchOverlay.x = newX
                filledTrack.width = newX + width / 2
                local value = minValue + (newX + width / 2) / width * (maxValue - minValue)
                onChange(value)
            end
        elseif event.phase == "ended" or event.phase == "cancelled" then
            display.getCurrentStage():setFocus(nil)
            thumb.isFocus = nil
            hideOverlay(touchOverlay)
        end
        return true
    end

    thumb:addEventListener("touch", onThumbTouch)

    return sliderGroup
end

Here is how it could be used:

-- Example usage
local slider = createSlider({
    width = 300,
    height = 6,
    thumbRadius = 12,
    minValue = 0,
    maxValue = 100,
    startValue = 50,
    onChange = function(value)
        print("Slider value: " .. value)
    end
})

Explanation

Track: A rectangle representing the slider’s range.

Filled Track: A rectangle that grows/shrinks with the thumb, representing the selected value.

Caps: Circles at both ends of the track for a smoother appearance.

Thumb: A circle that users can drag to select a value.

Touch Overlay: A larger, semi-transparent circle that appears when the thumb is pressed and dragged, providing visual feedback.

Feel free to customize the slider to fit your needs. I hope you find this useful! Cheers

1 Like

Thank you - very nice! Will be using this in my next project, since this is better than the default slider widget. A useful option would be to display the current value near the slider. Admittedly, this requires a few extra options like font, fontSize, fontColor and relative position, so I guess it’s sometime better to allow the user to handle this themselves.

1 Like