SPA (Single Page Application)

  • 서버에서 주는 html이 1개만 있는 어플리케이션
  • 전통적인 웹사이트 : 페이지를 이동할 때마다 정적자원들(html, css, js)을 내려줌
  • SPA : 딱 한번만 정적자원을 받아옴

라우팅

  • 브라우저 주소에 따라 다른 페이지를 보여주는 것
  • SPA에서 html은 딱 하나만 있지만 브라우저 주소창대로 다른 페이지를 보여줄 수 있음

리액트에서 라우팅 처리하기

  • react-route-dom 패키지 설치하기
yarn add react-router-dom

적용하기

1. index.js에 BrowserRouter 적용하기

import ReactDOM from 'react-dom';
import {BrowserRouter} from 'react-router-dom';

ReactDOM.render(
	<BrowserRouter>
		<App />
	</BrowserRouter>
	document.getElementById("root")
);
  • BrowserRouter : 웹 브라우저가 가지고 있는 주소 정보를 props로 넘겨줌

 

2. 세부 화면 만들기

  • Home.js
const Home = (props) => {
	return (
		<div>메인 화면</div>
	)
}

export default Home;
  • Cat.js
const Cat = (props) => {
	return (
		<div>고양이</div>
	)
}

export default Cat;
  • Dog.js
const Dog = (props) => {
	return (
		<div>강아지</div>
	)
}

export default Dog;

 

3. App.js에서 Route 적용하기

  • Route 사용 방법 1) 넘겨줄 props가 없을 때
<Route path="주소[ex. /home]" component={[보여줄 컴포넌트]}/>
  • Route 사용 방법 2) 넘겨줄 props가 있을 때
<Route path="주소" render={(props) => (<BucketList list={this.state.list}/>)} />
  • App.js에 적용하기
import {Route} from 'react-router-dom';

class App extends React.Component {
	constructor(props){
		super(props);
		this.state={};
	}

	render(){
		return (
			<div className="App">
				<Route path="/" exact component={Home}/>
				<Route path="/cat" component={Cat}/>
				<Route path="/dog" component={Dog}/>
			</div>
		);
	}
}

export default App;
  • "/"가 "/cat"와 "/dog"에 포함되어 있기 때문에 exact 추가

4. URL 파라미터 사용하기

  • 파라미터 : /cat/nabi
  • 쿼리 : /cat?name=nabi
  • App.js
<Route path="/cat/:cat_name" component={Cat}/>
  • Cat.js에서 console.log(props.match); 콘솔로 파라미터 확인 가능

5. 링크 이동시키기

1) <Link/> 사용하기

<Link to="주소">[텍스트]</Link>
  • App.js
import {Route, Link} from 'react-router-dom';

class App extends React.Component {
	constructor(props){
		super(props);
		this.state={};
	}

	render(){
		return (
			<div className="App">
				<div>
					<Link to="/">Home으로 가기</Link>
					<Link to="/cat">Cat으로 가기</Link>
					<Link to="/dog">Dog으로 가기</Link>
				</div>

				<hr/>
				<Route path="/" exact component={Home}/>
				<Route path="/cat" component={Cat}/>
				<Route path="/dog" component={Dog}/>
			</div>
		);
	}
}

export default App;

2) history 사용하기

  • App.js
import {withRouter} from 'react-router';
import {Route, Link} from 'react-router-dom';

class App extends React.Component {
	constructor(props){
		super(props);
		this.state={};
	}

	componentDidMount(){
		console.log(this.props);
	}

	render() {
		return (
			<div className="App">
				<div>
					<Link to="/">Home으로 가기</Link>
					<Link to="/cat">Cat으로 가기</Link>
					<Link to="/dog">Dog으로 가기</Link>
				</div>
	
				<hr/>
				<Route path="/" exact component={Home}/>
				<Route path="/cat" component={Cat}/>
				<Route path="/dog" component={Dog}/>
	
				<button onClick={() => {
					this.props.history.push("/cat");
				}}>
					cat으로 가기
				</button>
				<button onClick={() => {
					this.props.history.goBack();
				}}>뒤로가기</button>
			</div>
		);
	}
}
//내보내는 부분에서 withRouter로 감싸주기
export default withRouter(App);
  • push([이동할 주소]) : 페이지 이동시킴
  • goBack() : 뒤로가기

 

