1.  Todo 기능 개발

등록 작업의 경우 TodoMapper ▶️ TodoService ▶️ TodoController ▶️ JSP의 순서로 처리


1) TodoMapper 개발 및 테스트

 

TodoMapper에는 TodoVO를 파라미터로 받는 insert()를 추가
public interface TodoMapper {

    String getTime();

    void insert(TodoVO todoVO);
    
}

 

TodoMapper.xml에 insert를 다음과 같이 구현
<insert id="insert">
        INSERT INTO tbl_todo (title, dueDate, writer) VALUES (#{title}, #{dueDate}, #{writer})
</insert>

 

Mybatis를 이용하면 "?" 대신에 "#{title}" 같이 파라미터를 처리, "#{title}" 부분은 PreparedStatement로 변경되면서 "?"로 처리되고, 주어진 객체의 getTitle()을 호출한 결과를 적용

 

테스트 코드를 이용해서 TodoVO의 입력을 확인
 @Test
    public void testInsert() {
        TodoVO todoVO = TodoVO.builder() // 빌더를 이용해서 TodoVO 객체를 생성
                .title("스프링 테스트")
                .dueDate(LocalDate.of(2022, 10, 10))
                .writer("user00")
                .build();
        todoMapper.insert(todoVO);
    }

 

  테스트 실행 후에 tbl_todo 테이블을 조회해서 insert가 완료되었는지 확인

 


2) TodoService와 TodoServiceImpl 클래스


TodoMapper와 TodoController 사이에는 서비스 계층을 설계해서 적용
TodoService 인터페이스를 먼저 추가하고, 이를 구현한 TodoServiceImpl을 스프링 빈으로 처리

public interface TodoService {
    void register(TodoDTO todoDTO);
}

 

  - TodoService 인터페이스에 추가한 register()는 여러 개의 파라미터 대신에 TodoDTO로 묶어서 전달 받도록 구성

 

 

TodoService 인터페이스를 구현하는 TodoServiceImpl에는 의존성 주입을 이용해서 데이터베이스 처리를 하는 TodoMapper와 DTO, VO의 변환을 처리하는 ModelMapper를 주입

 

    ⚡️  의존성 주입이 사용되는 방식은 의존성 주입이 필요한 객체의 타입을 final로 고정하고 @RequiredArgsConstructor를 이용해서 생성자를 생성하는 방식을 사용

    ⚡️  register() 에서는 주입된 ModelMapper를 이용해서 TodoDTO를 TodoVO로 변환하고 이를 TodoMapper를 통해서 insert 처리

@Log4j2
@Service
@RequiredArgsConstructor // 생성자 객체 주입. private final로 선언된 참조변수에 객체를 저장하는 생성자 작성.
public class TodoServiceImpl implements TodoService{
    private final TodoMapper todoMapper;
    private final ModelMapper modelMapper;

    @Override
    public void register(TodoDTO todoDTO) {
        // 1) todoDTO를 전달 받아 2) todoDTO를 todoVO로 변환 후 3) dao의 insert()호출
        log.info(todoDTO);
        TodoVO todoVO = modelMapper.map(todoDTO, TodoVO.class);
        log.info(todoVO);
        todoMapper.insert(todoVO);
    }
}

 

service 패키지는 root-context.xml에서 component-scan 패키지로 추가

<context:component-scan base-package="com.example.spring_ex_01_2404.service"/>

 


3)  TodoService 테스트


서비스 계층에서 DTO를 VO로 변환하는 작업을 처리하기 때문에 가능하면 테스트를 진행해서 문제가 없는지 확인하는 것이 좋음
test 폴더내에 service 관련 패키지를 생성하고 TodoServiceTest 클래스를 작성

@Log4j2
@ExtendWith(SpringExtension.class)
@ContextConfiguration(locations = "file:src/main/webapp/WEB-INF/root-context.xml")
class TodoServiceTests {
    @Autowired
    private TodoService todoService;

    @Test
    public void testRegister() {
        TodoDTO todoDTO = TodoDTO.builder()
                .title("test...")
                .dueDate(LocalDate.now())
                .writer("user1")
                .build();
        todoService.register(todoDTO);
    }
}


4)  TodoController의 GET / POST 처리


서비스 계층까지 문제 없이 동작하는 것을 확인했다면 스프링 MVC를 처리
입력할 수 있는 화면을 위해 controller 패키지의 TodoController를 확인

 

TodoController에 GET 방식으로 '/todo/register'가 가능한지 확인
@Log4j2
@Controller
@RequestMapping("/todo")
@RequiredArgsConstructor
public class TodoController {

    @GetMapping("/register")
    public void register() {
        log.info("todo register...");
    }
}

 

/WEB-INF/view/todo/ 폴더에 register.jsp는 test.html을 복사해서 구성. 상단에 JSP 관련 설정을 추가.
register.jsp에 class 속성이 "card-body"로 지정된 부분의 코드를 수정
입력하는 화면의 디자인은 https://getbootstrap.com/docs/5.1/forms/form-control/

 

Form controls

