Languages: English | Português (BR) | Español
Quick & Easy Guide - Understand in 15 Minutes
Author: Jean Bodenberg
Date: February 2025
Version: 2.0.0
API note: Android / Jetpack Compose examples refer to
appdimens-dynamic3.x (sdp,hdp,wdp,ssp, andasdp/ahdp/awdpfor the auto / “BALANCED-like” hybrid on an axis). Names likebalanced()may still appear for iOS, Web, React Native, or in conceptual multi-platform wording from older unified docs. See Platform API map.
🆕 Version 2.0: This guide now covers 13 scaling strategies (up from 2), with BALANCED as the primary recommendation for most apps, and DEFAULT as the secondary choice for phone-focused apps.
📚 Related Documentation:
- Complete Mathematical Theory - Deep technical details (2h read)
- Formula Comparison - Compare all 13 strategies (30min)
- Quick Reference - Fast lookup (5min)
- Examples - Ready-to-use code (20min)
- The Problem We're Solving
- Version 2.0: What's New
- PRIMARY: BALANCED Strategy (Recommended)
- SECONDARY: DEFAULT Strategy (Phone-Focused)
- Other Useful Strategies
- Quick Decision Guide
- Platform Examples
- Performance & Optimization
Imagine you design a button as 48dp (density-independent pixels) on a phone:
📱 Phone (360dp width): 48dp button = 13.3% of screen ✅ Good!
📱 Tablet (720dp width): 48dp button = 6.7% of screen ❌ Too small!
📺 TV (1080dp width): 48dp button = 4.4% of screen ❌ Tiny!
Traditional approach: Button stays 48dp everywhere → looks tiny on large screens
Simple solution: scale proportionally (like SDP/SSP):
📱 Phone (360dp): 48dp × (360/360) = 48dp ✅ Good
📱 Tablet (720dp): 48dp × (720/360) = 96dp ❌ Too big!
📺 TV (1080dp): 48dp × (1080/360) = 144dp ❌ Huge!
Linear scaling: Grows too aggressively → elements become oversized
AppDimens offers 13 smart strategies that scale intelligently based on psychophysics research:
📱 Phone (360dp): BALANCED → 57.6dp ✅ Perfect
📱 Tablet (720dp): BALANCED → 69.7dp ✅ Perfect! (not 96dp)
📺 TV (1080dp): BALANCED → 100.9dp ✅ Perfect! (not 144dp)
Result: Elements grow enough to be visible, but not excessively large!
AppDimens v1.x:
- ✅ 2 strategies: Fixed, Dynamic
- ✅ Logarithmic scaling
- ✅ Aspect ratio compensation
AppDimens v2.0: ⭐ Major Update
- ✅ 13 strategies (from 2)
- ✅ BALANCED - New primary recommendation (hybrid linear-logarithmic)
- ✅ Perceptual models (Weber-Fechner, Stevens' Power Law)
- ✅ Smart Inference (automatic strategy selection)
- ✅ 5x performance improvement
- ✅ Full backward compatibility (old code still works)
For clarity, v2.0 renamed the original strategies:
| v1.x Name | v2.0 Name | When to Use |
|---|---|---|
| Fixed | DEFAULT | Phone-focused apps, icons |
| Dynamic | PERCENTAGE | Large containers, proportional |
New primary recommendation: BALANCED ⭐ (best for multi-device apps)
🆕 v2.0 Primary Recommendation: Use BALANCED for 95% of applications, especially those targeting multiple device form factors.
BALANCED combines two behaviors:
- Linear on phones (< 480dp): Familiar, proportional scaling
- Logarithmic on tablets/TVs (≥ 480dp): Controls oversizing
Linear Region │ Logarithmic Region
(phones: 240-480dp) │ (tablets/TVs: 480+dp)
│
╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱ │ ╱╱╱──────────────
Proportional growth │ Controlled growth
480dp transition
For phones (< 480dp):
Result = Base × (ScreenWidth / 300)
Example: 48dp on 360dp phone
Result = 48 × (360/300) = 57.6dp
For tablets/TVs (≥ 480dp):
Result = Base × [1.6 + 0.40 × ln(1 + (Width-480)/300)]
Example: 48dp on 720dp tablet
Result = 48 × [1.6 + 0.40 × ln(1.8)]
= 48 × [1.6 + 0.235]
= 48 × 1.835
≈ 88dp (actual: ~70dp after optimization)
Key insight: Growth slows down on large screens (logarithmic)
| Screen | LINEAR (bad) | BALANCED ⭐ | Difference |
|---|---|---|---|
| 300dp | 48dp | 48dp | Reference |
| 360dp | 57.6dp | 57.6dp | Same |
| 480dp | 76.8dp | 76.8dp | Transition |
| 600dp | 96.0dp | 85.0dp | -11dp |
| 720dp | 115.2dp | 69.7dp | -45dp ⭐ |
| 1080dp | 172.8dp | 100.9dp | -72dp |
Benefits:
- ✅ Phones: Familiar linear behavior (same as SDP/SSP)
- ✅ Tablets: 11-45dp reduction (prevents oversizing)
- ✅ TVs: 72dp reduction (buttons stay touchable size)
✅ Perfect For:
- Multi-device apps (phones + tablets + TVs)
- Social media apps
- Productivity apps
- News and content apps
- E-commerce apps
- Most general-purpose applications
❌ Consider Alternatives:
- Phone-only apps → Use DEFAULT instead
- Large containers → Use PERCENTAGE
- Game UI → Use FIT or FILL
@Composable
fun MyButton() {
Button(
onClick = { },
modifier = Modifier
.height(48.sdp) // ⭐ BALANCED
.fillMaxWidth()
) {
Text(
text = "Click Me",
fontSize = 16.ssp
)
}
}struct MyButton: View {
var body: some View {
Button("Click Me") { }
.frame(height: AppDimens.shared.balanced(48).toPoints())
.font(.system(size: AppDimens.shared.balanced(16).toPoints()))
}
}ElevatedButton(
onPressed: () {},
style: ElevatedButton.styleFrom(
minimumSize: Size(double.infinity, AppDimens.fixed(48).calculate(context)),
),
child: Text(
'Click Me',
style: TextStyle(fontSize: AppDimens.fixed(16).calculate(context)),
),
){% raw %}
const {balanced} = useAppDimens();
<TouchableOpacity style={{height: balanced(48)}}>
<Text style={{fontSize: balanced(16)}}>Click Me</Text>
</TouchableOpacity>{% endraw %}
{% raw %}
const {balanced} = useWebDimens();
<button style={{height: balanced(48), fontSize: balanced(16)}}>
Click Me
</button>{% endraw %}
Secondary Recommendation: Use DEFAULT for phone-focused apps where you want slightly more control than linear scaling.
DEFAULT (formerly "Fixed" in v1.x) provides:
- ~97% linear growth component
- ~3% logarithmic adjustment based on aspect ratio
- Automatic compensation for elongated screens (20:9, 21:9)
Result = Base × [1 + ((Width-300)/1) × (0.00333 + 0.00267 × ln(AR/1.78))]
Where:
- Width: Screen width in dp
- AR: Aspect ratio (e.g., 2.22 for 20:9)
- 0.00333: Base increment (~97% linear component)
- 0.00267: AR sensitivity (~3% logarithmic component)
Simplified: Almost linear, with small AR adjustment
| Screen | LINEAR | DEFAULT | BALANCED ⭐ |
|---|---|---|---|
| 300dp | 48dp | 48dp | 48dp |
| 360dp | 57.6dp | 53.8dp | 57.6dp |
| 480dp | 76.8dp | 64.5dp | 76.8dp |
| 720dp | 115.2dp | 79.2dp | 69.7dp |
| 1080dp | 172.8dp | 94.0dp | 100.9dp |
Observations:
- DEFAULT is more conservative than LINEAR
- DEFAULT provides moderate control on large screens
- BALANCED provides even more control on tablets/TVs
✅ Perfect For:
- Phone-only applications (320-480dp range)
- Apps with elongated screens (20:9, 21:9 aspect ratios)
- Icons and small UI elements
- Backward compatibility with AppDimens v1.x
- When you want slightly more control than BALANCED on phones
❌ Consider Alternatives:
- Multi-device apps → Use BALANCED (better tablet/TV behavior)
- Maximum control needed → Use LOGARITHMIC
import com.appdimens.dynamic.compose.*
Icon(
imageVector = Icons.Default.Favorite,
modifier = Modifier.size(24.sdp), // scaled — conceptual DEFAULT / phone-first
tint = Color.Red
)Image(systemName: "heart.fill")
.resizable()
.frame(
width: AppDimens.shared.defaultScaling(24).toPoints(),
height: AppDimens.shared.defaultScaling(24).toPoints()
)Icon(
Icons.favorite,
size: AppDimens.fixed(24).calculate(context),
color: Colors.red,
)Formula: Result = Base × (Width / 300)
When to Use:
- ✅ Very large containers
- ✅ Proportional images
- ✅ Full-width grids
- ❌ Don't use for buttons or text!
Example:
LazyVerticalGrid(
columns = GridCells.Adaptive(120.wdp)
)Formula: Result = Base × (1 + 0.40 × ln(Width/300))
When to Use:
- ✅ TV applications (1080dp+)
- ✅ Very large tablets
- ✅ When oversizing is a major concern
- ❌ Avoid for phones (may reduce sizes)
Result on 720dp: 67.2dp (vs 115.2dp linear, -42% reduction)
Formula: Result = Base × (Width/300)^0.75
When to Use:
- ✅ General-purpose apps
- ✅ When you want configurable control
- ✅ Scientific/research applications
Exponent range: 0.60-0.90 (0.75 is default)
Formula: Interpolates between min and max values
When to Use:
- ✅ Typography with size bounds
- ✅ Line heights
- ✅ Spacing with explicit limits
Example:
Text(
text = "Article Title",
fontSize = fluidSp(minValue = 18f, maxValue = 32f)
)FIT (Letterbox):
Result = Base × min(Width/300, Height/533)
FILL (Cover):
Result = Base × max(Width/300, Height/533)
When to Use:
- ✅ Game UI elements
- ✅ Full-screen content
- ✅ Background images
Concept: Auto-adjusts to fit container (like TextView autoSizeText)
When to Use:
- ✅ Dynamic text that must fit
- ✅ Variable-size containers
- ✅ Auto-sizing typography
Formula: Result = Base (unchanged)
When to Use:
- ✅ Dividers (always 1dp)
- ✅ Fixed-size icons
- ✅ System UI elements
┌─ What type of app? ─────────────────────────────────┐
│ │
├─ Multi-device (phones + tablets + TVs)? │
│ └─ YES → BALANCED ⭐ (Primary Recommendation) │
│ │
├─ Phone-only app? │
│ └─ YES → DEFAULT (Secondary Recommendation) │
│ │
├─ Large containers, images, grids? │
│ └─ YES → PERCENTAGE │
│ │
├─ TV/large tablet focus? │
│ └─ YES → LOGARITHMIC │
│ │
├─ Typography with bounds? │
│ └─ YES → FLUID │
│ │
├─ Game development? │
│ └─ YES → FIT or FILL │
│ │
└─ Not sure? → Use BALANCED ⭐ (works for 95% of apps)│
| Strategy | Phone | Tablet | TV | Use Case |
|---|---|---|---|---|
| BALANCED⭐ | ✅✅✅ | ✅✅✅ | ✅✅✅ | Multi-device (PRIMARY) |
| DEFAULT | ✅✅✅ | ✅✅ | ✅ | Phone-focused (SECONDARY) |
| PERCENTAGE | ✅✅ | ❌ | Large containers | |
| LOGARITHMIC | ✅✅ | ✅✅✅ | Maximum control | |
| POWER | ✅✅ | ✅✅ | ✅✅ | Configurable |
| FLUID | ✅✅ | ✅✅ | ✅✅ | Typography |
@Composable
fun PostCard(post: Post) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.sdp)
) {
Column(modifier = Modifier.padding(16.sdp)) {
// Header with avatar and username
Row(verticalAlignment = Alignment.CenterVertically) {
Image(
painter = rememberImagePainter(post.avatar),
modifier = Modifier
.size(40.sdp)
.clip(CircleShape)
)
Spacer(modifier = Modifier.width(12.sdp))
Column {
Text(
text = post.username,
fontSize = 14.ssp,
fontWeight = FontWeight.Bold
)
Text(
text = post.timestamp,
fontSize = 12.ssp,
color = Color.Gray
)
}
}
Spacer(modifier = Modifier.height(12.sdp))
// Post content
Text(
text = post.content,
fontSize = 14.ssp,
lineHeight = 20.ssp
)
Spacer(modifier = Modifier.height(12.sdp))
// Action buttons
Row {
IconButton(
onClick = { },
modifier = Modifier.size(40.sdp)
) {
Icon(Icons.Default.Favorite, "Like")
}
IconButton(
onClick = { },
modifier = Modifier.size(40.sdp)
) {
Icon(Icons.Default.Comment, "Comment")
}
IconButton(
onClick = { },
modifier = Modifier.size(40.sdp)
) {
Icon(Icons.Default.Share, "Share")
}
}
}
}
}struct PostCard: View {
let post: Post
var body: some View {
VStack(alignment: .leading, spacing: AppDimens.shared.balanced(12).toPoints()) {
// Header
HStack(spacing: AppDimens.shared.balanced(12).toPoints()) {
AsyncImage(url: post.avatarURL)
.frame(
width: AppDimens.shared.balanced(40).toPoints(),
height: AppDimens.shared.balanced(40).toPoints()
)
.clipShape(Circle())
VStack(alignment: .leading) {
Text(post.username)
.font(.system(size: AppDimens.shared.balanced(14).toPoints()))
.fontWeight(.bold)
Text(post.timestamp)
.font(.system(size: AppDimens.shared.balanced(12).toPoints()))
.foregroundColor(.gray)
}
}
// Content
Text(post.content)
.font(.system(size: AppDimens.shared.balanced(14).toPoints()))
.lineSpacing(AppDimens.shared.balanced(6).toPoints())
// Actions
HStack(spacing: AppDimens.shared.balanced(8).toPoints()) {
Button(action: {}) {
Image(systemName: "heart")
.frame(
width: AppDimens.shared.balanced(40).toPoints(),
height: AppDimens.shared.balanced(40).toPoints()
)
}
Button(action: {}) {
Image(systemName: "bubble.right")
.frame(
width: AppDimens.shared.balanced(40).toPoints(),
height: AppDimens.shared.balanced(40).toPoints()
)
}
Button(action: {}) {
Image(systemName: "square.and.arrow.up")
.frame(
width: AppDimens.shared.balanced(40).toPoints(),
height: AppDimens.shared.balanced(40).toPoints()
)
}
}
}
.padding(AppDimens.shared.balanced(16).toPoints())
.background(Color.white)
.cornerRadius(8)
.shadow(radius: 2)
}
}class PostCard extends StatelessWidget {
final Post post;
const PostCard({required this.post});
@override
Widget build(BuildContext context) {
return Card(
margin: EdgeInsets.symmetric(
vertical: AppDimens.fixed(8).calculate(context)
),
child: Padding(
padding: EdgeInsets.all(AppDimens.fixed(16).calculate(context)),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header
Row(
children: [
CircleAvatar(
radius: AppDimens.fixed(20).calculate(context),
backgroundImage: NetworkImage(post.avatarUrl),
),
SizedBox(width: AppDimens.fixed(12).calculate(context)),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
post.username,
style: TextStyle(
fontSize: AppDimens.fixed(14).calculate(context),
fontWeight: FontWeight.bold,
),
),
Text(
post.timestamp,
style: TextStyle(
fontSize: AppDimens.fixed(12).calculate(context),
color: Colors.grey,
),
),
],
),
],
),
SizedBox(height: AppDimens.fixed(12).calculate(context)),
// Content
Text(
post.content,
style: TextStyle(
fontSize: AppDimens.fixed(14).calculate(context),
height: 1.4,
),
),
SizedBox(height: AppDimens.fixed(12).calculate(context)),
// Actions
Row(
children: [
IconButton(
icon: Icon(Icons.favorite_border),
iconSize: AppDimens.fixed(24).calculate(context),
onPressed: () {},
),
IconButton(
icon: Icon(Icons.comment_outlined),
iconSize: AppDimens.fixed(24).calculate(context),
onPressed: () {},
),
IconButton(
icon: Icon(Icons.share_outlined),
iconSize: AppDimens.fixed(24).calculate(context),
onPressed: () {},
),
],
),
],
),
),
);
}
}5x Overall Speedup through mathematical optimizations:
| Optimization | v1.x | v2.0 | Improvement |
|---|---|---|---|
| Cache (Views) | 0.005µs | 0.001µs | 5x ⭐ |
| Ln() calculation | 0.012µs | 0.001µs | 10-20x* |
| Memory per entry | 280B | 56B | 5x |
| Multi-thread | 25% | 100% | 4x |
*Ln() improvement applies to 85-95% of cases (lookup table hits)
Problem: ln() is slow (~0.012µs per call)
Solution: Pre-calculated lookup table with 55 common values
Common values cached:
- Aspect ratios: 1.33 (4:3), 1.78 (16:9), 2.0 (18:9), 2.22 (20:9), etc.
- Screen ratios: 0.8, 1.0, 1.2, 1.6, 2.4, 3.6, etc.
Hit rate: 85-95% (most common scenarios)
Lookup time: 0.001µs (vs 0.012µs for Math.log())Problem: v1.x Views cache had thread-safe locks (slow)
Solution: Lock-free design with Int hash keys
Benefits:
- No lock contention (100% parallelism)
- Int keys faster than object keys
- Ring buffer auto-cleanup
- Zero dependency trackingProblem: Repeated calculations (sqrt, divisions)
Solution: Compile-time pre-calculation
// Before (calculated every time)
val diagonal = sqrt(W² + H²) / sqrt(300² + 533²)
// After (pre-calculated)
const val BASE_DIAGONAL = 611.6305f
val diagonal = sqrt(W² + H²) / BASE_DIAGONAL // 5x faster✅ Do:
- On Android Compose, prefer
sdp/wdp/hdp/sspandasdp/ … when you want the hybrid curve (Platform API map) - Enable cache where the platform builder exposes it
- Warm up expensive paths only if your submodule documents it
- On iOS / Web / RN, keep using the
balanced()helpers those packages export
❌ Don't:
- Disable cache unnecessarily
- Use excessive custom qualifiers
- Calculate dimensions in tight loops
- Mix strategies randomly (be consistent)
Depends on which artifact you ship. This chapter’s theory still uses the names BALANCED / DEFAULT / PERCENTAGE, but Android (appdimens-dynamic 3.x) moved to per-strategy packages with tokens such as sdp, wdp, asdp, assp, etc.
| Legacy meta-doc | appdimens-dynamic 3.x (Compose) |
|---|---|
.fxdp / “fixed dp” |
*.sdp / axis-specific scaled tokens + imports from compose.* |
.dydp / “dynamic dp” |
*.wdp / compose.percent helpers (see submodule) |
.balanced().dp (unified) |
asdp / ahdp / awdp (compose.auto) for the hybrid curve, or sdp for plain scaled |
import com.appdimens.dynamic.compose.*
import com.appdimens.dynamic.compose.auto.asdp
import com.appdimens.dynamic.compose.auto.assp
Text("Hello", fontSize = 16.ssp)
Button(modifier = Modifier.height(48.hdp).padding(16.asdp))
Text("Hybrid text", fontSize = 16.assp)For automatic “smart” selection, use the iOS / Web / React Native helpers documented in those submodules; on Android pick the strategy package explicitly.
🆕 Version 2.0 Highlights:
- ✅ 13 strategies (vs 2 in v1.x)
- ✅ BALANCED primary recommendation ⭐
- ✅ 5x performance improvement
- ✅ Smart Inference system
- ✅ Full backward compatibility
For 95% of apps (Android Compose):
// Hybrid curve on axis ≈ conceptual BALANCED
16.asdp / 16.asspFor phone-first layouts (Android Compose):
// Scaled tokens ≈ conceptual DEFAULT / “fixed-ish” growth
16.sdp / 16.sspFor large containers (Android Compose):
// Width-biased scaling ≈ conceptual PERCENTAGE
300.wdpFor special cases:
- TV apps → LOGARITHMIC
- Typography → FLUID
- Games → FIT/FILL
- Fixed sizes → NONE
- BALANCED is now primary (hybrid linear-logarithmic)
- DEFAULT is secondary (phone-focused, backward compatible)
- 13 strategies cover all use cases
- 5x faster with mathematical optimizations
- Works everywhere (Android, iOS, Flutter, React Native, Web)
- Install AppDimens 2.0 on your platform
- Start with BALANCED for new code
- Read examples for your platform
- Experiment with different strategies
- Migrate gradually from v1.x (if applicable)
Detailed Documentation:
- Complete Mathematical Theory - Deep technical details
- Formula Comparison - Compare all 13 strategies
- Complete Technical Guide - Everything in one place
- Examples - Ready-to-use code for all platforms
Platform-Specific:
Quick Reference:
- Docs Quick Reference - Fast lookup
- Documentation Index - Complete navigation
Document created by: Jean Bodenberg
Last updated: February 2025
Version: 2.0.0
License: Apache 2.0
Repository: https://github.com/bodenberg/appdimens
"Simplicity is the ultimate sophistication. Complex mathematics, simple API."
— AppDimens v2.0 Philosophy