잘못된 주소 처리하기 (Switch 이용)

  • NotFound.js
const NotFound = (props) => {
	return <h1>주소가 올바르지 않습니다</h1>;
);

export default NotFound;
  • App.js
render() {
	return (
		<div className="App">
			...
			<Switch>
				<Route
					path="/"
					exact
					render={(props) => {
						<BucketList
							list={this.state.list}
							history={this.props.history}
						/>
					}}
				/>
				<Route path="/detail" component={Detail}/>
				<Route component={NotFound}/>
			</Switch>
			...
		</div>
	);
}
  • NotFound 컴포넌트를 주소 없이 연결함
  • Switch는 첫번째로 매칭되는 path를 가진 컴포넌트를 렌더링함

1. 로딩 화면(스플래시 화면)에서 firebase DB 데이터 불러오는 문제 해결

- 스레드의 run 메소드 안에 DB 조회 코드가 들어있던 것이 문제... 스레드 밖으로 해당 코드를 뺐더니 해결!

- 하지만 지금은 스레드의 딜레이 시간을 고정으로 준 상태라 그 시간 내에 데이터를 불러오지 못할 수도 있음

- 따라서 확실히 DB 데이터 조회를 완료한 후 액티비티가 이동할 수 있도록 보완할 필요가 있음

 

2. 챗봇 답장 '일기장으로 이동합니다' 출력 시 일기장 액티비티로 이동

- 이 외에도 dialogflow 자체에서 처리할 수 없는 것들은 안드로이드 스튜디오에서 코드 작성해서 처리해야할듯

 

일기장으로 이동합니다 멘트 출력 시 1.5초 뒤 일기장으로 이동

 

3. 진단테스트 안내 문구 추가

- 테스트 진행 전 테스트 목적, 문항 수, 유의사항 등의 안내를 확인할 수 있음

 

진단테스트 안내 화면

 


 

지난 일요일에 회의에서는 테스트 결과에 따라 마음 온도를 어떻게 설정할건지,

마음 온도에 따라 앱 기능의 제한 정도, 게시판 기능의 모호성 등을 논의해보았다.

슬슬 앱의 디자인을 제대로 꾸미기 위해 이모티콘 그림도 각자 그려오기로 했고 (일요일 회의때 대회 진행할 예정 ^_^)

새 기능을 구현하기 위해 각자 역할 분담해서 수행해오기로 했다.

8월 30일까지 한이음 공모전 접수 마감이니까 호다닥 개발해버리자~!😎

1. 진단테스트 결과 출력

- 테스트 점수에 따라 피해, 가해 정도를 [아주 약함 / 약함 / 보통 / 심함 / 아주 심함]으로 분류하여 상세 진단 내용 출력

- 프로그레스바로 최대 점수 중 몇 점을 차지했는지 나타냄

 

피해/가해 정도 결과 상세 내용

 

2. 채팅 내역 전체 삭제 기능 구현

- 상단바에 있는 삭제 버튼을 클릭하면 삭제 안내 팝업이 뜸

- 확인을 클릭하면 지금까지 진행한 모든 채팅 내역이 삭제됨

 

채팅 전체 내역 삭제 팝업

 

3. 채팅 내역이 없을 때 챗봇 웰컴 메세지 출력

- 채팅 화면에 처음 접속했을 때, 또는 채팅 내역 전체 삭제를 진행했을 때 웰컴 메세지를 받음

- 사용자에게 예시 질문을 보여줌으로써 원활한 1:1 상담 채팅을 이용할 수 있도록 함

 

채팅을 처음 시작할 때 웰컴 메세지 출력 (챗봇 가이드 느낌)

 

4. 메인 화면 랜덤 문구 리스트 추가