Give textual form controls like <input>s and <textarea>s an upgrade with custom styles, sizing, focus states, and more.

getbootstrap.com

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<div class="card-body">
    <form  method="post">
        <div class="input-group mb-3">
            <span class="input-group-text">Title</span>
            <input type="text" name="title" class="form-control" placeholder="Title">
        </div>
        <div class="input-group mb-3">
            <span class="input-group-text">DueDate</span>
            <input type="date" name="dueDate" class="form-control">
        </div>
        <div class="input-group mb-3">
            <span class="input-group-text">Writer</span>
            <input type="text" name="writer" class="form-control" placeholder="Writer">
        </div>
        <div class="my-4">
            <div class="float-end">
                <button type="submit" class="btn btn-primary">Submit</button>
                <button type="reset" class="btn btn-secondary">Reset</button>
            </div>
        </div>
    </form>
</div>

 

 POST 방식의 처리


    1.  register.jsp의 <form method="post"> 태그에 의해서 submit 버튼을 클릭하면 POST 방식으로 "title, dueDate, writer"를 전송하게 됨
    2.  TodoController에서는 TodoDTO로 바로 전달된 파라미터의 값들을 수집
    3.  POST 방식으로 처리된 후에는 "/register/list"로 이동해야 하므로 "redirect:/todo/list"로 이동할 수 있도록 문자열을 반환

@PostMapping("/register")
public String registerPOST(TodoDTO todoDTO) {
    log.info("POST todo register");
    log.info(todoDTO);
    return "redirect:/todo/list";
}

 

  ✓  한글 문제가 있기는 하지만 브라우저에서 입력한 데이터들이 수집되고 /todo/list로 이동하는 기능에 문제가 없는 것을 확인

 


2.  한글 처리를 위한 필터 설정

서버의 한글 처리에 대한 설정은 스프링 MVC에서 제공하는 필터로 쉽게 처리할 수 있음

 

web.xml에 필터에 대한 설정을 추가
    <filter>
        <filter-name>encoding</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>encoding</filter-name>
        <servlet-name>appServlet</servlet-name>
    </filter-mapping>

 

  ✓  web.xml의 설정을 서버를 재시작해야 올바르게 반영되므로 톰캣을 재시작하고 한글 처리를 확인


3.  @Valid를 이용한 서버사이드 유효성 검증

유효성 검증


과거의 웹 개발에는 자바 스크립트를 이용하여 브라우저에서만 유효성 검사를 진행하는 방식이 많았지만, 모바일과 같이 다양한 환경에서 서버를 이용하는 현재에는 브라우저를 사용하는 프론트쪽에서의 검증과 더불어 서버에서도 입력되는 값들을 검증하는 것이 좋음. 이러한 검증 작업은 컨트롤러에서 진행하는데 스프링MVC의 경우 @Valid와 BindingResult라는 존재를 이용해서 간단하게 처리할 수 있음


  ⚡️  스프링 MVC에서 검증을 처리하기 위해서는 hibernate-validate 라이브러리가 필요 (build.gradle 에 추가)

// https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator
implementation 'org.hibernate.validator:hibernate-validator:6.2.1.Final'

 

hibernate-validate를 이용해서 사용하는 대표적인 어노테이션
어노테이션 설명 어노테이션 설명
@NotNull Null 불가 @Null Null만 입력 가능
@NotEmpty Null 빈 문자열 불가 @NotBlank Null 빈 문자열, 스페이스만 있는 문자열 불가
@SIze(min=, max=) 문자열, 배열 등의 크기가
만족하는가
@Pattern(regex=) 정규식을 만족하는가
@Max(num) 지정 값 이하인가 @Min(num) 지정 값 이상인가
@Future 현재 보다 미래인가 @Past 현재 보다 과거인가
@Positive 양수만 가능 @PositiveOrZero 양수와 0만 가능
@Negative  음수만 가능 @NegativeOrZero 음수와 0만 가능

 

 

1) TodoDTO 검증하기

 

TodoDTO에 간단한 어노테이션 적용
@ToString
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class TodoDTO {
    private Long tno;
    
    @NotEmpty
    private String title;
    
    @Future
    private LocalDate dueDate;
    
    private boolean finished;
    
    @NotEmpty
    private String writer;
}

 

 

TodoController에서 POST 방식으로 처리할 때 이를 반영하도록 BindingResult와 @Valid 어노테이션을 적용
 @PostMapping("/register")
    public String registerPOST(@Valid TodoDTO todoDTO, BindingResult bindingResult, RedirectAttributes redirectAttributes) {
        log.info("POST todo register");

        if (bindingResult.hasErrors()) {
            log.info("has error...");
            redirectAttributes.addFlashAttribute("errors", bindingResult.getAllErrors());
            return "redirect:/todo/register";
        }

        log.info(todoDTO);
        return "redirect:/todo/list";
    }

 

  ✓  TodoDTO 에는 @Valid를 적용하고, BindingResult 타입을 파라미터로 새롭게 추가

    ➡️  BindingResult는  스프링이 제공하는 검증 오류를 보관하는 객체

  ✓  registerPOST() 에서는 hasErrors()을 이용해서 검증에 문제가 있다면 다시 입력화면으로 리다이렉트되도록 처리


  ✓  처리 과정에서 잘못된 결과는 RedirectAttributes의 addFlashAttribute()를 이용해서 전달

    ➡️ flash 속성에 객체를 저장, 요청 매개 변수(requestparameters)로 값을 전달하지않고 객체로 값을 그대로 전달. 일회성으로 한번 사용하면 Redirect후 값이 소멸


  ✓  TodoDTO의 writer는 @NotEmpty가 적용되어 있으므로 항목을 입력하지 않고 submit을 하면 다시 입력화면으로 돌아감

 

 

 

 

 


 

