이번 시리즈에서는 실시간 채팅 앱을 만들어보도록 하겠습니다. 저의 본업이 백엔드 개발이다보니, 서버가 있는 채팅 앱을 만들어보려 합니다. 참고로, 프론트엔드 코딩에는 Android Kotlin을, 백엔드 코딩에는 Java Spring을 사용할 것이며, 서버는 EC2, 데이터베이스는 RDS, 이미지 저장에는 S3를 사용하도록 하겠습니다. 코드에 대한 별도의 설명 없이 구현 코드만 보여드릴 예정이며, 구현 코드는 아래의 깃허브 링크에서도 확인하실 수 있습니다.
>> 프론트엔드 깃허브 링크
>> 백엔드 깃허브 링크
이번 포스팅에서는 스플래시 화면, 회원가입, 로그인 페이지를 만들어보도록 하겠습니다.
① chatting app이라는 이름으로 프로젝트를 생성한다.
② default 패키지 하위로, SplashActivity와 IntroActivity를 추가한다.
③ SplashActivity에 아래의 내용을 입력한다.
@Suppress("DEPRECATION")
class SplashActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_splash)
Handler().postDelayed({
startActivity(Intent(this, IntroActivity::class.java))
finish()
}, 3000)
}
}
④ Splash 화면이 가장 먼저 나타날 수 있도록, AndroidManifest.xml 파일의 activity 컨테이너를 수정한다.
<activity
android:name=".IntroActivity"
android:exported="false" />
<activity
android:name=".SplashActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".MainActivity"
android:exported="true">
</activity>
⑤ 스플래시 화면에 사용할 말풍선 아이콘을 splash라는 이름으로 drawable 패키지에 추가한다.
⑥ 하늘색을 main color로 사용하기 위해 values > colors.xml 파일에 아래의 내용을 추가한다.
<color name="skyBlue">#B8F8FB</color>
⑦ activity_splash.xml 파일에 아래의 내용을 입력한다.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/skyBlue"
tools:context=".SplashActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="120dp"
android:text="U & I TALK"
android:textColor="@color/black"
android:textSize="60sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:layout_width="350dp"
android:layout_height="350dp"
android:layout_marginBottom="80dp"
android:src="@drawable/splash"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
activity_intro.xml 파일에 아래의 내용을 입력한다.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/skyBlue"
tools:context=".IntroActivity">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="50dp"
android:gravity="center"
android:text="너와 나의 채팅공간,\n\nU & I TALK"
android:textSize="40sp"
android:textStyle="bold"
android:textColor="#000000"
app:layout_constraintTop_toTopOf="parent"
tools:layout_editor_absoluteX="15dp" />
<ImageView
android:layout_width="250dp"
android:layout_height="250dp"
android:src="@drawable/splash"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="250dp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.93"
tools:layout_editor_absoluteX="9dp">
<Button
android:id="@+id/join"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="15dp"
android:background="@color/black"
android:text="회원 가입"
android:textColor="@color/white"
android:textSize="20sp"
android:textStyle="bold" />
<Button
android:id="@+id/login"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="15dp"
android:background="@color/black"
android:text="로그인"
android:textColor="@color/white"
android:textSize="20sp"
android:textStyle="bold" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
① default 패키지 하위로, authentication이라는 패키지를 생성한다.
② authentication 패키지 하위로 JoinActivity를 생성한다.
③ IntroActivity 파일에 아래의 내용을 입력한다.
class IntroActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_intro)
val joinBtn = findViewById<Button>(R.id.join)
joinBtn.setOnClickListener {
val intent = Intent(this, JoinActivity::class.java)
startActivity(intent)
}
}
}
④ 프로필 이미지를 나타낼 사진을 drawable 디렉토리 하위에 profile이라는 이름의 파일을 추가한다.
⑤ 어플리케이션의 main color인 하늘색으로 된 테두리를 만들자. drawable 디렉토리 하위로, main_border 리소스 파일을 생성한다.
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="@color/skyBlue" />
</shape>
</item>
<item android:top = "3dp"
android:right= "3dp"
android:left="3dp"
android:bottom="3dp">
<shape android:shape="rectangle">
<solid android:color="#ffffff" />
</shape>
</item>
</layer-list>
⑥ 버튼의 색이 적용되지 않는 문제를 해결하기 위해 res > values > themes > (night 안 쓰여있는) themes.xml 파일의 3번째 줄을 아래와 같이 수정한다.
<style name="Base.Theme.ChattingApp" parent="Theme.AppCompat.Light">
⑦ activity_join.xml 파일에 아래의 내용을 입력한다.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
tools:context=".authentication.JoinActivity">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:layout_width="150dp"
android:layout_height="150dp"
android:src="@drawable/splash"
android:layout_marginTop="10dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="닉네임"
android:layout_marginHorizontal="40dp"
android:layout_marginTop="10dp"
android:layout_marginBottom="5dp"
android:textStyle="bold"
android:textColor="#000000"
android:textSize="20sp"/>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="40dp"
android:layout_marginBottom="10dp">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/nickname"
android:hint="nickname"
android:padding="5dp"
android:background="@drawable/main_border"
android:textColorHint="#808080"
android:textSize="25sp"
android:layout_width="match_parent"
android:layout_height="40dp"/>
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="이메일"
android:layout_marginHorizontal="40dp"
android:layout_marginTop="10dp"
android:layout_marginBottom="5dp"
android:textStyle="bold"
android:textColor="#000000"
android:textSize="20sp"/>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="40dp"
android:layout_marginBottom="10dp">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/joinEmail"
android:hint="email"
android:padding="5dp"
android:background="@drawable/main_border"
android:textColorHint="#808080"
android:textSize="25sp"
android:layout_width="match_parent"
android:layout_height="40dp"/>
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="비밀번호"
android:layout_marginHorizontal="40dp"
android:layout_marginTop="10dp"
android:layout_marginBottom="5dp"
android:textStyle="bold"
android:textColor="#000000"
android:textSize="20sp"/>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="40dp"
android:layout_marginBottom="10dp"
app:counterMaxLength="12"
app:counterEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/joinPassword"
android:hint="password"
android:padding="5dp"
android:background="@drawable/main_border"
android:textColorHint="#808080"
android:textSize="25sp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="비밀번호 확인"
android:layout_marginHorizontal="40dp"
android:layout_marginTop="10dp"
android:layout_marginBottom="5dp"
android:textStyle="bold"
android:textColor="#000000"
android:textSize="20sp"/>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="40dp"
android:layout_marginBottom="10dp"
app:counterMaxLength="12"
app:counterEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/passwordChk"
android:hint="password check"
android:padding="5dp"
android:background="@drawable/main_border"
android:textColorHint="#808080"
android:textSize="25sp"
android:layout_width="match_parent"
android:layout_height="40dp"
android:inputType="textPassword" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
<Button
android:id="@+id/joinBtn"
android:text="회원가입"
android:textSize="30sp"
android:textStyle="bold"
android:gravity="center"
android:layout_margin="30dp"
android:background="@color/skyBlue"
android:layout_width="match_parent"
android:layout_height="50dp"/>
</LinearLayout>
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
⑧ 타이틀 바를 없애기 위해 themes.xml 파일의 style 컨테이너 안에 아래와 같이 item 컨테이너를 추가하자.
<style name="Base.Theme.ChattingApp" parent="Theme.AppCompat.Light">
<!-- Customize your light theme here. -->
<!-- <item name="colorPrimary">@color/my_light_primary</item> -->
<item name="windowNoTitle">true</item>
</style>
① authentication 패키지 하위로 LoginActivity를 생성한다.
② IntroActivity 파일에 로그인 버튼에 대한 클릭 이벤트 리스너를 추가한다.
class IntroActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_intro)
val joinBtn = findViewById<Button>(R.id.join)
joinBtn.setOnClickListener {
val intent = Intent(this, JoinActivity::class.java)
startActivity(intent)
}
val loginBtn = findViewById<Button>(R.id.login)
loginBtn.setOnClickListener {
val intent = Intent(this, LoginActivity::class.java)
startActivity(intent)
}
}
}
③ activity_login.xml 파일에 아래의 내용을 입력한다.
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
tools:context=".authentication.LoginActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:layout_width="250dp"
android:layout_height="250dp"
android:src="@drawable/splash"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="40dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="40dp"
android:layout_marginTop="40dp">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/email"
android:hint="email"
android:padding="5dp"
android:background="@drawable/main_border"
android:textSize="25sp"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="40dp"
android:layout_marginTop="20dp"
app:counterMaxLength="12"
app:counterEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/password"
android:hint="password"
android:padding="5dp"
android:background="@drawable/main_border"
android:textSize="25sp"
android:inputType="textPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</com.google.android.material.textfield.TextInputLayout>
<Button
android:id="@+id/loginBtn"
android:text="로그인"
android:layout_marginTop="90dp"
android:layout_marginHorizontal="40dp"
android:background="@color/skyBlue"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
</ScrollView>
유저의 회원가입 및 로그인에 대한 처리는 다음 포스팅에서 다루기로 하자.