- strings.xml에 string-array의 item들로 랜덤 문구 리스트를 저장하고 랜덤으로 출력하게 함

 

앱에 접속할 때마다 메인 화면 상단에 사용자를 응원하는 랜덤 문구가 출력된다

 


현재 진단테스트 점수에 따라 결과 상세 내용을 출력하고,

1:1 채팅에서 dialogflow에 작성된 인텐트에 따라 상담을 진행할 수 있는 상태이다.

 

테스트 결과와 챗봇 내용을 구성하고 dialogflow에 옮기기까지 많은 시간이 걸렸을 것 같은데

이것을 무사히 끝낸 팀원들에게 박수를 보낸다👏👏👏... 덕분에 앱의 완성도가 한층 높아진 것 같다!

 

상담 내용 디테일👍

 

7월 한달간 앱의 많은 기능들을 구현했다.

이제 8월에는 교화 프로그램을 통한 마음 온도 조정, 기능 제한, 앱 디자인 보완 등을 중점적으로 다루면 될 것 같다.

1. 자유게시판 댓글 개수 표시

- 공감 개수 표시는 지난번에 구현 완료했었고 이번에 댓글 개수 표시까지 완료함


2. 익명게시판 목록에서 댓글 개수 표시X, 글 내용 페이지에서 댓글 영역 제거

 

3. 익명게시판 글 작성자 닉네임 대신 익명으로 뜨도록 함


4. 앱 내부 폰트 변경 (어비미미체)

 

 

이젠 댓글 개수도 표시되는 자유게시판
익명게시판에서는 댓글 기능을 지원하지 않으므로 영역 제거

 

이로써 게시판 기능은 구현 끝!

이제 테스트, 채팅 기능과 앱 전반적인 디자인에 신경써야할 때인것 같다.

6월 초~중순

6월에는 1학기 마무리를 마무리하는 시점이며 기말고사로 한창 바쁜 시즌이었다.

따라서 6월 초~중순에는 학업에 충실하기로 하여 한이음 프로젝트 개발은 잠시 쉬어가는 타이밍이었다.

종강을 먼저 한 사람부터 앞으로 개발을 하기 위해 공부를 하고 사용할 기술(Dialogflow 등)에 대해 조사하기로 했다.

 

6월 말

우선 비교적으로 구현이 간단할 것으로 판단되며, CRUD의 유사한 기능을 가지는

일기장, 게시판 기능을 6월까지 마무리하기로 하였고 주요 기능 구현을 마쳤다.

 

일기장

일기장 기능에서는 기본으로 지원되는 CalendarView보다는 커스텀 캘린더를 만들어 적용하는 것이

디자인적인 면에서 더 낫다고 생각해서 MaterialCalendarView를 이용하여 구현하는 것을 시도하다가

작성한 코드에서 오류가 발생했는데 그 원인을 1~2주간 고민해도 찾지 못했다.

캘린더 구현 부분에서 너무 오랜 시간을 보내고 있는 것으로 판단하여 결국 MaterialCalendarView 사용을 포기하고

다른 커스텀 캘린더 구현 방법을 찾아보기로 했고 다행히도! 유튜브에서 관련 자료를 찾아 해당 방법을 사용하여 구현했다.

 

게시판

그리고 게시판 기능은 글 작성, 글 목록 조회(DB에서 불러오기) 및 수정/삭제 기능을 구현했다.

게시판은 자유게시판과 익명게시판으로 나뉘는데 fragment 개념을 이용하여 두 게시판을 이동하도록 했다.

아직 로그인/회원가입 구현이 되어있지 않은 상태라 구현이 되고 나면

사용자 계정에 따라 자신이 작성한 글에 대해서만 수정/삭제 권한을 부여할 것이고

각각의 게시글에 대한 댓글 기능, 공감 기능을 추가할 것이다.

 

7월 계획

여름방학 기간 7~8월에 열심히 개발하여 8월 말까지는 개발을 끝내는 것을 목표로 하고 있다!

 