2) JSP에서 검증 에러 메시지 확인하기

 

register.jsp에 검증된 결과를 확인하기 위해 상단에 태그 라이브러리 추가
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

 

자바스크립트 객체를 생성해 둔 후 필요할 때 화면에서 처리
<form>태그가 끝난 후에 <script> 태그를 추가
<script>
    const serverValidResult = {};
    <c:forEach items="${errors}" var="error">
    serverValidResult['${error.getField()}'] = '${error.defaultMessage}';
    </c:forEach>
    console.log(serverValidResult);
</script>

 

  ✓  자바스크립트를 이용해서 오류 객체를 생성하면 나중에 화면에서 자유롭게 처리할 수 있다는 장점이 있음

  ✓  아무것도 입력하지 않은 상태에서 submit을 하면 다음과 같은 코드가 출력

아무 것도 입력하지 않았을 때와 과거 날짜를 입력했을 때 콘솔 창 출력 결과

 


4.  Todo 등록 기능 완성

입력값의 검증까지 끝났다면 최종적으로 TodoService를 주입하고, 연동하도록 구성

 

TodoController의 클래스 선언부에서 TodoService를 주입
@Log4j2
@Controller
@RequestMapping("/todo")
@RequiredArgsConstructor
public class TodoController {
    private final TodoService todoService;

 

registerPOST()에서는 TodoService의 기능을 호출하도록 구성
 @PostMapping("/register")
    public String registerPOST(@Valid TodoDTO todoDTO, BindingResult bindingResult, RedirectAttributes redirectAttributes) {
        log.info("POST todo register");

        if (bindingResult.hasErrors()) {
            log.info("has error...");
            redirectAttributes.addFlashAttribute("errors", bindingResult.getAllErrors());
            return "redirect:/todo/register";
        }

        log.info(todoDTO);
        todoService.register(todoDTO); // 호출 코드 생성
        return "redirect:/todo/list";
    }

 

 

 

 

모든 기능의 개발이 완료 되었다면 등록 후에 "/todo/list"로 이동하게 됨. 아직 "/todo/list"의 개발은 완료되지 않았으니 데이터베이스를 이용해서 최종 확인

 

 

 

 

 

 

 

 

 

 

 

 

 

 

[ 내용 참고 : IT 학원 강의 ]


1.  프로젝트의 구현 목표와 준비

  • 검색과 필터링을 적용할 수 있는 화면을 구성하고 MyBatis의 동적 쿼리를 이용해서 상황에 맞는 Todo들을 검색
  • 새로운 Todo를 등록할 때 문자열, boolean, LocalDate를 자동으로 처리
  • 목록에서 조회 화면으로 이동할 때 모든 검색, 필터링, 페이징 조건을 유지하도록 구성
  • 조회 화면에서는 모든 조건을 유지한 채로 수정 / 삭제 화면으로 이동하도록 구성
  • 삭제 시에는 다시 목록 화면으로 이동
  • 수정 시에는 다시 조회 화면으로 이동하지만, 검색, 필터링, 페이징 조건은 초기화

(1) 프로젝트의 준비

build.gradle 설정 코드
    // 1. 스프링 관련
    // https://mvnrepository.com/artifact/org.springframework/spring-core
    implementation 'org.springframework:spring-core:5.3.30'
    implementation 'org.springframework:spring-context:5.3.30'
    implementation 'org.springframework:spring-test:5.3.30'
    implementation 'org.springframework:spring-webmvc:5.3.30'

    implementation 'org.springframework:spring-jdbc:5.3.30'
    implementation 'org.springframework:spring-tx:5.3.30'

 
    // 2. JSTL   
    // https://mvnrepository.com/artifact/javax.servlet/jstl
    implementation 'javax.servlet:jstl:1.2'


    // 3. MyBatis / MySQL / HikariCP 관련
    // https://mvnrepository.com/artifact/mysql/mysql-connector-java
    implementation 'mysql:mysql-connector-java:8.0.33'
    // https://mvnrepository.com/artifact/com.zaxxer/HikariCP
    implementation 'com.zaxxer:HikariCP:5.0.1'
    // https://mvnrepository.com/artifact/org.mybatis/mybatis
    implementation 'org.mybatis:mybatis:3.5.9'
    // https://mvnrepository.com/artifact/org.mybatis/mybatis-spring
    implementation 'org.mybatis:mybatis-spring:2.0.7'


