In modern mobile UI development, responsive layouts help make an app feel vibrant and alive. Static screens can feel rigid, but interactive layouts that adapt dynamically to user input create a noticeable sense of control and polish. This guide walks you through building an interactive, resizable split-screen view in React Native using popular libraries such as React Native Reanimated and Gesture Handler.
✨ Why Micro-animations Matter:
Micro-animations are small, functional animations that make a big difference in user experience:
- Provide Feedback: Instantly Confirm User Actions.
- Guide the User: Direct attention and maintain spatial awareness.
- Add Polish: Make the UI feel crafted and deliberate. These animations only truly shine when paired with a consistent, well-designed UI foundation. Even the best animations can’t elevate an unpolished interface.
🏗️ Setting Up the Layout:
The screen splits into two sections: a top and a bottom pane. Using React Native Reanimated’s sharedValue,The height of the top section changes dynamically as the user drags a divider. Shared values update on the UI thread smoothly, without triggering costly re-renders.
const HEADER_HEIGHT = 60;
const MIN_SECTION_HEIGHT = 100;
const MAX_TOP_SECTION_HEIGHT = SCREEN_HEIGHT * 0.7;
const DEFAULT_TOP_SECTION_HEIGHT = SCREEN_HEIGHT * 0.45;
export default function Index() {
const topSectionHeight = useSharedValue(DEFAULT_TOP_SECTION_HEIGHT);
const topSectionAnimatedStyle = useAnimatedStyle(() => ({
height: topSectionHeight.value,
}));
return (
<GestureHandlerRootView style={{ flex: 1 }}>
<SafeAreaView style={styles.container}>
{/* Top Section - Resizable */}
<Animated.View style={[styles.topSection, topSectionAnimatedStyle]}>
</Animated.View>
{/* Bottom Section - Auto-adjusting */}
<View style={styles.bottomSection}>
</View>
</SafeAreaView>
</GestureHandlerRootView>
)
};
✋ Draggable with Gesture Handler:
Using the powerful gesture system from React Native Gesture Handler:
- onStart records the starting height of the top section.
- onStart records the starting height of the top section.
- onUpdate adjusts the height dynamically, clamping it within min and max bounds to keep the UI stable.
- onEnd snaps the height to the closest preset point (minimum, default, or maximum height) to optimize usability and predictability.
const ANIMATION_CONFIG = { damping: 25, stiffness: 300, mass: 0.8 };
const VELOCITY_THRESHOLD = 800;
const startY = useSharedValue(0);
const isDragging = useSharedValue(false);
const panGesture = Gesture.Pan()
.onStart(() => {
startY.value = topSectionHeight.value;
isDragging.value = true;
})
.onUpdate((event) => {
const newHeight = startY.value + event.translationY;
topSectionHeight.value = Math.max(
MIN_SECTION_HEIGHT,
Math.min(newHeight, MAX_TOP_SECTION_HEIGHT)
);
})
.onEnd((event) => {
isDragging.value = false;
const velocity = event.velocityY;
let targetHeight;
if (Math.abs(velocity) > VELOCITY_THRESHOLD) {
targetHeight =
velocity > 0 ? DEFAULT_TOP_SECTION_HEIGHT : MAX_TOP_SECTION_HEIGHT;
} else {
const distances = [
{
h: MIN_SECTION_HEIGHT,
d: Math.abs(topSectionHeight.value - MIN_SECTION_HEIGHT),
},
{
h: DEFAULT_TOP_SECTION_HEIGHT,
d: Math.abs(topSectionHeight.value - DEFAULT_TOP_SECTION_HEIGHT),
},
{
h: MAX_TOP_SECTION_HEIGHT,
d: Math.abs(topSectionHeight.value - MAX_TOP_SECTION_HEIGHT),
},
];
targetHeight = distances.sort((a, b) => a.d - b.d)[0].h;
}
topSectionHeight.value = withSpring(targetHeight, ANIMATION_CONFIG);
});
Subtle handle scaling during drag:
const dragHandleAnimatedStyle = useAnimatedStyle(() => ({
transform: [
{
scale: withSpring(isDragging.value ? 1.2 : 1, ANIMATION_CONFIG),
},
],
}));
const dragHandleContainerAnimatedStyle = useAnimatedStyle(() => ({
top: topSectionHeight.value - DRAG_HANDLE_HEIGHT / 2,
}));
{/* Drag Handle - Positioned at boundary */}
<GestureDetector gesture={panGesture}>
<Animated.View
style={[
styles.dragHandleContainer,
dragHandleContainerAnimatedStyle,
]}
>
<Animated.View
style={[styles.dragHandle, dragHandleAnimatedStyle]}
/>
</Animated.View>
</GestureDetector>
📂 Code & GitHub Repository:
Check out the full source code on GitHub for hands-on exploration:
Repo: Interactive-Resizable-Split-View
📄 Key Dependencies:
-
react-native-reanimated
: For shared values and animations. -
react-native-gesture-handler
: For pan gesture handling. -
react-native-safe-area-context
: For safe area handling.
🎯 Conclusion:
A resizable split view with smooth, responsive animations enhances your app’s interactivity and polish. Utilizing shared values improves performance, while snap points create predictable, user-friendly behavior. Carefully crafted micro-animations add meaningful feedback, transforming a static UI into one that truly feels alive.
Polishing the core layout and motion together delivers a compelling mobile experience that users appreciate.
Top comments (1)
Demo Video:
miro.medium.com/v2/resize:fit:640/...