7월에 구현을 끝내야할 사항들을 나열해보았다.

  • Firebase Authentication을 이용하여 사용자 계정 관리, 로그인/회원가입 기능 구현
  • 진단테스트 문항 구성 및 기능 구현
  • Dialogflow 챗봇 인텐트 구성 시작, 앱 화면과 연결하기
  • 일기 작성 시 사진 첨부 기능 추가하기
  • 게시판 댓글, 공감 기능 추가하기
  • 앱 디자인, 레이아웃 개선하기

6월 개발 일지

 

2021.06.21 개발 일지 (게시물 목록 구현, 자체테스트 문항 목록 구현)

1. 게시판 게시물 목록 구현 (RecyclerView 이용) 구현 방법 1) 기존 화면에 RecyclerView 태그 추가 2) 게시물.java 생성 3) 게시물_item.xml 생성 -현재는 CardView 사용, 추후에 레이아웃 수정할 예정 4) 게시..

askges20.tistory.com

 

 

2021.06.25 개발 일지 (게시판 Fragment화, 게시글 작성 및 조회 기능)

1. 게시판 Fragment화 앱에는 자유게시판과 익명게시판, 2개의 게시판이 존재한다. 그런데 기존 코드는 자유게시판/익명게시판 버튼을 터치할 때마다 새로운 Intent를 만들어 페이지 이동을 해서 Inte

askges20.tistory.com

 

SCSS

  • CSS를 편하게 쓸 수 있도록 도와줌
  • node-sass 설치하기
yarn add node-sass@4.14.1 open-color sass-loader classnames
  • 중첩된 속성
div {
	p {
		color: #888888;
		font: {
			family: sans-serif;
			size: 14px;
		}
	}

	img {
		width: 400px;
	}
}
  • 상위 요소 이어쓰기 (& 사용)
div {
	background-color: green
	&:hover { background-color: blue }
}

.div {
	background-color: green
	&_blue { background-color: blue }
}
  • 변수 사용
$defaultSize: 20px;
$className: blue;

p {
	font-size: #{$defaultSize};
	&.#{$className} {color: #{$className}}
}

styled-components

  • 컴포넌트에 스타일을 직접 입히는 방식
  • styled-components 패키지 설치하기
yarn add styled-components
  • class 이름 지을 필요 없어짐, 간단하고 직관적임
import React from 'react';
import styled from 'styled-components';

function App() {
	return (
		<div className="App">
			<MyStyled bgColor={"red"}>hello react!</MyStyled>
		</div>
	);
}

const MyStyled = styled.div`
	width: 50vw;
	min-height: 150px;
	padding: 10px;
	border-radius: 15px;
	color: #fff;
	&:hover {
		background-color: #ddd;
	}
	background-color: ${(props) => (props.bgColor ? "red" : "purple")};
`;

export default App;

 


가상돔

DOM (Document Object Model)

  • HTML의 단위 하나하나를 객체로 생각하는 모델
  • 트리 구조를 가짐
  • 수정이 일어날 때마다 모든 DOM을 조회해서 수정할 것을 찾고 전부 수정하면 연산이 많이 일어남 → 가상돔의 필요성

가상돔 (Virtual DOM)

  • 메모리 상에서 돌아가는 가짜 DOM
  • 기존 DOM과 새로 그린 DOM을 비교해서 바뀐 부분만 수정

라이프 사이클

  • 컴포넌트가 렌더링을 준비하는 순간 ~ 페이지에서 사라질 때까지

  • 컴포넌트 생성수정(업데이트)제거
  • 생성 : 처음으로 컴포넌트를 불러오는 단계
  • 수정(업데이트) : 사용자의 행동으로 데이터가 바뀌거나 부모 컴포넌트가 렌더링할 때 발생
  • ex. props가 바뀔 때, state가 바뀔 때, 부모 컴포넌트가 업데이트 되었을 때 (리렌더링), 강제로 업데이트했을 때 (forceUpdate())
  • 제거 : 페이지를 이동하거나 사용자의 행동으로 인해 컴포넌트가 화면에서 사라지는 단계