    // 4. DTO와 VO의 변환을 위한 ModelMapper
    // https://mvnrepository.com/artifact/org.modelmapper/modelmapper
    implementation 'org.modelmapper:modelmapper:3.0.0'


    // 5. DTO 검증을 위한 validate 관련 라이브러리
    // https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator
    implementation 'org.hibernate.validator:hibernate-validator:6.2.1.Final'

 


 

(2) 프로젝트의 폴더 / 패키지 구조

테이블 생성
CREATE TABLE `tbl_todo` (
    `tno` int auto_increment PRIMARY KEY,
    `title` varchar(100) not null,
    `dueDate` date not null,
    `writer` varchar(50) not null,
    `finished` tinyint default 0
)

 

패키지 구조

 


 

(3) ModelMapper 설정과 @Configuration

👩🏻‍💻  프로젝트 개발에는 DTO를 VO로 변환하거나 VO를 DTO로 변환해야 하는 작업이 빈번하므로 이를 처리하기 위해서 ModelMapper를 스프링의 빈으로 등록해서 처리

  • config패키지에  ModelMapperConfig 클래스 생성
  • ModelMapperConfig는 @Configuration을 이용
  • @Configuration은 해당 클래스가 스프링 빈에 대한 설정을 하는 클래스임을 명시
@Configuration
public class ModelMapperConfig {
    @Bean
    public ModelMapper getMapper() {
        ModelMapper modelMapper = new ModelMapper();
        modelMapper.getConfiguration()
                .setFieldMatchingEnabled(true)
                .setFieldAccessLevel(org.modelmapper.config.Configuration.AccessLevel.PRIVATE)
                .setMatchingStrategy(MatchingStrategies.STRICT);
        return modelMapper;
    }
}

 

  ✓  ModelMapperConfig 클래스 내에는 getMapper()라는 메서드가 ModelMapper를 반환하도록 설계
  ✓  getMapper() 선언부에 있는 @Bean 어노테이션은 해당 메서드의 실행 결과로 반환된 객체를 스프링의 빈으로 등록시키는 역할

 

  • ModelMapperConfig를 스프링 빈으로 인식할 수 있도록 root-context.xml에 config 패키지를 component-scan을 이용해서 추가
<context:component-scan base-package="com.example.spring_ex_01_2404.config"/>

 


2.  화면 디자인 - 부트 스트랩 적용

👩🏻‍💻   JSP 파일을 작성하기 전, 프로젝트의 시작 단계에서 화면 디자인을 결정하는 것이 좋음
         ✓
화면 디자인 없이 개발을 진행할 때는 나중에 코드를 다시 입혀야 하는 작업을 할 수도 있기 때문
👩🏻‍💻 
최근에는 부트스트랩 (bootstrap)이나 머터리얼(Material Design)과 같이 쉽게 웹 화면을 디자인할 수 있는 라이브러리들 덕분에 전문적인 웹 디자이너의 도움 없이도 어느정도 완성도가 있는 디자인 작업이 가능해 짐

 

https://elements.envato.com/web-templates/site-templates

 

HTML Website Templates - Envato Elements

Browse our Collection of fully customizable HTML templates. Get Unlimited Downloads with a subscription with Envato Elements.

elements.envato.com

 

📌  webapp의 resources 폴더에 test.html을 작성해서 부트스트랩을 적용하는 페이지를 작성

 

✓  부트스트랩의 화면 구성에는 container와 row를 이용


✓  Card 컴포넌트 적용하기

    부트스트랩에는 화면을 쉽게 제작할 수 있는 여러 종류의 컴포넌트를 제공
    이중에서 Card라는 컴포넌트를 적용해서 현재의 화면에서 Content라는 영역을 변경


✓  Navbar 컴포넌트의 적용
    화면 상단에는 간단한 메뉴를 보여줄 수 있는 Nav 혹은 Navbar 컴포넌트를 적용
    공식 문서의 Navbar의 예제를 선택해서 Header 라고 출력되는 부분에 적용


✓  Footer 처리
    맨 아래 <div class="row">에는 간단한 <footer>를 적용
    해당 <div>를 맨 아래쪽으로 고정하기 위해서 fixed-bottom을 적용
    내용이 많은 경우에는 Footer 영역으로 인해 가려질 수 있는 부분이 있으므로 z-index 값은 음수로 처리해서 가려질 수 있도록 구성

 

<!doctype html>
<html lang="en">
<head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- Bootstrap CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet"
          integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">

    <title>Hello, world!</title>
</head>

<body>
<div class="container-fluid">
    <div class="row">
        <nav class="navbar navbar-expand-lg navbar-light bg-light">
            <div class="container-fluid">
                <a class="navbar-brand" href="#">Navbar</a>
                <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNavAltMarkup" aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
                    <span class="navbar-toggler-icon"></span>
                </button>
                <div class="collapse navbar-collapse" id="navbarNavAltMarkup">
                    <div class="navbar-nav">
                        <a class="nav-link active" aria-current="page" href="#">Home</a>
                        <a class="nav-link" href="#">Features</a>
                        <a class="nav-link" href="#">Pricing</a>
                        <a class="nav-link disabled">Disabled</a>
                    </div>
                </div>
            </div>
        </nav>
        
