Smart Responsive Dimensions for Any Screen
๐ Documentation | โก Quick Reference | ๐ฌ Technical Details
Languages: English | Portuguรชs (BR) | Espaรฑol
๐ฏ 13 Scaling Strategies (up from 2!)
- BALANCED โญ New recommended primary strategy - hybrid linear-logarithmic
- DEFAULT (formerly Fixed) - logarithmic with aspect ratio (secondary recommendation)
- PERCENTAGE (formerly Dynamic) - proportional scaling
- LOGARITHMIC - pure Weber-Fechner psychophysics
- POWER - Stevens' Power Law (configurable)
- FLUID - CSS clamp-like with breakpoints
- Plus 7 more: INTERPOLATED, DIAGONAL, PERIMETER, FIT, FILL, AUTOSIZE ๐, NONE
๐ง Smart Inference System
- Automatic strategy selection based on element type
- 18 element types (BUTTON, TEXT, ICON, CONTAINER, etc.)
- 8 device categories (PHONE_SMALL to TV)
- Weight-based decision system
โก 5x Performance Improvement
- Unified lock-free cache (0.001ยตs)
- Ln() lookup table (10-20x faster)
- Pre-calculated constants
- Binary search algorithms (O(log n))
โป๏ธ Full Backward Compatibility
- Old
.fxdp/.dydpextensions still work - Smooth migration path to
.balanced(),.defaultDp,.percentageDp
AppDimens makes your UI elements scale perfectly across all devices - from phones to tablets, TVs, watches, and web browsers.
Instead of fixed sizes that look tiny on tablets or huge on watches, AppDimens uses perceptual scaling based on psychophysics research (Weber-Fechner, Stevens) that adapts intelligently to screen size, aspect ratio, and device type.
โ Without AppDimens:
Phone (360dp): Button = 48dp (13% of screen) โ
Good
Tablet (720dp): Button = 48dp (7% of screen) โ Too small!
โ With Linear Scaling (SDP):
Phone (360dp): Button = 58dp (16% of screen) โ
OK
Tablet (720dp): Button = 115dp (16% of screen) โ Too big!
โ
With AppDimens BALANCED โญ:
Phone (360dp): Button = 58dp (16% of screen) โ
Perfect
Tablet (720dp): Button = 70dp (10% of screen) โ
Perfect!
- โ Perfect proportions on any screen size
- โ Works everywhere: Android, iOS, Flutter, React Native, Web
- โ
Simple API:
.balanced(),.defaultDp,.percentageDp - โ Scientifically proven: Based on psychophysics research (Weber-Fechner, Stevens)
- โ Best performance: 5x faster with lock-free cache and optimizations
- โ 13 scaling strategies: From simple to advanced, covering all use cases
- โ Smart Inference: Automatic strategy selection for 18 element types
- โ Physical units: Real-world measurements (mm, cm, inch) across all platforms
- โ Game development: Specialized modules for Android (C++/NDK) and iOS (Metal)
- โ AutoSize ๐: Container-aware auto-sizing like TextView autoSizeText
dependencies {
// Core library (Fixed + Dynamic scaling + Physical Units)
// Includes: .fxdp, .dydp, Physical Units (mm/cm/inch), Grid calculations
implementation("io.github.bodenberg:appdimens-dynamic:2.0.0")
// SDP scaling (Scalable DP for XML)
// Includes: @dimen/_16sdp, etc.
implementation("io.github.bodenberg:appdimens-sdps:2.0.0")
// SSP scaling (Scalable SP for text in XML)
// Includes: @dimen/_18ssp, etc.
implementation("io.github.bodenberg:appdimens-ssps:2.0.0")
// All-in-one (includes dynamic, sdps, ssps)
// โ ๏ธ Note: Does NOT include games module
implementation("io.github.bodenberg:appdimens-all:2.0.0")
// Game development (C++/NDK + OpenGL)
// ๐ฎ Separate dependency - not included in "all"
implementation("io.github.bodenberg:appdimens-games:2.0.0")
}CocoaPods:
# Full package (Main + UI)
pod 'AppDimens', '~> 2.0.0'
# Only Main module
pod 'AppDimens/Main', '~> 2.0.0'
# Games module (separate)
pod 'AppDimens/Games', '~> 2.0.0'Swift Package Manager:
dependencies: [
.package(url: "https://github.com/bodenberg/appdimens.git", from: "2.0.0")
]dependencies:
appdimens: ^2.0.0# npm
npm install [email protected]
# yarn
yarn add [email protected]# npm
npm install [email protected]
# yarn
yarn add [email protected]
# pnpm
pnpm add [email protected]Vanilla JavaScript (CDN):
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/index.js"></script>
<script>
const { balanced, defaultScaling, fluid } = WebDimens;
document.getElementById('myElement').style.width = balanced(300);
</script>๐ Complete Installation Guide
@Composable
fun MyCard() {
Card(
modifier = Modifier
.width(300.balanced().dp) // โจ BALANCED (RECOMMENDED) โญ
.padding(16.balanced().dp) // โจ Adapts intelligently
) {
Text(
text = "Hello World",
fontSize = 18.balanced().sp // โจ Readable everywhere
)
}
}<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/_16sdp">
<TextView
android:layout_width="@dimen/_300sdp"
android:layout_height="wrap_content"
android:textSize="@dimen/_18ssp"
android:text="Hello World" />
</LinearLayout>class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// BALANCED scaling (recommended) โญ
val width = 300.balanced().toPx(resources)
binding.card.layoutParams.width = width.toInt()
// Physical units
val margin = AppDimensPhysicalUnits.toCm(2f, resources)
binding.button.setPadding(margin.toInt(), 0, margin.toInt(), 0)
}
}<!-- layout/activity_main.xml -->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<import type="com.appdimens.dynamic.compose.AppDimensExtKt"/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/_16sdp">
<TextView
android:layout_width="@dimen/_300sdp"
android:layout_height="wrap_content"
android:textSize="@dimen/_18ssp"
android:text="Hello World" />
<!-- Dynamic dimensions in DataBinding -->
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="@{AppDimensExtKt.balanced(48).dp}"
android:text="Click Me" />
</LinearLayout>
</layout>// Activity with DataBinding
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding: ActivityMainBinding =
DataBindingUtil.setContentView(this, R.layout.activity_main)
// Set dimensions programmatically
binding.button.apply {
layoutParams.width = 200.balanced().toPx(resources).toInt()
layoutParams.height = 56.balanced().toPx(resources).toInt()
}
}
}// Use real-world measurements
// Physical Units are part of appdimens-dynamic
val cardWidth = AppDimensPhysicalUnits.toCm(8f, resources) // 8 cm
val buttonHeight = AppDimensPhysicalUnits.toInch(0.5f, resources) // 0.5 inch
val padding = AppDimensPhysicalUnits.toMm(10f, resources) // 10 mm
view.layoutParams.width = cardWidth.toInt()
button.layoutParams.height = buttonHeight.toInt()
view.setPadding(padding.toInt(), padding.toInt(), padding.toInt(), padding.toInt())
// Grid calculations (also in appdimens-dynamic)
val spanCount = AppDimens.calculateAvailableItemCount(
containerSizePx = recyclerView.width,
itemSizeDp = 100f,
itemMarginDp = 8f,
resources = resources
)@Composable
fun GameScreen() {
val gamesManager = remember { AppDimensGames.getInstance() }
LaunchedEffect(Unit) {
gamesManager.initialize(context)
}
Canvas(modifier = Modifier.fillMaxSize()) {
// Game-specific dimensions
val buttonSize = gamesManager.calculateButtonSize(48f)
val playerSize = gamesManager.calculatePlayerSize(64f)
// Draw game elements with scaled dimensions
drawCircle(
color = Color.Blue,
radius = playerSize / 2
)
}
}struct MyCard: View {
var body: some View {
VStack {
Text("Hello World")
.font(.system(size: AppDimens.shared.balanced(18).toPoints()))
}
.padding(AppDimens.shared.balanced(16).toPoints())
.frame(width: AppDimens.shared.balanced(300).toPoints())
}
}class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let containerView = UIView()
containerView.backgroundColor = .systemBlue
containerView.layer.cornerRadius = AppDimens.shared.balanced(16).toPoints()
view.addSubview(containerView)
let titleLabel = UILabel()
titleLabel.text = "Hello World"
titleLabel.font = .systemFont(ofSize: AppDimens.shared.balanced(18).toPoints())
containerView.addSubview(titleLabel)
}
}Widget build(BuildContext context) {
return Container(
width: AppDimens.balanced(300).calculate(context),
padding: EdgeInsets.all(AppDimens.balanced(16).calculate(context)),
child: Text(
'Hello World',
style: TextStyle(fontSize: AppDimens.balanced(18).calculate(context)),
),
);
}{% raw %}
function MyCard() {
const { balanced } = useAppDimens();
return (
<View style={{ width: balanced(300), padding: balanced(16) }}>
<Text style={{ fontSize: balanced(18) }}>
Hello World
</Text>
</View>
);
}{% endraw %}
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/index.js"></script>
</head>
<body>
<div id="container">
<header id="header">
<h1 id="title">Hello World</h1>
</header>
</div>
<script type="module">
import { webdimens } from 'https://cdn.jsdelivr.net/npm/[email protected]/dist/index.mjs';
// Apply balanced dimensions (recommended) โญ
document.getElementById('header').style.height = webdimens.balanced(64);
document.getElementById('title').style.fontSize = webdimens.balanced(24);
document.getElementById('container').style.padding = webdimens.balanced(24);
</script>
</body>
</html>{% raw %}
import { useWebDimens } from 'webdimens/react';
function MyCard() {
const { balanced, fluid } = useWebDimens();
return (
<div style={{ width: balanced(300), padding: balanced(16) }}>
<h2 style={{ fontSize: fluid(18, 24) }}>Hello World</h2>
</div>
);
}{% endraw %}
<template>
<div :style="{ width: balanced(300), padding: balanced(16) }">
<h2 :style="{ fontSize: balanced(18) }">Hello World</h2>
</div>
</template>
<script setup>
import { useWebDimens } from 'webdimens/vue';
const { balanced } = useWebDimens();
</script><script>
import { webDimensStore } from 'webdimens/svelte';
$: wd = $webDimensStore;
$: width = wd.balanced(300);
$: padding = wd.balanced(16);
$: fontSize = wd.balanced(18);
</script>
<div style="width: {width}; padding: {padding};">
<h2 style="font-size: {fontSize};">Hello World</h2>
</div>import { Component } from '@angular/core';
import { WebDimensService } from 'webdimens/angular';
@Component({
selector: 'app-card',
template: `
<div [ngStyle]="{ width: width, padding: padding }">
<h2 [ngStyle]="{ fontSize: fontSize }">Hello World</h2>
</div>
`
})
export class CardComponent {
width = '';
padding = '';
fontSize = '';
constructor(private wd: WebDimensService) {
this.width = wd.balanced(300);
this.padding = wd.balanced(16);
this.fontSize = wd.balanced(18);
}
}๐ More Examples
AppDimens 2.0 offers 13 scaling strategies for different needs:
| Strategy | When to Use | Example | Availability |
|---|---|---|---|
| BALANCED โญ RECOMMENDED | 95% of cases - multi-device apps (phones, tablets, TVs) | 48.balanced().dp |
All platforms |
| DEFAULT (Secondary) | Phone-focused apps, icons, backward compatibility | 48.defaultDp |
All platforms |
| PERCENTAGE | Large containers, full-width grids, proportional elements | 100.percentageDp |
All platforms |
| LOGARITHMIC | TV apps, maximum control on large screens | 48.logarithmic() |
All platforms |
| POWER | General purpose, configurable with exponent | 48.power(0.75) |
All platforms |
| FLUID ๐ | Typography, spacing with smooth min/max transitions | fluid(16, 24) |
All platforms |
| FIT / FILL | Game UI (letterbox/cover) | 48.fit() / 48.fill() |
All platforms |
| AUTOSIZE ๐ | Container-aware auto-sizing (like TextView autoSizeText) | smart().autosize() |
All platforms |
| INTERPOLATED | Moderate scaling (50% linear) | 48.interpolated() |
All platforms |
| DIAGONAL | Scale based on screen diagonal (true physical size) | 48.diagonal() |
All platforms |
| PERIMETER | Scale based on W+H perimeter | 48.perimeter() |
All platforms |
| NONE | No scaling (constant size) | 48.none() |
All platforms |
๐ Understanding Scaling Strategies
AppDimens was scientifically compared against 7 other scaling approaches:
๐ฅ #1 AppDimens BALANCED: 93/100 โญโญโญโญโญ (Primary recommendation)
๐ฅ #2 AppDimens LOGARITHMIC: 88/100 โญโญโญโญโญ (TV/Large tablets)
๐ฅ #3 AppDimens POWER: 86/100 โญโญโญโญ
#4 AppDimens DEFAULT: 82/100 โญโญโญโญ (Phone-focused)
#5 SDP/SSP: 65/100
#6 CSS vw/vh: 58/100
- โ BALANCED strategy: Hybrid linear-logarithmic (40% oversizing reduction)
- โ Perceptual models: Based on psychophysics (Weber-Fechner, Stevens)
- โ 13 strategies: Most comprehensive library
- โ Smart Inference: Automatic strategy selection
- โ 5x faster: Lock-free cache and optimizations
- โ Aspect ratio compensation: Only library with AR adjustment (DEFAULT strategy)
๐ See Full Comparison
- Quick Reference โก Find anything in seconds
- Simplified Guide ๐ Understand in 15 minutes
- Examples ๐ป Ready-to-use code
- Complete Technical Guide ๐ฌ Everything in one place (2h read)
- Formula Comparison ๐ Scientific analysis & rankings
- Mathematical Theory ๐ Formal mathematical foundation
- ๐ค Android Guide
- ๐ iOS Guide
- ๐ฏ Flutter Guide
- โ๏ธ React Native Guide
- ๐ Web Guide
๐ Complete Documentation Index
Auto-adapt to screen rotation! Design for one orientation, automatically maintain proportions when rotated:
// Android - Design for portrait, auto-adapts to landscape
val cardWidth = 300.balanced().portraitLowest().dp
// Portrait (360x800): Uses width (360) โ
// Landscape (800x360): Auto-inverts to width (800) โ
// Shorthand extensions
val padding = 16.balancedPortraitLowest // Auto-inverts in landscape
val height = 200.balancedLandscapeHighest // Auto-inverts in portrait// iOS - Same concept
let cardWidth = AppDimens.shared.balanced(300).portraitLowest().toPoints()// Flutter
final cardWidth = AppDimens.balanced(300).portraitLowest().calculate(context);๐ Complete Base Orientation Guide
Smooth interpolation between min/max values - perfect for typography and controlled growth:
// Android Compose - Font size 16-24sp between 320-768dp width
@Composable
fun FluidTypography() {
val fontSize = fluidSp(16f, 24f)
Text("Fluid Text", fontSize = fontSize)
// With custom breakpoints
val padding = fluidDp(8f, 16f, minWidth = 280f, maxWidth = 600f)
}// iOS SwiftUI - Same concept
struct FluidView: View {
var body: some View {
Text("Fluid Text")
.font(.system(size: AppDimens.shared.fluid(min: 16, max: 24).toPoints()))
.padding(AppDimens.shared.fluid(min: 8, max: 16).toPoints())
}
}// Flutter
final fontSize = AppDimens.fluid(16, maxValue: 24).calculate(context);
final padding = AppDimens.fluid(8, maxValue: 16).calculate(context);Availability: All platforms
When to use:
- โ Typography with explicit bounds
- โ Line heights and letter spacing
- โ Smooth transitions across breakpoints
- โ Not for general UI elements (use BALANCED instead)
Automatic strategy selection based on element type:
// Android - Automatic strategy selection
val buttonSize = 48.smart().forElement(ElementType.BUTTON).dp
// โ Automatically selects BALANCED for buttons on tablets
val containerWidth = 300.smart().forElement(ElementType.CONTAINER).dp
// โ Automatically selects PERCENTAGE for containers// iOS
let buttonSize = AppDimens.shared.smart(48).forElement(.button).toPoints()// Flutter
final buttonSize = AppDimens.smart(48).forElement(ElementType.button).calculate(context);// Android - Different sizes for different devices
val buttonSize = 56.balanced()
.screen(UiModeType.TV, 96.dp) // TVs: 96dp
.screen(UiModeType.WATCH, 40.dp) // Watches: 40dp
.screen(DpQualifier.SMALL_WIDTH, 600, 72.dp) // Tablets: 72dp
.dp // Others: auto-scaled from 56dpReal-world measurements across all platforms:
// Android - Physical units (mm, cm, inch)
val buttonWidth = 10.mm // 10 millimeters
val cardWidth = 8.cm // 8 centimeters
val screenSize = 5.inch // 5 inches
// AppDimens Games also supports physical units
val playerSize = appDimensGames.mm(15f) // 15mm player sprite// iOS - Same API
let buttonWidth = AppDimensPhysicalUnits.mm(10)
let cardWidth = AppDimensPhysicalUnits.cm(8)
let screenSize = AppDimensPhysicalUnits.inch(5)// Flutter
final buttonWidth = AppDimensPhysicalUnits.mmToPixels(10, context);
final cardWidth = AppDimensPhysicalUnits.cmToPixels(8, context);Available everywhere: Android (Code + Compose), iOS (SwiftUI + UIKit), Flutter, React Native, Web
Specialized high-performance modules for game development:
val appDimensGames = AppDimensGames.getInstance()
appDimensGames.initialize(context)
// Game-specific dimension types
val buttonSize = appDimensGames.calculateButtonSize(48f) // UI elements
val playerSize = appDimensGames.calculatePlayerSize(64f) // Game world
val enemySize = appDimensGames.calculateEnemySize(32f) // Game world
val uiOverlay = appDimensGames.calculateUISize(24f) // HUD/Overlay
// Vector and Rectangle operations
val position = GameVector2D(100f, 200f)
val scaledPos = appDimensGames.calculateVector2D(position, GameDimensionType.GAME_WORLD)
val bounds = GameRectangle(0f, 0f, 800f, 600f)
val scaledBounds = appDimensGames.calculateRectangle(bounds, GameDimensionType.DYNAMIC)
// Physical units in games
val physicalSize = appDimensGames.cm(2f) // 2cm for touch targetsFeatures:
- โ Native C++/NDK for maximum performance
- โ 4 dimension types: DYNAMIC, FIXED, GAME_WORLD, UI_OVERLAY
- โ Vector2D and Rectangle utilities
- โ OpenGL ES integration
- โ Physical units support (mm, cm, inch)
- โ Performance monitoring and optimization
- โ Separate dependency - not included in appdimens-all
// Metal/MetalKit integration
let buttonSize = gameUniform(48) // Uniform scaling
let playerSize = gameAspectRatio(64) // Aspect-ratio aware
let uiOverlay = gameViewport(24) // Viewport-based
// SwiftUI Integration
struct GameView: View {
var body: some View {
MetalGameView()
.frame(
width: gameAspectRatio(320),
height: gameAspectRatio(240)
)
.withAppDimens()
}
}Features:
- โ Native Metal/MetalKit support
- โ 5 viewport modes: Uniform, Horizontal, Vertical, AspectRatio, Viewport
- โ SIMD extensions for performance
- โ SwiftUI and UIKit compatible
- โ Coordinate transformations (Screen โ NDC)
๐ Android Game Development Guide ๐ iOS Game Development Guide
Control caching behavior globally across all AppDimens instances:
// Android - Global cache control
AppDimens.setGlobalCache(true) // Enable (default)
AppDimens.setGlobalCache(false) // Disable all caches
AppDimens.clearAllCaches() // Clear all cached values
// Per-instance cache control
val dimension = AppDimens.balanced(100)
.cache(true) // Enable cache for this instance
.toDp(resources)
// Check cache status
val isEnabled = AppDimens.isGlobalCacheEnabled()// iOS - Same concept
AppDimensGlobal.globalCacheEnabled = true
AppDimensGlobal.clearAllCaches()
// Per-instance
let dimension = AppDimens.shared.balanced(100)
.cache(true)
.toPoints()// Flutter
AppDimens.setGlobalCache(true);
AppDimens.clearAllCaches();
// Per-instance
final dimension = AppDimens.balanced(100)
.cache(true)
.calculate(context);Features:
- โ Global cache control affects all instances
- โ Per-instance cache control for fine-tuning
- โ Automatic cache invalidation on configuration changes
- โ Zero performance overhead when disabled
- โ Memory efficient with automatic cleanup
We welcome contributions!
- ๐ Report bugs
- ๐ก Suggest features
- ๐ Improve documentation
- โญ Star this repo!
Apache License 2.0 - see LICENSE file
Jean Bodenberg
- GitHub: @bodenberg
- Website: appdimens-project.web.app
If AppDimens helps your project:
- โญ Star this repository
- ๐ฆ Share on social media
- ๐ Write a review
- ๐ค Contribute to the project
Made with โค๏ธ for developers worldwide
Documentation โข Examples โข Technical Guide