라이프 사이클 함수

  • constuctor() : 생성자 함수, 컴포넌트가 생성되면 가장 처음 호출되는 함수
  • render() : 컴포넌트의 모양을 정의하는 함수
  • componentDidMount() : 첫번째 렌더링을 마친 후에 실행, 리렌더링시에는 호출되지 않음 (컴포넌트가 화면에 나타나는 것 → 마운트(Mount) 한다고 표현함)
  • componentDidUpdate(prevProps, prevState, snapshot) : 리렌더링을 완료한 후 실행되는 함수, prevProps/prevState는 업데이트 전 props/state
  • componentWillUnmount() : 컴포넌트가 DOM에서 제거될 때 실행하는 함수, 컴포넌트에 이벤트 리스너를 등록했다면 반드시 해제해야 함
import React from 'react';

class LifecycleEx extends React.Component {
	//생성자 함수
	constructor(props) {
		super(props);
		this.state = {
			cat_name: '나비',
		};
		console.log('in constructor!');
	}

	componentDidMount() {
		console.log('in componentDidMount!');
	}

	componentDidUpdate(prevProps, prevState) {
		console.log(prevProps, prevState);
		console.log('in componentDidUpdate!');
	}

	componentWillUnmount() {
		console.log('in componentWillUnmount!');
	}

	render() {
		return (
			<div>
				<h1>고양이 이름은 {this.state.cat_name}</h1>
				<button onClick={this.changeCatName}>고양이 이름 바꾸기</button>
			</div>
		);
	}
}

export default LifecycleEx;
  • 라이프 사이클 함수는 클래스형 컴포넌트에서만 사용 가능함

Ref

  • 리액트에서 DOM 요소를 가져올 때 사용
class App extends React.Component {
	constructor(props) {
		super(props);
		this.state = {
			list: ['영화관 가기', '매일 책읽기', '수영 배우기']
		};
		this.text = React.createRef(); //ref 선언하기
	}

	componentDidMount() {
		console.log(this.text);
		console.log(this.text.current);
	}

	render() {
		return (
			<div className="App">
				...
				<input type="text" ref={this.text}/>
			</div>
		);
	}
}

 


클래스형 컴포넌트 state 관리

  • setState() : 클래스형 컴포넌트의 state를 업데이트할 때 사용하는 함수
  • 네모 추가/삭제 예제
더보기
import React from 'react';

class App extends React.Component {
	constructor(props) {
		super(props);
		this.state = {
			count: 3,
		};
	}

	componentDidMount() {}

	addNemo = () => {
		this.setState({count: this.state.count + 1});
	};

	removeNemo = () => {
		if (this.state.count > 0) {
			this.setState({count: this.state.count - 1});
		} else {
			window.alert('네모가 없습니다');
		}
	};

	render() {
		const nemo_count = Array.from({length: this.state.count}, (v, i) => i);
		console.log(nemo_count);
		return (
			<div className="App">
				{nemo_count.map((num, idx) => {
					return (
						<div
							key={idx}
							style={{
								width: '150px',
								height: '150px',
								backgroundColor: '#ddd',
								margin: '10px',
							})
						></div>
					);
		}}}

			<div>
				<button onClick={this.addNemo}>하나 추가</button>
				<button onClick={this.removeNemo}하나 빼기</button>
			</div>
		);
	}
}

export default App;
  • addNemo, removeNemo에서 count 상태를 수정함

 

함수형 컴포넌트에서 state 관리

  • 함수형 컴포넌트는 state가 없음
  • 그러나 react hooks를 사용하면 state를 가질 수 있음
  • useState()를 이용하여 state 관리
  • 네모 추가/삭제 예제
더보기
import React from "react";

