-
Notifications
You must be signed in to change notification settings - Fork 33
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
경북대 Android_유지예_1주차_과제 #10
base: yjy1220
Are you sure you want to change the base?
Changes from all commits
34f79e7
bce177a
175d6e3
0e91fa8
7aed137
e0144a9
232d9c3
4fe166e
6181c41
f5a7482
750f6bd
e0fa7b5
d98073f
020f0d6
3cdea64
a416479
e83af53
538a974
015d5ba
9bdd6bb
9643a3e
7c0d571
f3dbeb2
5f1ac66
8471f81
3378f9d
34ea9c1
606fed8
eee04a8
8ae4646
c9edb53
c6dcba1
e9e8ec9
1910cee
e81c6e9
b9e0400
b40e90a
e9cdf63
207f8cf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,94 @@ | ||
# android-contacts | ||
# KakaoTechCampus 2기 step2 | ||
## 1주차 1단계 과제 - 연락처 추가 | ||
|
||
### 기능 설명 | ||
1. 전체 폼 디자인 | ||
2. 프로필 이미지 설정 | ||
3. 입력 필드 기능 | ||
4. 전화번호 필수 입력 값 기능 | ||
5. 더보기 토글 기능 | ||
6. 생일 선택 캘린더 팝업 기능 | ||
7. 성별 선택 기능 | ||
8. 취소 기능 | ||
9. 저장 기능 | ||
|
||
--- | ||
|
||
## 1주차 2단계 과제 - 연락처 목록 | ||
|
||
### 화면 3가지 | ||
> main 화면 | ||
> > 오른쪽 하단에 + 블록 버튼이 있는 연락처 조회 및 추가 시작 화면 | ||
|
||
> 연락처 추가 화면 | ||
> > 1단계에서 구현한 화면 | ||
|
||
> 연락처 세부사항 조회 화면 | ||
> > 추가된 연락처를 누룰 시, 해당 연락처 프로필 이미지, 이름, 전화번호 조회 가능 | ||
|
||
### 작동 방법 | ||
1. main 화면에서 오른쪽 하단의 + 버튼을 누르면 '연락처 추가 화면'으로 넘어간다. | ||
2. 해당 '연락처 추가 화면'에서 이름과 전화번호(필수값)을 입력 후 '저장'을 누르면 다시 초기의 main 화면으로 돌아간다. 단, 이때 main 화면에는 추가한 연락처 목록이 조회된다. | ||
3. 해당 연락처 목록을 클릭 시 '연락처 세부사항 조회 화면'으로 넘어간다. | ||
4. 해당 '연락처 세부사항 조회 화면'에는 입력한 이름, 전화번호(필수값)이 있고, 그 외 입력한 값들이 있다면 그 정보들이 추가로 떠 있다. | ||
5. 이후 뒤로가기 버튼을 누르면 다시 main 화면으로 돌아간다. | ||
6. 앱을 종료 시 해당 데이터들은 모두 리셋된다. | ||
|
||
### 기능 구현 | ||
1. 연락처 추가 기능 구현 - 1단계 구현 내용 | ||
2. main 화면 플로팅 버튼 및 텍스트 구현 | ||
3. 추가한 연락처 데이터 - contact 객체 - intent로 전달 기능 구현 | ||
4. 전달받은 객체 데이터를 Recyclerview로 표현 | ||
5. 연락처 추가 시 항목 리스트 조회 기능 구현 | ||
6. 추가된 연락처 스크롤 및 세부사항 화면 클릭 기능 구현 | ||
7. 연락처 세부 사항 조회 기능 구현 | ||
8. 세부 조회 데이터 조건 구현 | ||
|
||
--- | ||
|
||
### 전체 디렉토리 구조 (2단계 기준) | ||
```bash | ||
app | ||
├── build | ||
├── src | ||
│ ├── androidTest | ||
│ │ └── java | ||
│ │ └── contacts | ||
│ │ └── .gitkeep | ||
│ ├── main | ||
│ │ ├── java | ||
│ │ │ └── contacts | ||
│ │ │ ├── Contact.kt | ||
│ │ │ ├── ContactAddActivity.kt | ||
│ │ │ ├── ContactDetailActivity.kt | ||
│ │ │ ├── ContactsAdapter.kt | ||
│ │ │ └── MainActivity.kt | ||
│ │ └── res | ||
│ │ ├── drawable | ||
│ │ │ ├── circle_background.xml | ||
│ │ │ ├── ic_add.xml | ||
│ │ │ ├── ic_launcher_background.xml | ||
│ │ │ ├── ic_launcher_foreground.xml | ||
│ │ │ ├── profile_image.png | ||
│ │ │ ├── round_background.xml | ||
│ │ │ └── round_corners.xml | ||
│ │ └── layout | ||
│ │ ├── activity_main.xml | ||
│ │ ├── contact_add.xml | ||
│ │ ├── contact_detail.xml | ||
│ │ └── contact_item.xml | ||
├── .gitignore | ||
├── build.gradle.kts | ||
└── proguard-rules.pro | ||
``` | ||
### 구현 화면 | ||
<img src="https://github.com/YJY1220/Programmers_prac/assets/93771689/6cd67398-f9af-483a-8f5c-d2f50c154352" width="200" height="400"/> | ||
<img src="https://github.com/YJY1220/Programmers_prac/assets/93771689/bb312d57-7b6d-4c30-ab6a-febfadb8d7cc" width="200" height="400"/> | ||
<img src="https://github.com/YJY1220/Programmers_prac/assets/93771689/f522fb92-7139-469f-b7d2-fffdf0f75d9d" width="200" height="400"/> | ||
<img src="https://github.com/YJY1220/Programmers_prac/assets/93771689/ea04d308-2bb5-4387-9b2c-2e9853cb37ee" width="200" height="400"/> | ||
<img src="https://github.com/YJY1220/Programmers_prac/assets/93771689/b2e5f425-d791-4265-8b07-da94115e3044" width="200" height="400"/> | ||
<img src="https://github.com/YJY1220/Programmers_prac/assets/93771689/f6f155cd-7e36-4c83-9be9-24aa1165c2e6" width="200" height="400"/> | ||
<img src="https://github.com/YJY1220/Programmers_prac/assets/93771689/fcc10402-f863-4a88-9b59-f36be7779d0e" width="200" height="400"/> | ||
|
||
### 실행 영상 (전체 실행영상) | ||
[[https://youtu.be/re7rHcPNlhE](https://youtu.be/S2kDtQot_4Y)](https://youtu.be/UEXYAcABTMc) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package campus.tech.kakao.contacts | ||
|
||
import android.os.Parcel | ||
import android.os.Parcelable | ||
|
||
//연락처 데이터 객체 클래스 | ||
data class Contact ( | ||
val name: String, | ||
val phone: String, | ||
val email: String?, | ||
val birth: String?, | ||
val gender: String?, | ||
val memo: String? | ||
) : Parcelable { //intent로 전달하는 거 | ||
constructor(parcel: Parcel) : this( | ||
parcel.readString() ?: "", | ||
parcel.readString() ?: "", | ||
parcel.readString(), | ||
parcel.readString(), | ||
parcel.readString(), | ||
parcel.readString() | ||
) | ||
|
||
//Contact 객체 데이터 내용을 parcel에 쓰기 | ||
override fun writeToParcel(parcel: Parcel, flags: Int){ | ||
parcel.writeString(name) | ||
parcel.writeString(phone) | ||
parcel.writeString(email) | ||
parcel.writeString(birth) | ||
parcel.writeString(gender) | ||
parcel.writeString(memo) | ||
} | ||
|
||
//오류 해결 | ||
//Parcelable 필수 | ||
override fun describeContents(): Int { | ||
return 0 | ||
} | ||
|
||
//Parcelable 객체 생성 | ||
companion object CREATOR: Parcelable.Creator<Contact> { | ||
override fun createFromParcel(parcel: Parcel): Contact { | ||
return Contact(parcel) | ||
} | ||
|
||
//parcelable 인터페이스의 객체 직렬화 시 필수 - newArray method | ||
override fun newArray(size:Int):Array<Contact?>{ | ||
return arrayOfNulls(size) | ||
} | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
package campus.tech.kakao.contacts | ||
|
||
import android.os.Bundle | ||
import androidx.appcompat.app.AppCompatActivity | ||
import android.app.DatePickerDialog //날짜 선택 | ||
import java.text.SimpleDateFormat // 날짜 형식 지정 | ||
import java.util.* | ||
import android.widget.* | ||
import android.content.Intent | ||
|
||
class ContactAddActivity : AppCompatActivity() { | ||
|
||
private lateinit var nameText: EditText | ||
private lateinit var phoneText: EditText | ||
private lateinit var emailText: EditText | ||
private lateinit var birthText: TextView | ||
private lateinit var memoText: EditText | ||
private lateinit var moreLayout: LinearLayout | ||
private lateinit var moreText: TextView | ||
private lateinit var cancelButton: Button | ||
private lateinit var saveButton: Button | ||
private lateinit var genderRadio: RadioGroup | ||
private lateinit var femaleButton: RadioButton | ||
private lateinit var maleButton: RadioButton | ||
override fun onCreate(savedInstanceState: Bundle?) { | ||
super.onCreate(savedInstanceState) | ||
setContentView(R.layout.contact_add) | ||
|
||
initialViews() | ||
|
||
//호출 | ||
moreText.setOnClickListener{ | ||
toggleMore() | ||
} | ||
|
||
birthText.setOnClickListener{ | ||
showDatePicker() | ||
} | ||
|
||
cancelButton.setOnClickListener { | ||
Toast.makeText(this,getString(R.string.cancel_message), Toast.LENGTH_SHORT).show() | ||
finish() //main 화면으로 돌아가기 | ||
} | ||
|
||
saveButton.setOnClickListener { | ||
val name = nameText.text.toString().trim() | ||
val phone = phoneText.text.toString().trim() | ||
val email = emailText.text.toString().trim() | ||
val birth = birthText.text.toString().trim() | ||
val memo = memoText.text.toString().trim() | ||
|
||
//이름 or 전화번호 미 입력 시 저장 불가 | ||
if(name.isEmpty() || phone.isEmpty()){ | ||
Toast.makeText(this, getString(R.string.notice_message), Toast.LENGTH_SHORT).show() | ||
return@setOnClickListener | ||
} | ||
|
||
//성별 선택 | ||
//아무런 성별도 선택하지 않은 경우 " " 값이 default 이어야 함 | ||
val gender = when(genderRadio.checkedRadioButtonId){ | ||
R.id.femaleButton -> "여성" | ||
R.id.maleButton -> "남성" | ||
else -> " " | ||
} | ||
|
||
//contact 객체 생성 | ||
val contact = Contact(name, phone, email, birth, gender, memo) | ||
|
||
//intent로 결과 반환 | ||
val resultIntent = Intent() | ||
resultIntent.putExtra("contact", contact) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 보통 이런 요소는 Constant라는 object를 파일단위로 생성하고 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 객체 자체를 넣기보다는 전화번호부의 키값이 될만한 별도의 id 값이나, 혹은 현재까지 세팅된 것중에서는 phone 번호만 넘기고 넘어가는 다음페이지에서 해당 정보를가지고 쿼리를 하듯이 별도로 Contact를 로드해오는게 이상적으론 더 나은 구현이긴합니다. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 넵, 다음엔 그렇게 구현해보겠습니다..! 더 코드가 깔끔해질 것 같습니다. 감사합니다! |
||
setResult(RESULT_OK, resultIntent) | ||
|
||
Toast.makeText(this, getString(R.string.save_message), Toast.LENGTH_SHORT).show() | ||
finish() | ||
} | ||
} | ||
|
||
private fun initialViews(){ | ||
nameText = findViewById(R.id.nameText) | ||
phoneText = findViewById(R.id.phoneText) | ||
emailText = findViewById(R.id.emailText) | ||
birthText = findViewById(R.id.birthText) | ||
memoText = findViewById(R.id.memoText) | ||
moreLayout = findViewById(R.id.moreLayout) | ||
moreText = findViewById(R.id.moreText) | ||
cancelButton = findViewById(R.id.cancelButton) | ||
saveButton = findViewById(R.id.saveButton) | ||
genderRadio = findViewById(R.id.genderRadio) | ||
femaleButton = findViewById(R.id.femaleButton) | ||
maleButton = findViewById(R.id.maleButton) | ||
} | ||
//더보기 토글 기능 - 표시 및 숨기기 | ||
private fun toggleMore(){ | ||
val moreLayout = findViewById<LinearLayout>(R.id.moreLayout) | ||
val moreText = findViewById<TextView>(R.id.moreText) | ||
Comment on lines
+95
to
+96
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 메소드를 실행시킬때마다 비싼 xml 파싱을 통해 뷰를 탐색해내는 findViewBy 를 실행하는 것이 아닌, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. b40e90a |
||
|
||
if(moreLayout.visibility == LinearLayout.GONE){ | ||
moreLayout.visibility = LinearLayout.VISIBLE | ||
moreText.visibility = TextView.GONE | ||
} | ||
else { | ||
moreLayout.visibility = LinearLayout.GONE | ||
moreText.visibility = TextView.VISIBLE | ||
} | ||
Comment on lines
+98
to
+105
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Activity에서 이런 처리에 대한 책임이 있는것이 문제가 있는건 아닙니다. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 아하 넵, 알겠습니다! 감사합니다! |
||
} | ||
|
||
private fun showDatePicker() { | ||
val calendar = Calendar.getInstance() | ||
val datePicker = DatePickerDialog( | ||
this, | ||
{ _, year, month, day -> | ||
val selectedDate = Calendar.getInstance() | ||
selectedDate.set(year, month, day) | ||
val setting = SimpleDateFormat("yyyy.MM.dd", Locale.getDefault()) | ||
val formDate = setting.format(selectedDate.time) | ||
birthText.text = formDate | ||
}, | ||
calendar.get(Calendar.YEAR), | ||
calendar.get(Calendar.MONTH), | ||
calendar.get(Calendar.DAY_OF_MONTH) | ||
) | ||
datePicker.show() | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
package campus.tech.kakao.contacts | ||
|
||
import android.os.Bundle | ||
import androidx.appcompat.app.AppCompatActivity | ||
import android.widget.ImageView | ||
import android.widget.TextView | ||
|
||
class ContactDetailActivity : AppCompatActivity() { | ||
override fun onCreate(savedInstanceState: Bundle?) { | ||
super.onCreate(savedInstanceState) | ||
setContentView(R.layout.contact_detail) | ||
|
||
//view 초기화 보는 거 | ||
val profileView = findViewById<ImageView>(R.id.profileView) | ||
val nameView = findViewById<TextView>(R.id.nameView) | ||
val phoneView = findViewById<TextView>(R.id.phoneView) | ||
val emailView = findViewById<TextView>(R.id.emailView) | ||
val birthView = findViewById<TextView>(R.id.birthView) | ||
val genderView = findViewById<TextView>(R.id.genderView) | ||
val memoView = findViewById<TextView>(R.id.memoView) | ||
|
||
val nameLabelText = findViewById<TextView>(R.id.nameLabelText) | ||
val phoneLabelText = findViewById<TextView>(R.id.phoneLabelText) | ||
val emailLabelText = findViewById<TextView>(R.id.emailLabelText) | ||
val birthLabelText = findViewById<TextView>(R.id.birthLabelText) | ||
val genderLabelText = findViewById<TextView>(R.id.genderLabelText) | ||
val memoLabelText = findViewById<TextView>(R.id.memoLabelText) | ||
|
||
//parcel에 적어서 intent로 전달된 contact 객체 가져오기 | ||
val contact = intent.getParcelableExtra<Contact>("contact") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Parcelable을 써보는것도 좋지만, 여러 객체중 측정 객체를 식별해낼수있는 key 개념만을 전달하고 받아서 동일한 기능을 처리하는 방식으로 로직을 구성해보면 좋겠습니다. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 구현 시에는 하나에만 꽂혀서 해당 로직 방법을 생각해보지 못했었습니다. 다음엔 해당 로직처럼 구성해보겠습니다. 감사합니다! |
||
|
||
//연락처 정보 설정하기 - 조건 | ||
contact?.let { | ||
|
||
//이름 필수조건 | ||
nameView.text = it.name | ||
nameView.visibility = TextView.VISIBLE | ||
nameLabelText.visibility = TextView.VISIBLE | ||
|
||
//이메일 필수조건 | ||
phoneView.text = it.phone | ||
phoneView.visibility = TextView.VISIBLE | ||
phoneLabelText.visibility = TextView.VISIBLE | ||
|
||
if (!it.email.isNullOrEmpty()) { | ||
emailView.text = it.email | ||
emailView.visibility = TextView.VISIBLE | ||
emailLabelText.visibility = TextView.VISIBLE | ||
} else { | ||
emailView.visibility = TextView.GONE | ||
emailLabelText.visibility = TextView.GONE | ||
} | ||
|
||
//birth | ||
if (!it.birth.isNullOrEmpty()) { | ||
birthView.text = it.birth | ||
birthView.visibility = TextView.VISIBLE | ||
birthLabelText.visibility = TextView.VISIBLE | ||
} else { | ||
birthView.visibility = TextView.GONE | ||
birthLabelText.visibility = TextView.GONE | ||
} | ||
|
||
//gender | ||
//default 값이 아닌 경우 값을 집어 넣도록 설정 - '성별' 문구 안뜨도록 | ||
if (!it.gender.isNullOrEmpty() && it.gender != " ") { | ||
genderView.text = it.gender | ||
genderView.visibility = TextView.VISIBLE | ||
genderLabelText.visibility = TextView.VISIBLE | ||
} else { | ||
genderView.visibility = TextView.GONE | ||
genderLabelText.visibility = TextView.GONE | ||
} | ||
|
||
//memo | ||
if (!it.memo.isNullOrEmpty()) { | ||
memoView.text = it.memo | ||
memoView.visibility = TextView.VISIBLE | ||
memoLabelText.visibility = TextView.VISIBLE | ||
} else { | ||
memoView.visibility = TextView.GONE | ||
memoLabelText.visibility = TextView.GONE | ||
} | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
날짜를 좀 더 직접적으로 나타내는 Date나 Kotlin.date.Instant 에 대해서도 알아보면 어떨까 합니다. :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
아하, 날짜를 더 직접적으로 나타내는 방법이 있었는지 몰랐습니다. 더 공부해보겠습니다. 감사합니다!