오늘부터 본격적으로 앱개발 기초 팀프로젝트가 시작 됐다. 주제는 SNS 앱 만들기로 예시로 인스타그램이 주어졌지만 팀원들과 여러 SNS를 얘기하며 결정한 주제는 게임 커뮤니티 앱이다. 구현 하고 싶은 기능은 많지만 비교적 짧은 기간으로 인해 최소한의 기능으로 디테일을 높이려고 한다. 앞으로 약 일주일간 프로젝트를 진행하며 프로젝트 개발 현황을 정리하고자 한다. 📝
- 로그인 화면
- 이메일, 비밀번호 텍스트 필드 빈값 체크
- 비밀번호 입력시 *** 표시
- 회원가입 버튼 클릭시 회원가입 화면으로 이동
- 로그인 버튼 클릭시 메인 화면으로 이동
- 회원가입 화면
- 유효성 검사 (비밀번호 조합, 이메일, 닉네임 중복 체크 등)
- 스피너를 사용해 이메일 서비스 제공사 목록을 보여줌
- 텍스트 필드 빈값 체크
- 비밀번호 입력시 *** 표시
- 메인 화면
- 캐릭터 리스트 RecyclerView 사용
- 캐릭터 이미지를 모서리가 둥근 이미지로 출력
- 캐릭터 클릭 시 해당 캐릭터의 디테일 화면으로 이동
- 디테일 화면으로 이동시 animation 구현
- bottom navigation bar
- 디테일 화면
- 메인 화면에서 전달받은 id값에 해당하는 캐릭터 정보 출력
- 캐릭터에 대해 모든 사용자가 작성한 댓글 목록 출력
- 마이 페이지 화면
- 대표 캐릭터를 설정했을 경우에만 프로필 사진 출력
- 팝업, 스피너를 사용해 대표 캐릭터 설정
- 사용자가 작성한 댓글 목록 출력
data class Character(
val id: Int, // 고유값
val profileImage: Int, // 캐릭터 이미지
val korName: String, // 한글 이름
val engName: String, // 영어 이름
val type: String, // 계열
val explainType: String, // 계열 설명
val weapon: String, // 대표 무기
val identification: String, // 아이덴티티
val explainIdentity: String // 아이덴티티 설명
)
class CharacterManager {
companion object {
fun getItems(): List<Character> = listOf(
Character(0, R.drawable.character_breaker, "브레이커", "Breaker", "무도가", "", "헤비 건틀릿", "기력/충격/투지 에너지", ""),
Character(1, R.drawable.character_sorceress, "소서리스", "Sorceress", "마법사", "", "롱 스태프", "마력 방출/점멸", ""),
Character(2, R.drawable.character_striker, "스트라이커", "Striker", "무도가", "", "엘리멘탈 건틀렛", "엘리멘탈 버블", ""),
Character(3, R.drawable.character_reaper, "리퍼", "Reaper", "암살자", "", "대거", "페르소나", ""),
Character(4, R.drawable.character_gunslinger, "건슬링어", "Gunslinger", "헌터(여)", "", "샷건, 더블 핸드건, 라이플", "퀵 스탠스", ""),
Character(5, R.drawable.character_bard, "바드", "Bard", "마법사", "", "리아네 하프", "세레나데", ""),
Character(6, R.drawable.character_summoner, "서머너", "Summoner", "마법사", "", "매직 스태프", "고대정령 소환", ""),
Character(7, R.drawable.character_blade, "블레이드", "Blade", "암살자", "", "검", "블레이드 아츠", ""),
Character(8, R.drawable.character_lance_master, "창술사", "Lance Master", "무도가", "", "창", "듀얼 스탠스", ""),
Character(9, R.drawable.character_arcana, "아르카나", "Arcana", "마법사", "", "마법덱", "카드 덱", ""),
Character(10, R.drawable.character_holyknight, "홀리나이트", "Holyknight", "전사", "", "한손검", "신앙게이지", "")
)
}
fun getSelectedItem(id: Int) {
getItems().find { it.id == id }?.let { item ->
println("${item.korName}, ${item.engName}, ${item.type}, ${item.explainType}, ${item.weapon}, ${item.identification}, ${item.explainIdentity}") // TODO
}
}
}
class RecyclerCharacterAdapter(
private val items: List<Character>,
private val itemClickListener: (Int) -> Unit
) :
RecyclerView.Adapter<RecyclerCharacterAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.item_rv_character, parent, false)
)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.itemView.setOnClickListener {
itemClickListener.invoke(position)
}
holder.bindItem(items[position])
}
override fun getItemCount(): Int = items.size
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bindItem(item: Character) {
val ivCharacter = itemView.findViewById<ImageView>(R.id.ivCharacter)
val tvName = itemView.findViewById<TextView>(R.id.tvCharacterName)
val tvComment = itemView.findViewById<TextView>(R.id.tvCharacterComment)
ivCharacter.setImageResource(item.profileImage)
ivCharacter.clipToOutline = true
tvName.text = item.korName
val commentsSize = "댓글 ${item.korName.getSize()}"
tvComment.text = commentsSize
}
}
// 캐릭터 이름으로 검색해 댓글 수 가져오기
fun String.getSize() = MyCommentsTempDatas().getCommentSize(this)
}
class MainActivity : AppCompatActivity() {
private val rcCharacter: RecyclerView by lazy {
findViewById(R.id.rcCharacter)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initView()
}
private fun initView() {
val items = CharacterManager.getItems().sortedBy { it.korName }
val adapter = setCharacterAdapter(items) { position ->
Toast.makeText(applicationContext, items[position].korName, Toast.LENGTH_SHORT).show()
// TODO 디테일 화면 이동
}
rcCharacter.adapter = adapter
rcCharacter.layoutManager = GridLayoutManager(this, 3)
}
// 어댑터 세팅
private fun setCharacterAdapter(
items: List<Character>,
onItemClick: (Int) -> Unit
): RecyclerCharacterAdapter {
return RecyclerCharacterAdapter(items, onItemClick)
}
}
class MyCommentsTempDatas {
val dataset = arrayOf(
...
) // 댓글 리스트
// 리스트에서 캐릭터 이름에 해당하는 댓글 수
fun getCommentSize(name: String): Int = dataset.count { it.charName == name }
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:orientation="vertical">
<ImageView
android:id="@+id/ivCharacter"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:background="@drawable/iv_background_radius" />
<TextView
android:id="@+id/tvCharacterName"
style="@style/ThemeTvCharacter"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tvCharacterComment"
style="@style/ThemeTvCharacter"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="13sp" />
</LinearLayout>
프로젝트 기간이 일주일도 채 안되는 기간이다보니 우선적으로 화면을 액티비티로 구성하여 화면 전환을 구현하려고 한다. 개발 현황을 보고 네비게이션 바를 사용하여 프래그먼트간 화면 전환이 이루어지도록 수정하고, Room database를 사용하여 데이터를 저장하면 좋을 것 같다 ! 🔥