const Nemo = (props) => {
	//setCount로 count라는 state 값을 수정함
  const [count, setCount] = React.useState(3); //useState로 초깃값 설정

  const addNemo = () => {
    setCount(count + 1); //setCount로 count 1 증가
  };

  const removeNemo = () => {
    setCount(count > 0 ? count - 1 : 0); //setCount로 count 1 감소
  };

  const nemo_count = Array.from({ length: count }, (v, i) => i);
  return (
    <div className="App">
      {nemo_count.map((num, idx) => {
        return (
          <div
            key={idx}
            style={{
              width: "150px",
              height: "150px",
              backgroundColor: "#ddd",
              margin: "10px",
            }}
          >
            nemo
          </div>
        );
      })}

      <div>
        <button onClick={addNemo}>하나 추가</button>
        <button onClick={removeNemo}>하나 빼기</button>
      </div>
    </div>
  );
};

export default Nemo;

 


이벤트 리스너 (Event Listener)

  • 사용자가 어떤 행동(이벤트)을 하는지 지켜보고 알려줌
  • 마우스 클릭, 터치, 마우스 오버, 키보드 누름 등의 이벤트
  • onClick처럼 엘리먼트에 직접 넣어주는 방법 or addEventListener을 통해 추가하는 방법이 있음

클래스형 컴포넌트에서 Event Listener 구독하기

  • DOM 요소가 있어야 이벤트 발생하는지를 지켜볼 수 있음 → componentDidMount()에서 작성
  • addEventListener() : 이벤트 등록 함수
  • removeEventListener() : 이벤트 제거 함수
constructor(props) {
	super(props);
	this.state = {
		count: 3,
	};
	this.div = React.createRef();
}

hoverEvent = (e) => {
	console.log(e.target);
	if (e.target.className === "App") {
		e.target.style.background = "#eee";
	}
}

componentDidMount() {
	console.log(this.div);
	//addEventListener로 이벤트 등록
	this.div.current.addEventListener("mouseover", this.hoverEvent);
}

componentWillUnmount() {
	//컴포넌트가 제거될 때 이벤트 지우기
	this.div.current.removeEventListener("mouseover", this.hoverEvent);
}

return (
	<div className="App" ref={this.div}>
		<Nemo/>
	</div>
);

일주일 전에 정보처리기사 실기 시험 보고나서 한동안 아무 의욕 없이 지내다가

오늘에서야 다시 의욕이 되돌아온듯 하다.

그동안 많이 쉰만큼 오늘은 12시간동안 빡세게 개발했지!⭐🤸‍♀️

 

 

1. 게시글 공감 기능

- 각 게시글마다 공감 버튼을 누른 사용자의 uid를 Firebase DB에 저장

- 몇 명이 공감을 눌렀는지 실시간으로 반영하여 화면에 표시함

- 금방 구현할줄 알았는데 PostAdapter의 구조 변경이 많이 이루어져서 생각보다 시간이 오래 걸렸던 작업

 

공감 버튼을 클릭하면 하트가 채워지고 공감 수가 오른다. 같은 버튼으로 공감 취소도 가능하다.

 

게시판의 글 목록에서도 공감 수를 확인할 수 있다. 댓글 수는 나중에 추가할 예정이다.

 

2. 메인 화면 사이드바 구현

- 메인 화면 좌측 상단 버튼을 클릭하면 사이드바가 나옴

- DrawerLayout, NavigationView를 이용하여 구현 (https://onelight-stay.tistory.com/86 참고)

- NavigationView는 headerLayout(헤더 부분)과 menu(메뉴 부분)를 포함함

- 각 메뉴를 클릭했을 때 해당 페이지로 이동하도록 구현함

 

좌측 상단 버튼을 클릭했을 때 사이드바가 나오는 모습이다. 진단테스트 ~ 마이페이지 버튼 클릭 시 해당 페이지로 이동한다.

 

3. 1:1 상담 채팅 날짜 표시

- 보통 메신저 앱을 이용할 때 각 날짜를 기준으로 가장 상단 부분에 날짜가 출력됨 → 이것을 구현

- 채팅 시간 표시도 구현할까 했으나, 사람간의 대화도 아닐뿐더러 사용자가 보낸 이후 몇 초만에 챗봇으로부터 답장이 오기 때문에 시간은 그닥 중요하지 않다고 판단하고 구현하지 않았음

날짜 별 채팅의 가장 상단에 날짜가 출력되는 모습이다.

 

4. 다크모드를 적용하지 않도록 수정

- 항상 기본모드로 폰을 사용해서 몰랐는데 다크모드일 때 앱을 접속하면 themes.xml (night)의 테마가 적용되는듯함

- 다크모드를 고려하지 않고 항상 동일한 테마를 제공하기 위해 스플래시.java에 다음 코드를 추가함

AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);

 

