neumorphic_flutter_kr

License: MIT Flutter Dart Platforms

요약

간단한 UI의 대표 중 하나인 뉴로모픽 디자인을 한글 API로 쉽게 쓰는 Flutter 위젯/샘플입니다. 조정하시고, 코드를 긁어가시고, 인사이트도 얻어가세요!


뱃지란? README 상단에 표시되는 작은 상태 라벨로, 라이선스/버전/지원 플랫폼 등을 한눈에 보여줍니다. 위 배지는 라이선스(MIT), 요구 Flutter/Dart 버전, 지원 플랫폼 정보를 담고 있어요.

특징

  • 한글 친화·직관 API: depth(깊이), lightSource(광원), radius(곡률), color(배경색)
  • 최소 설정으로 자연스러운 양·음영 박스 효과(눌림/돌출)
  • 경량 의존성, 웹/모바일/데스크톱 모두 지원
  • 예제 앱에서 실시간 조정 + 코드 자동 생성/복사 제공
  • 다크모드 토글·텍스트 스케일(접근성) 조절 지원

설치

pub.flutter-io.cn 등록 전에는 로컬 path 의존성으로 사용하세요.

dependencies:
	neumorphic_flutter_kr:
		path: ../neumorphic_flutter_kr

빠른 시작

import 'package:flutter/material.dart';
import 'package:neumorphic_flutter_kr/neumorphic_flutter_kr.dart';

class Demo extends StatelessWidget {
	const Demo({super.key});
	@override
	Widget build(BuildContext context) {
		return const Center(
			child: NeumorphicBox(
				padding: EdgeInsets.all(24),
				child: Text('눌러보세요'),
			),
		);
	}
}

복붙용: 단일 파일 데모(자급자족)

아래 코드는 새 Flutter 앱의 lib/main.dart에 그대로 붙여 넣어도 동작합니다. 추가 파일이 필요 없습니다.

import 'package:flutter/material.dart';
import 'package:neumorphic_flutter_kr/neumorphic_flutter_kr.dart';

void main() => runApp(const MyApp());

class MyApp extends StatefulWidget {
	const MyApp({super.key});
	@override
	State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
	ThemeMode _mode = ThemeMode.light;
	@override
	Widget build(BuildContext context) {
		return MaterialApp(
			debugShowCheckedModeBanner: false,
			title: 'Neumorphic Lite Demo',
			theme: ThemeData(useMaterial3: true, brightness: Brightness.light),
			darkTheme: ThemeData(useMaterial3: true, brightness: Brightness.dark),
			themeMode: _mode,
			home: DemoPage(
				themeMode: _mode,
				onThemeModeChanged: (m) => setState(() => _mode = m),
			),
		);
	}
}

class DemoPage extends StatefulWidget {
	final ThemeMode themeMode;
	final ValueChanged<ThemeMode> onThemeModeChanged;
	const DemoPage({super.key, required this.themeMode, required this.onThemeModeChanged});
	@override
	State<DemoPage> createState() => _DemoPageState();
}

class _DemoPageState extends State<DemoPage> {
	double _depth = 8;
	double _radius = 12;
	bool _pressed = false;
	Color _bg = NeumorphicTheme.defaultBackground;
	Alignment _light = NeumorphicTheme.defaultLightSource;

	@override
	Widget build(BuildContext context) {
		return Scaffold(
			appBar: AppBar(
				title: const Text('Neumorphic Lite'),
				actions: [
					const Icon(Icons.dark_mode, size: 18),
					Switch(
						value: widget.themeMode == ThemeMode.dark,
						onChanged: (v) => widget.onThemeModeChanged(v ? ThemeMode.dark : ThemeMode.light),
					),
				],
			),
			body: Padding(
				padding: const EdgeInsets.all(16),
				child: Column(
					crossAxisAlignment: CrossAxisAlignment.stretch,
					children: [
						// Preview
						Expanded(
							child: Center(
								child: GestureDetector(
									onTapDown: (_) => setState(() => _pressed = true),
									onTapUp: (_) => setState(() => _pressed = false),
									onTapCancel: () => setState(() => _pressed = false),
									child: NeumorphicBox(
										depth: _pressed ? -_depth.abs() : _depth,
										radius: BorderRadius.all(Radius.circular(_radius)),
										color: _bg,
										lightSource: _light,
										padding: const EdgeInsets.all(40),
										child: Icon(_pressed ? Icons.touch_app : Icons.pan_tool, size: 60, color: Colors.blueGrey[700]),
									),
								),
							),
						),
						const SizedBox(height: 16),
						// Controls
						Text('깊이(depth): ${_pressed ? -_depth.abs() : _depth}'),
						Slider(
							value: _depth,
							min: -20,
							max: 20,
							divisions: 40,
							label: _depth.round().toString(),
							onChanged: (v) => setState(() => _depth = v),
						),
						Text('곡률(radius): ${_radius.round()}px'),
						Slider(
							value: _radius,
							min: 0,
							max: 40,
							divisions: 40,
							label: _radius.round().toString(),
							onChanged: (v) => setState(() => _radius = v),
						),
						Row(
							children: [
								const Text('광원: '),
								DropdownButton<Alignment>(
									value: _light,
									items: const [
										DropdownMenuItem(value: Alignment.topLeft, child: Text('좌상')),
										DropdownMenuItem(value: Alignment.topCenter, child: Text('상단')),
										DropdownMenuItem(value: Alignment.topRight, child: Text('우상')),
										DropdownMenuItem(value: Alignment.centerLeft, child: Text('좌측')),
										DropdownMenuItem(value: Alignment.center, child: Text('중앙')),
										DropdownMenuItem(value: Alignment.centerRight, child: Text('우측')),
										DropdownMenuItem(value: Alignment.bottomLeft, child: Text('좌하')),
										DropdownMenuItem(value: Alignment.bottomCenter, child: Text('하단')),
										DropdownMenuItem(value: Alignment.bottomRight, child: Text('우하')),
									],
									onChanged: (a) => setState(() => _light = a ?? _light),
								),
								const SizedBox(width: 12),
								ElevatedButton(
									onPressed: () => setState(() => _bg = _bg == NeumorphicTheme.defaultBackground
											? const Color(0xFFE3F2FD)
											: NeumorphicTheme.defaultBackground),
									child: const Text('배경색 토글'),
								),
							],
						),
					],
				),
			),
			backgroundColor: _bg,
		);
	}
}

주의: 위 “단일 파일 데모”는 빠른 복붙용 간이 버전입니다. 전체 기능(코드 생성/샘플 갤러리 등)이 있는 풀 인터랙티브 데모는 이 저장소의 example/에서 실행하세요.

인터랙티브 데모 실행

실시간으로 깊이/곡률/광원/배경색을 조정하고, 생성된 코드를 복사할 수 있습니다.

cd example
flutter run -d chrome   # 웹(크롬)
# 또는
flutter run -d ios      # iOS 시뮬레이터
flutter run -d android  # Android 에뮬레이터

시각 샘플(골든 테스트 이미지)

돌출(emboss)

Neumorphic emboss

눌림(pressed)

Neumorphic pressed

링크