        <div class="row content">
            <div class="col">
                <div class="card">
                    <div class="card-header">
                        Featured
                    </div>
                    <div class="card-body">
                        <h5 class="card-title">Special title treatment</h5>
                        <p class="card-text">With supporting text below as a natural lead-in to additional content.</p>
                        <a href="#" class="btn btn-primary">Go somewhere</a>
                    </div>
                </div>
            </div>
        </div>
    </div>
    
    <div class="row content">
        <h1>Content</h1>
    </div>
    <div class="row footer">
        <div class="row fixed-bottom" style="z-index: -100">
            <footer class="py-1 my-1">
                <p class="tab-content text-muted">Footer</p>
            </footer>
        </div>
    </div>
</div>

<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"
        integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p"
        crossorigin="anonymous"></script>

</body>
</html>

 

 

 

[ 내용 참고 : IT 학원 강의 ]


1. 테이블 레이아웃

테이블레이아웃은 위젯을 표 형태로 배치할 때 주로 활용. 테이블레이아웃을 사용하여 행과 열의 수를 정하고 그 안에 위젯을 배치. <TableRow>가 행의 수, 열의 수는 <TableRow> 안에 포함된 위젯의 수로 결정

3행 4열의 테이블레이아웃

 


1) 테이블레이아웃의 속성

 

  • layout_span과 layout_column은 테이블레이아웃 안에 포함된 위젯에 설정하는 속성
    - layout_span은 열을 합쳐서 표시하라는 의미로, 예를 들어 layout_span="2"는 현재 셀부터 2개의 셀을 합쳐서 표시
    - layout_column은 지정된 열에 현재 위젯을 표시하라는 의미
  • stretchColumns은 <TableLayout> 자체에 설정하는 속성으로, 지정된 열의 너비를 늘이라는 의미
    - stretchColumns="*"는 각 셀을 모두 같은 크기로 확장하여 전체 화면을 꽉 차는 효과를 냄. 열번호는 0부터 시작.
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TableRow>
        <Button
            android:layout_width="60dp"
            android:text="1" />

        <Button
            android:layout_width="60dp"
            android:layout_span="2"
            android:text="2" />

        <Button
            android:layout_width="60dp"
            android:text="3" />
    </TableRow>

    <TableRow>
        <Button
            android:layout_width="60dp"
            android:layout_column="1"
            android:text="4" />

        <Button
            android:layout_width="60dp"
            android:text="5" />

        <Button
            android:layout_width="60dp"
            android:text="6" />
    </TableRow>

</TableLayout>


2.  그리드 레이아웃

테이블레이아웃과 마찬가지로 위젯을 표 형태로 배치할 때 사용하지만 좀 더 직관적
테이블레이아웃에서는 다소 어려웠던 행 확장도 간단하게 할 수 있어서 유연한 화면 구성에 적합.
  ✓  행과 열을 지정하는 방법 :  2행 3열을 지정하려면 layout_row 속성은 1로, layout_column 속성은 2로 설정

 

1)  그리드레이아웃의 속성

  • rowCount : 행의 수
  • columnCount : 열의 수
  • orientation : 그리드를 수평 방향으로 우선할 것인지, 수직 방향으로 우선할 것인지를 결정
     -  그리드레이아웃 안에 포함될 위젯에서 자주 사용되는 속성
  • layout_row : 자신이 위치할 행 번호(0번부터 시작)
  • layout_column : 자신이 위치할 열 번호(0번부터 시작)
  • layout_rowSpan : 행을 지정된 수만큼 확장
  • layout_columnSpan : 열을 지정된 수만큼 확장
  • layout_gravity : 주로 fill, fill_vertical, fill_horizontal 등으로 지정

    💡  layout_rowSpan이나 layout_columnSpan으로 셀 확장 시 위젯을 확장된 셀에 꽉 채우는 효과를 줌
<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:rowCount="2"
    android:columnCount="4">

    <Button
        android:layout_gravity="fill_vertical"
        android:layout_column="0"
        android:layout_row="0"
        android:layout_rowSpan="2"
        android:text="1" />

    <Button
        android:layout_gravity="fill_horizontal"
        android:layout_column="1"
        android:layout_columnSpan="2"
        android:layout_row="0"
        android:text="2" />

    <Button
        android:layout_column="3"
        android:layout_row="0"
        android:text="3" />

    <Button
        android:layout_column="1"
        android:layout_row="1"
        android:text="4" />

    <Button
        android:layout_column="2"
        android:layout_row="1"
        android:text="5" />

    <Button
        android:layout_column="3"
        android:layout_row="1"
        android:text="6" />

</GridLayout>


3.  프레임 레이아웃

 

 

