
그냥 한줄의 Inline 텍스트를 작성하는 것은 무지 쉽지만, 아래와 텍스트 사이에 링크를 집어넣어야 하는 경우들이 있다. 웹에서는 간단하게 <a> 태그로 감쌀 수 있었는데, Flutter 에서는 어떻게 처리할 수 있을지 궁금했다.

기본적으로 Flutter 에서는 다양한 스타일이 추가된 텍스트를 사용하고 싶을 때를 위해 RichText 위젯을 제공한다.
아래와 같은 예시처럼 가운데 글자만 Bold 처리를 원할 경우, 유용하게 쓰이는 위젯이다.

이 위젯을 사용해서 위 구글세팅 이미지 혹은 아래 회원가입 처럼 글자 사이에 링크를 걸어보자.

위 예제대로 간단하게 구현해보자
기본 텍스트 생성 방식
Column(
  children: const [
    Text('Don\'t have an account? Sign Up'),
  ],
)
	 
RichText 의 경우, 텍스트는 TextSpan 개체의 트리를 사용하며, 각 TextSpan 에는 children 이 존재해서 그 밑으로 계속해서 TextSpan 을 지정이 가능하다.
기본 스타일이 있기에 비교를 위해서 스타일 지정
Column(
  children: [
    const Text('Don\'t have an account? Sign Up'),
    RichText(
      text: const TextSpan(
        text: 'Don\'t have an account? ',
        style: TextStyle(
          fontSize: 15,
          color: Colors.black,
        ),
      ),
    ),
  ],
)
	 
이제 TextSpan 으로 이루어진 children 을 TextSpan 아래에 넣어주면 된다.
RichText(
  text: const TextSpan(
    text: 'Don\'t have an account? ',
    style: TextStyle(
      fontSize: 15,
      color: Colors.black,
    ),
    children: <TextSpan>[
      TextSpan(
        text: 'Sign Up',
        style: TextStyle(
          color: Colors.blueAccent,
          fontSize: 15,
        ),
      ),
    ],
  ),
)아래와 같이 recognizer 로 사용자 탭하였을 때 어떤 이벤트를 실행시킬지 정할 수 있다.
TextSpan(
  text: 'Sign Up',
  style: const TextStyle(
    color: Colors.blueAccent,
    fontSize: 15,
  ),
  recognizer: TapGestureRecognizer()..onTap = () {
    // onTap Event
  },
)
	 
단순히 한줄의 안내에서만 사용한다면 문제 없겠지만, 한줄에 2개 이상의 링크가 포함이 되어야 한다고 하면 코드가 복잡해질 것이다. 이것을 LinkText 라 하여 모듈화해보자.

import 'package:flutter/src/widgets/framework.dart';
import 'package:flutter/src/widgets/placeholder.dart';
class LinkText extends StatelessWidget {
  const LinkText({super.key});
  
  Widget build(BuildContext context) {
    return const Placeholder();
  }
}받아야 할 children 의 타입을 지정해주자.
class LinkTextItem {
  String text;  /// 보여질 텍스트
  bool isLink;  /// 링크 유무
  Function()? onTap;  /// 링크 클릭 시 이벤트
  LinkTextItem({
    required this.text,
    this.isLink = false,
    this.onTap,
  });
}위에서 지정한 타입의 children 들을 배열로 받는다.
const LinkText({
  super.key,
  required this.items,
});
final List<LinkTextItem> items;일반 텍스트와 링크의 스타일을 build 안에 지정
Widget build(BuildContext context) {
  TextStyle mainTextStyle = const TextStyle(
    color: Colors.black,
    fontSize: 14,
    fontWeight: FontWeight.w500,
  );
  TextStyle linkTextStyle = mainTextStyle.copyWith(
    color: Theme.of(context).primaryColor,
    fontWeight: FontWeight.w600,
  );
  ...위에서 구현한 RichText 를 Row 로 감싼다.
return Row(
  mainAxisAlignment: MainAxisAlignment.center,
  children: [
    RichText(
      text: TextSpan(
        text: 'Don\'t have an account? ',
        style: const TextStyle(
          fontSize: 15,
          color: Colors.black,
        ),
        children: <TextSpan>[
          TextSpan(
            text: 'Sign Up',
            style: const TextStyle(
              color: Colors.blueAccent,
              fontSize: 15,
            ),
            recognizer: TapGestureRecognizer()
              ..onTap = () {
                // 선택처리
              },
          ),
        ],
      ),
    ),
  ],
);다시 메인으로 와서 구현한 RichText 아래에 LinkText 를 불러본다.
Column(
  crossAxisAlignment: CrossAxisAlignment.start,
  children: [
  	...,
    const SizedBox(
      height: 30,
    ),
    const LinkText(
      items: [],
    ),
  ],
),
	 
위에 Don't have an account? 에서 an 을 링크로 걸어보자.
build 아래에 아래와 같이 함수를 정의해준다.
링크유무에 따라 다르게 TextSpan 을 처리해서 return 해준다.
TextSpan getTextSpan(LinkTextItem item) {
  if (item.isLink) {
    return TextSpan(
      text: item.text,
      style: linkTextStyle,
      recognizer: TapGestureRecognizer()..onTap = item.onTap,
    );
  } else {
    return TextSpan(
      text: item.text,
      style: mainTextStyle,
    );
  }
}위 함수를 사용하기 전에 RichText 를 아래와 같이 비워준다.
return Row(
  mainAxisAlignment: MainAxisAlignment.center,
  children: [
    RichText(
      text: const TextSpan(
        children: <TextSpan>[
          /// getTextSpan
        ],
      ),
    ),
  ],
);메인페이지 build 아래에 코드처럼 데이터를 설정해준다.
얼마든지 링크를 걸고 싶은 텍스트에 설정이 가능하다.
Widget build(BuildContext context) {
  List<LinkTextItem> linkTextItems = [
    LinkTextItem(text: 'Don\'t have '),
    LinkTextItem(
      text: 'an',
      isLink: true,
      onTap: () => print('an'),
    ),
    LinkTextItem(text: ' account? '),
    LinkTextItem(
      text: 'Sign Up',
      isLink: true,
      onTap: () => print('Sign Up'),
    ),
  ];이제 아래에 import 해두었던 LinkText 에 값을 적용시켜주면 끝이다.
const SizedBox(
  height: 30,
),
LinkText(
  items: linkTextItems,
),
	 
전체 소스코드
GitHub