💙

斬新なLoginUIを作成する

2024/11/03に公開

💡Tips

海外のサイトを見ていると斬新なログイン画面を見かける。ニューホリックデザインなるものは今は流行っていないらしいので、グニャッて曲がったデザインを再現してみた。

グラデーションに変更するとおしゃれなアプリのUIになりましたね🥰

import 'package:flutter/material.dart';

class LoginScreen extends StatelessWidget {
  const LoginScreen({super.key});

  
  Widget build(BuildContext context) {
    final size = MediaQuery.of(context).size;
    final theme = Theme.of(context);

    return Scaffold(
      body: GestureDetector(
        onTap: () => FocusScope.of(context).unfocus(),
        child: Stack(
          children: [
            // 更新された背景の波形グラデーション
            Positioned(
              child: ClipPath(
                clipper: _WaveClipper(),
                child: Container(
                  height: size.height * 0.5,
                  width: size.width,
                  decoration: const BoxDecoration(
                    gradient: LinearGradient(
                      begin: Alignment.topLeft,
                      end: Alignment.bottomRight,
                      colors: [
                        Color(0xFF2196F3),  // 青
                        Color(0xFF9C27B0),  // 紫
                        Color(0xFFE91E63),  // ピンク
                        Color(0xFFFF9800),  // オレンジ
                        Color(0xFFFFEB3B),  // 黄色
                      ],
                      stops: [0.0, 0.25, 0.5, 0.75, 1.0],
                      transform: GradientRotation(45 * 3.14 / 180),
                    ),
                  ),
                ),
              ),
            ),
            // メインコンテンツ
            ListView(
              padding: EdgeInsets.zero,
              children: [
                SizedBox(height: size.height * 0.45),
                Padding(
                  padding: EdgeInsets.only(left: size.width * 0.15),
                  child: Text(
                    'Log In',
                    style: theme.textTheme.displaySmall?.copyWith(
                      color: Colors.grey[800],
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                ),
                const SizedBox(height: 40),
                Padding(
                  padding: const EdgeInsets.all(20.0),
                  child: _buildLoginForm(context),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildLoginForm(BuildContext context) {
    final size = MediaQuery.of(context).size;

    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        // Emailフィールド
        const Text(
          'Email',
          style: TextStyle(
            color: Color(0xFF2196F3),  // 青色に変更
            fontSize: 14,
            fontWeight: FontWeight.w600,
          ),
        ),
        TextField(
          decoration: InputDecoration(
            hintText: 'Enter your email address',
            hintStyle: TextStyle(color: Colors.grey[400]),
            enabledBorder: UnderlineInputBorder(
              borderSide: BorderSide(color: Colors.blue[100]!),
            ),
            focusedBorder: const UnderlineInputBorder(
              borderSide: BorderSide(color: Color(0xFF2196F3)),
            ),
          ),
        ),
        const SizedBox(height: 20),

        // Passwordフィールド
        const Text(
          'Password',
          style: TextStyle(
            color: Color(0xFF2196F3),  // 青色に変更
            fontSize: 14,
            fontWeight: FontWeight.w600,
          ),
        ),
        TextField(
          obscureText: true,
          decoration: InputDecoration(
            hintText: 'Enter your password',
            hintStyle: TextStyle(color: Colors.grey[400]),
            enabledBorder: UnderlineInputBorder(
              borderSide: BorderSide(color: Colors.blue[100]!),
            ),
            focusedBorder: const UnderlineInputBorder(
              borderSide: BorderSide(color: Color(0xFF2196F3)),
            ),
          ),
        ),
        const SizedBox(height: 30),

        // ログインボタン
        Center(
          child: SizedBox(
            width: size.width * 0.6,
            child: ElevatedButton(
              onPressed: () {},
              style: ElevatedButton.styleFrom(
                backgroundColor: const Color(0xFF2196F3),  // 青色に変更
                padding: const EdgeInsets.symmetric(vertical: 15),
                shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.circular(30),
                ),
                elevation: 3,  // 影を追加
              ),
              child: const Text(
                'LOG IN',
                style: TextStyle(
                  color: Colors.white,
                  fontSize: 16,
                  fontWeight: FontWeight.w700,
                ),
              ),
            ),
          ),
        ),
        const SizedBox(height: 40),

        // サインアップリンク
        Center(
          child: RichText(
            text: const TextSpan(
              children: [
                TextSpan(
                  text: "Don't have an account? ",
                  style: TextStyle(
                    color: Color(0xFF757575),
                    fontSize: 16,
                  ),
                ),
                TextSpan(
                  text: 'Sign Up',
                  style: TextStyle(
                    color: Color(0xFF2196F3),  // 青色に変更
                    fontSize: 16,
                    fontWeight: FontWeight.bold,
                  ),
                ),
              ],
            ),
          ),
        ),
        const SizedBox(height: 16),
      ],
    );
  }
}

// 波形のカスタムクリッパー
class _WaveClipper extends CustomClipper<Path> {
  
  Path getClip(Size size) {
    final path = Path();
    path.lineTo(0, size.height * 0.75);
    
    final firstControlPoint = Offset(size.width * 0.25, size.height);
    final firstEndPoint = Offset(size.width * 0.5, size.height * 0.75);
    path.quadraticBezierTo(
      firstControlPoint.dx,
      firstControlPoint.dy,
      firstEndPoint.dx,
      firstEndPoint.dy,
    );
    
    final secondControlPoint = Offset(size.width * 0.75, size.height * 0.5);
    final secondEndPoint = Offset(size.width, size.height * 0.75);
    path.quadraticBezierTo(
      secondControlPoint.dx,
      secondControlPoint.dy,
      secondEndPoint.dx,
      secondEndPoint.dy,
    );
    
    path.lineTo(size.width, 0);
    path.close();
    return path;
  }

  
  bool shouldReclip(CustomClipper<Path> oldClipper) => false;
}

インポートして実行する。

main.dart
import 'package:flutter/material.dart';
import 'package:widget_cookbook/login_screen/login_screen.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const LoginScreen(),
    );
  }
}

最後に

斬新なUIを再現するには、カスタムペインターが必要なように思えました。まれにすごく凝ったデザインを見かけるのですが、あまりこだりすぎるとアプリのパフォーマンスが落ちているような気がするものがあったので程々にしないといけないのかな。。。

斬新なUIを探求してみたいと思う日でした。

Discussion