단순히 레이아웃 안의 위젯을 왼쪽 상단부터 겹쳐서 출력
프레임레이아웃 그 자체를 사용하기 보다는 탭 위젯 등과 혼용할 때 유용

 

👩🏻‍💻  프레임레이아웃의 속성

  •  foreground : 프레임레이아웃의 전경 이미지를 지정
  •  foregroundGravity : 전경 이미지의 위치를 지정. fill, right, left, top, bottom 등의 값을 사용

 

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:foreground="@drawable/dog2"
    android:foregroundGravity="center|fill_horizontal">

    <RatingBar
        android:id="@+id/ratingBar1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="80dp"/>

    <ImageView
        android:id="@+id/imageView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/ic_launcher" />

    <CheckBox
        android:id="@+id/checkBox1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="CheckBox" />

</FrameLayout>

 

 

 

[ 내용 참고 : IT 학원 강의 ]


1.  RelativeLayout

👩🏻‍🚀  렐러티브레이아웃은 상대 레이아웃이라고도 하며, 이름처럼 레이아웃 내부에 포함된 위젯을 상대적인 위치로 배치
👩🏻‍🚀  렐러티브레이아웃 안에 포함된 위젯은 렐러티브레이아웃의 어디쯤에 위치시킬 것인지 지정해야 함
👩🏻‍🚀  렐러티브레이아웃에 있는 위젯의 위치와 관련된 속성은 크게 두 부류로 나눌 수 있음
      1) 렐러티브레이아웃의 상하좌우에 배치하는 경우  2) 다른 위젯의 상대 위치에 배치하는 경우

 

상하좌우에 배치

  • layout_alignParentLeft
  • layout_alignParentRight
  • layout_alignParentTop
  • layout_alignParentBottom
  • layout_centerHorizontal
  • layout_centerVertical
  • layout_centerInParent

  ⚡️ 렐러티브레이아웃에 있는 위젯을 부모(렐러티브레이아웃)의 어느 부분에 위치시킬지를 결정. 각 속성의 값은 true 또는 false.
  ⚡️  예를 들어 우측하단에 위젯을 배치하려면 layout_alignParentBottom 과 layout_alignParentRight 속성에 true를 설정하면 됨

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        android:text="위쪽" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_centerVertical="true"
        android:text="좌측" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="중앙" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_centerVertical="true"
        android:text="우측" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:text="아래" />

</RelativeLayout>

 

 


 

다른 위젯의 상대 위치에 배치 

 

👾  렐러티브레이아웃 안에서 다른 위젯의 특정한 곳에 배치하는 방법. 다른 위젯과 관련된 속성.

👾  각 속성의 값에는 다른 위젯의 아이디를 지정하면 되는데 '@+id/기준 위젯의 아이디'와 같은 형식으로 사용

 

 

  • 상하좌우에는 layout_above, layout_below, layout_toLeftOf, layout_toRightOf
  • 상단, 중앙, 하단  layout_alignTop, layout_alignBaseline, layout_alignBottom
  • 좌측, 우측 기준에는 layout_alignLeft, layout_alignRight

 

        <Button
            android:id="@+id/baseButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:text="기준 위젯"/>

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_above="@+id/baseButton"
            android:text="above"/>

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@+id/baseButton"
            android:text="below"/>

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_toLeftOf="@+id/baseButton"
            android:text="toLeftOf"/>

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_toRightOf="@+id/baseButton"
            android:text="toRightOf"/>


 <Button
        android:id="@+id/baseButton"
        android:layout_width="wrap_content"
        android:layout_height="400dp"
        android:layout_centerInParent="true"
        android:text="기준 위젯"/>
    <Button
        android:layout_width="100dp"
        android:layout_height="70dp"
        android:layout_alignTop="@+id/baseButton"
        android:text="alignTop"/>
    <Button
        android:layout_width="100dp"
        android:layout_height="70dp"
        android:layout_alignBaseline="@+id/baseButton"
        android:text="alignBaseline"/>
    <Button
        android:layout_width="100dp"
        android:layout_height="70dp"
        android:layout_alignBottom="@+id/baseButton"
        android:text="alignBottom"/>

 

 

 

 

[ 내용 참고 : IT 학원 강의 ]


1.  텍스트뷰

텍스트뷰는 View 클래스 바로 다음에 위치하며 다양한 위젯이 그 하위에 존재
텍스트뷰의 하위(에디트텍스트, 버튼, 체크박스 등)는 모두 텍스트뷰로부터 상속받는다.