5. 게시글 삭제 시 관련 댓글, 공감도 DB에서 삭제하도록 코드 추가

- 기존 코드는 글을 삭제했을 때 해당 글의 댓글 데이터를 삭제하는 부분이 없었음

- 따라서 댓글과 오늘 구현한 공감 모두 관련 글이 삭제되면 같이 삭제되도록 구현함

 

6. 게시글 내용 페이지 드롭다운 메뉴 추가

- 원래는 수정, 삭제 버튼이 글 내용 하단에 있었는데 이것을 드롭다운 메뉴로 변경

우측 상단 버튼을 클릭하면 수정/삭제 버튼이 있는 드롭다운 메뉴가 나온다.

 

7. 익명게시판 게시글 댓글 영역 삭제

- 자유게시판에는 댓글과 공감, 익명게시판에는 공감 기능만 제공할 것이기 때문에 익명게시판 글은 댓글 영역을 안보이도록 setVisibility(View.GONE); 적용

댓글 영역이 사라진 익명게시판 글 내용 페이지이다.

 

8. 각 페이지의 상단바 통일

- 높이 70dp에 좌측 상단에 뒤로가기 버튼 추가

 


게시판 공감 기능 구현을 완료하였으니, 댓글 수 출력 이외의 게시판 기능 구현은 모두 완료한 듯 하다!!👍

뭔가 앱다운 앱을 만들고 싶어서 메인 화면에 사이드바를 추가했는데, 생각보다 어렵지 않은 방법이 있어 금방 구현했다.

 

나중에는 메인 화면 레이아웃을 한번 갈아 엎을 예정이다.

현재 메인 화면은 프로토타입(내가 작성했었음)과 너~~무 똑같아서 좀 단순하달까?? 다채로운 느낌이 들진 않는다.

실제로 출시된 앱들의 메인 화면을 찾아보면서 참고해야겠다!

 

앱의 기능을 하나씩 구현해나가고, 디자인이 점차 개선되는 것을 보니 뿌듯하다✌

안드로이드 스튜디오 앱 개발 프로젝트를 진행하면서 다크모드는 생각도 못하고 있었는데

다크모드를 적용하고 앱을 실행하면 레이아웃의 많은 부분이 원치 않게 깨지는 현상을 발견했다.

 

res - values - themes 폴더에 일반/다크모드 전용 themes.xml 파일이 있어서

원하는 사람은 themes.xml (night) 파일을 수정하여 다크모드에 맞게 따로 디자인을 적용해도 좋을 것 같으나

나는 다크모드를 지원하지 않는 방법을 찾아보았다.

 

 

앱을 실행할 때 가장 먼저 실행되는 액티비티인 로딩 화면(스플래시 화면) java 파일 onCreate 메소드에

다음과 같이 코드를 작성했다.

public class ActivitySplash extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO); //다크모드 지원 X
	//생략
    }
}

 

이렇게 작성하면 AppCompatActivity를 상속한 클래스는 다크모드를 킨 상태로 앱을 이용하더라도

다크모드 테마가 아닌 기본 테마가 적용이 된다.

 

하지만 일부 페이지는 위의 코드를 작성해도 다크모드가 적용되는 것을 발견했는데

AppCompatActivity가 아닌 Activity를 상속한 클래스들이었다.

따라서 해당 클래스들을 모두 AppCompatAcitivty를 상속하도록 수정했다.

 

이로써 다크모드 적용으로 인해 앱의 디자인이 원치 않게 깨지는 현상을 막을 수 있었다!

+ Recent posts