속성 설명
text 텍스트뷰에 나타나는 문자열을 표현
'문자열'형식으로 값을 직접 입력하거나 '@string/변수이름'형식으로 설정한 후 
strings.xml 파일에 지정할 수 있음
textColor 글자의 색상을 지정
textSize 글자의 크기를 dp, px, in, mm, sp 단위로 지정
typeface 글자의 글꼴을 지정
값으로 sans, serif, monospace를 설정할 수 있고 디폴트는 normal
textStyle 글자의 스타일을 지정
값으로 bold, italic, bold|italic을 설정할 수 있고 디폴트는 normal
singleLine 글이 길어 줄이 넘어갈 경우 강제로 한 줄만 출력하고 문자열의 맨 뒤에 '...'를 표시
값으로 true와 false를 설정할 수 있고 디폴트는 false

 

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="textSize 속성"
        android:textSize="30dp" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="textColor 속성"
        android:textColor="#00FF00"
        android:textSize="30dp" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="textStyle 속성"
        android:textSize="30dp"
        android:textStyle="bold|italic" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="typeface 속성"
        android:textSize="30dp"
        android:typeface="serif" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:singleLine="true"
        android:text="singleLine 속성 singleLine 속성 singleLine 속성"
        android:textSize="30dp" />


Kotlin 코드로 XML 속성 설정 

 

activity_main.xml파일에서 지정한 XML 속성을 Kotlin 코드에서도 설정 가능.

기본적인 텍스트뷰만 만들어 놓고 id속성과 text만 설정한 XML파일.

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val textView1 = findViewById<TextView>(R.id.textView1)
        val textView2 = findViewById<TextView>(R.id.textView2)
        val textView3 = findViewById<TextView>(R.id.textView3)

        textView1.text = "안녕하세요?"
        textView1.setTextColor(Color.RED)
        textView2.textSize = 30.0f
        textView2.setTypeface(Typeface.SERIF, Typeface.BOLD_ITALIC)
        textView3.text = "가나다라마바사아자차카타파하가나다라마바사아자차카타파하가나다라마바사아자차카타파하가나다라마바사아자차카타파하"
        textView3.isSingleLine = true
    }
}

 

  • textView1.setText("안녕하세요?")  : 첫 번째 텍스트뷰의 문자열을 변경
  • textView1.setTextColor(Color.RED) : 첫 번째 텍스트뷰의 색상을 빨간색으로 지정. 색상은 android.graphics.Color 클래스에 상수로 지정
  • textView2.setTextSize(30.0f) :  두 번째 텍스트뷰에 sp 단위의 폰트 크기를 지정 (XML 속성의 textSize="30sp"와 동일한 역할)
  • textView2.setTypeface(Typeface.SERIF, Typeface.BOLD_ITALIC) : 두 번째 텍스트뷰에 XML속성의 typeface와 textStyle 속성을 동시에 지정하는 메서드 (android.graphics.Typeface 클래스에 상수로 지정)
  • textView3.setText("가나다라마바사아자차카타파하가나다라마바사아자차카타파하")
  • textView3.setSingleLine() : 세 번째 텍스트뷰에 긴 글을 대입하고 XML 속성에서 singleLine="true" 효과를 줌

💫  XML 파일에서 설정하는 내용의 대부분이 메서드로 제공되어 Kotlin 코드에서 XML 설정이 가능

 


2.  버튼과 에디트텍스트

버튼과 에디트텍스트는 사용자에게서 어떤 값을 입력받기 위한 가장 기본적인 위젯으로 활용도가 높음.
두 위젯은 View 클래스와 TextView 클래스를 상속받으므로 거의 비슷하게 사용할 수 있음.

 

1) 버튼

버튼에서는 버튼을 클릭하는 이벤트

일반적인 버튼의 XML 코드


    A. 버튼 변수 선언    val myButton :Button
    B. 변수에 버튼 위젯 대입    myButton = findViewById<Button>(R.id.button1);
    C. 버튼을 클릭할 때 동작하는 클래스 정의

myButton.setOnClickListener {
// 동작 내용을 이 부분에 코딩
}


위의 세 단계는 대부분의 위젯(라디오버튼, 이미지버튼, 체크박스, 토글버튼 등)에서 거의 동일하게 사용

 

checkbox
 <CheckBox
        android:id="@+id/android"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:checked="true"
        android:text="안드로이드폰" />

    <CheckBox
        android:id="@+id/iphone"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="아이폰" />

    <CheckBox
        android:id="@+id/window"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:checked="true"
        android:text="윈도우폰" />

 

radio button
    <RadioGroup
        android:id="@+id/rGroup1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">

        <RadioButton
            android:id="@+id/radio0"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:checked="true"
            android:text="남성" />

        <RadioButton
            android:id="@+id/radio1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="여성" />
    </RadioGroup>

 

switch & togglebutton
 <Switch
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:checked="true" />

    <Switch
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:checked="false" />

    <ToggleButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:checked="true" />

    <ToggleButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:checked="false" />



2) 에디트텍스트

에디트텍스트는 값을 입력받은 후 해당 값을 Java 코드에서 가져와 사용하는 용도로 많이 쓰임
에디트텍스트도 변수를 선언하고 이 변수에 해당 아이디 값을 넣은 후에 접근

XML 코드

 

    A. 에디트텍스트 변수 선언    var myEdit :EditText
    B. 변수에 에디트텍스트 위젯 대입    myEdit = findViewById<TextEdit>(R.id.edittext1);
    C. 에디트텍스트에 입력된 값 가져오기 ( 주로 버튼 클릭 이벤트 리스너 안에 삽입 )
         var myStr : String = myEdit.getText().toString();
          ✓ getText() 메서드는 에디트텍스트에 입력한 값을 반환. 이를 문자열로 바꾸기 위해 toString()을 사용
               반환값을 문자열로 변경할 때 가장 많이 사용하는 방식

 

 

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="아래에 이름을 입력 :"
        android:layout_margin="20dp"/>

    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="여기에 채우세요"
        android:layout_margin="20dp"/>

    <Button
        android:id="@+id/button1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="확인"
        android:layout_margin="20dp"/>

 

 

 

 

[ 내용 참고 : IT 학원 강의 ]


1.  람다식   Lambda Expression

🚀  람다식은 람다 표현식, 람다라고도 불림
🚀  람다식을 한 줄로 표현하면 '람다식은 마치 값처럼 다룰 수 있는 익명함수'

      ⚡️  람다식을 값처럼 다룰 수 있다는 말은, 람다식 자체가 함수의 인수가 되거나 반환값이 될 수 있다는 의미

    val sayHello = fun() { println("Hello world!") }
    sayHello()

 

1)  람다식 정의

    ✓  람다를 이용하여, 인수 숫자의 제곱값을 반환

    val squareNum: (Int) -> Int = { number -> number * number }
    println(squareNum(12))
  • squareNum : 람다식을 저장할 변수의 이름을 지정
  • (Int) : 람다식의 인수 자료형을 지정
  • Int : 람다식의 반환 자료형을 지정. 이 경우에는 정수를 넣고 정수를 반환.
  • number : 인수 목록을 나열. number의 자료형은 자료형에서 명시해주었으므로 형추론이 되어 number는 Int가 됨.
  • number * number : 람다식에서 실행할 코드를 지정

 

 📌  자료형은 인수목록에서 명시해주어도 됨. 앞의 코드와 동일하게 작동하는 함수

    val squareNum2 = { number: Int -> number * number }
    println(squareNum2(12))

 

📌  람다식의 인수가 한 개이면 인수를 생략하고 it으로 지칭할 수 있음

    val squareNum3: (Int) -> Int = { it * it }
    println(squareNum3(12))

 


    2)  람다를 표현하는 다양한 방법

 람다는 '값처럼' 사용할 수 있는 익명 함수. 값처럼 사용한다는 것은 함수의 인수로도 넣어줄 수 있다.

    fun invokeLambda(lambda: (Int) -> Boolean): Boolean { //람다식을 인수로 받음.
        return lambda(5)
    }
    // 이 함수는 다음과 같이 람다식을 인수로 넣어 사용할 수 있음.
    val paramLambda: (Int) -> Boolean = { num -> num == 10 }
    println(invokeLambda(paramLambda)) // 람다식의 인수로 넣은 5 != 10 이므로 -> false
    // 변수를 사용하지 않고도 바로 넣어줄 수도 있음.
    // 다음의 람다식들은 모두 똑같이 작동
    invokeLambda({ num -> num == 10 }) // 람다식 바로 넣어주기
    invokeLambda({it == 10}) // 인수가 하나일 때 it으로 변경 가능
    invokeLambda(){ it == 10 } // 만약 함수의 마지막 인수가 람다일 경우 밖으로 뺄 수 있음.
    invokeLambda{ it == 10 } // 그 외 인수가 없을 때 () 생략 가능.

 

3)  SAM(Single Abstract Method) 변환

📍  안드로이드를 개발하다 보면 다음과 같은 코드를 아주 많이 작성하게 됨

    button.setOnClickListener {
        // 버튼이 눌렸을 때 작성할 코드
    }

 

  👾  함수의 인수가 하나이고 람다식인 경우에 ()를 생략하고 {}에 코드를 작성할 수 있음
  👾  setOnClickListener() 함수는 람다식이 아닌 OnClickListener 인터페이스를 인수로 받음

    public void setOnClickListener(@Nullable OnClickListener l) {
        if (!isClickable()) {
            setClickable(true);
        }
        getListenerInfo().mOnClickListener = l;
    }


  👾  OnClickListener는 다음과 같이 추상 메서드 하나가 있는 인터페이스

public interface OnClickListener {
        /**
         * Called when a view has been clicked.
         *
         * @param v The view that was clicked.
         */
        void onClick(View v);
    }


  ⚡️  setOnClickListener 는 이와 같이 람다식이 아님에도 마치 람다식처럼 취급되고 있음
         ▶️  이것이 가능한 이유가 바로 자바 8에서 소개된 SAM 변환
             

🤓  SAM 변환은 두 가지 조건이 있다

    1. 코틀린 인터페이스가 아닌 자바 인터페이스
    2. 인터페이스 내에는 딱 한 개의 추상 메서드만 존재

    이 조건을 만족하는 경우 익명 인터페이스 객체 생성에 람다식을 사용할 수 있음.
    람다식을 사용하면 코드가 훨씬 간결해지고 가독성이 높아짐.

 

 

 

[ 내용 참고 : IT 학원 강의 ]

+ Recent posts