Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
Tags
- android studio
- 안드로이드 api
- 앱개발
- Callback
- 앱 출시
- android api
- 안드로이드스튜디오
- 달력 만들기
- 레트로핏
- 안드로이드 스튜디오
- 공유 기능
- Kotlin
- 비동기
- Dialog
- Exposed Drop-Down Menu
- Retrofit
- urlconnection
- 플레이 콘솔 프로덕션
- 안드로이드 http
- 플레이스토어 앱 게시 문제
- Bottom sheet
Archives
- Today
- Total
Strong 감자의 공부
달력 만들기 본문
안녕하세요!
이틀만에 다시 뵙습니다~! 😊
오늘은 달력 만들기를 소개하려해요!
해당 기능을 만들 때 타 블로그를 참고해서 만들었어요 차이점이 있다면 제가 구현한 달력은 시작날짜와 끝날짜를 선택해서 기간을 설정하는 방식이예요
1. 완성본
2. 구현
1, 2... 일 자가 리사이클러뷰 item이고 이를 반복해 달력을 만들었어요
저 하얀색 네모에 1, 2, 3 .. 날짜가 표시될 것이고 모여서 달력이 구성돼요
날짜를 클릭하면 색이 변경돼야하므로 이를 button으로 구성했어요
파일명 : calendar_call_item.xml
<?xml version="1.0" encoding="utf-8"?>
<layout ... >
<data>
<variable
name="model"
type="kr.co.gamja.study_hub.feature.studypage.createStudy.InfoOfDays" />
</data>
<LinearLayout
android:layout_width="45dp"
android:layout_height="45dp"
android:background="@color/syswhite"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/txt_day"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="@drawable/solid_syswhite"
android:textColor="@color/sysblack1"
android:textSize="18dp"
tools:text="@{model.infoDay}" />
</LinearLayout>
</layout>
파일명 : fragment_calendar.xml
<?xml version="1.0" encoding="utf-8"?>
<layout ...>
<data>
<variable
name="viewModel"
type="kr.co.gamja.study_hub.feature.studypage.createStudy.CreateStudyViewModel" />
</data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" ... >
<View
android:id="@+id/view_top"
... />
// "완료" 버튼
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btn_ok"
... />
// 이전 달로 가는 "<" 버튼
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btn_left"
... />
// "month" 글자
<TextView
android:id="@+id/txt_Month"
.../>
// 다음 달로 가는 ">" 버튼
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btn_right"
.../>
// 일 ~ 토 요일 표현
<LinearLayout
android:id="@+id/layout_linear"
android:layout_width="match_parent"
android:layout_height="wrap_content"
...>
<TextView
android:id="@+id/txt_sunday"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/sunday"
android:textColor="@color/BG_80"
android:gravity="center"
android:textSize="14dp" />
<TextView
android:id="@+id/txt_monday"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:text="@string/monday"
android:textColor="@color/BG_80"
android:textSize="14dp" />
<TextView
android:id="@+id/txt_tuesday"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/tuesday"
android:gravity="center"
android:textColor="@color/BG_80"
android:textSize="14dp" />
<TextView
android:id="@+id/txt_wednesday"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/wednesday"
android:gravity="center"
android:textColor="@color/BG_80"
android:textSize="14dp" />
<TextView
android:id="@+id/txt_Thursday"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/Thursday"
android:gravity="center"
android:textColor="@color/BG_80"
android:textSize="14dp" />
<TextView
android:id="@+id/txt_friday"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/friday"
android:gravity="center"
android:textColor="@color/BG_80"
android:textSize="14dp" />
<TextView
android:id="@+id/txt_Saturday"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:text="@string/Saturday"
android:textColor="@color/BG_80"
android:textSize="14dp" />
</LinearLayout>
// 1~몇 일 표현
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_day"
android:layout_width="match_parent"
android:layout_height="wrap_content"
... />
</RelativeLayout>
</layout>
이제 리사이클러뷰의 어댑터를 보러 갈게요
파일명 : CalandarAdapter.kt
package kr.co.gamja.study_hub.feature.studypage.createStudy
import ...
class CalendarAdapter(private val context: Context) :
RecyclerView.Adapter<CalendarAdapter.CalendarHolder>() {
...
var daysInfo = ArrayList<InfoOfDays>() // 날짜
private var onCalendarItemClickListener: OnCalendarItemClickListener? = null
...
// CalendarHolder라는 ViewHolder 객체 생성 및 바인딩 객체를 ViewHolder에 전달하여, 나중에 데이터를 뷰와 연결.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CalendarHolder {
val binding =
CalendarCellItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return CalendarHolder(binding)
}
// position을 통해 해당하는 데이터를 viewHolder에 넣어주기
override fun onBindViewHolder(holder: CalendarHolder, position: Int) {
val day = daysInfo.get(position)
holder.setDays(day)
}
// onBindViewHolder로 받은 데이터 실제 뷰와 연결하기
inner class CalendarHolder(val binding: CalendarCellItemBinding) :
RecyclerView.ViewHolder(binding.root) {
fun setDays(item: InfoOfDays) {
binding.model = item // 1일... day
// 날짜가 없는 경우 선택 불가
if (daysInfo[absoluteAdapterPosition].infoDay.isNotEmpty()) {
/*
selectedPosition: 사용자가 선택한 아이템의 위치. absoluteAdapterPosition: 현재 ViewHolder가 관리하는 아이템의 위치.
현재 ViewHolder가 selectedPosition의 아이템을 관리하고 있는지 확인.
⭐⭐⭐
recycelrView는 뷰를 재활용하므로 꼭 확인해야함. 안그러면 재활용 된거와 전의 뷰 홀더 구분을 못하는거 같음
*/
if (selectedPosition == absoluteAdapterPosition) {
// 선택한것으로 표시
} else {
// 선택 취소하기
}
// 날짜 클릭이벤트가 발생했을 경우
if (onCalendarItemClickListener != null) {
binding.txtDay.setOnClickListener {
onCalendarItemClickListener?.onItemClick(item, absoluteAdapterPosition)
if (selectedPosition != absoluteAdapterPosition) {
binding.setChecked(false)
notifyItemChanged(selectedPosition)
selectedPosition = absoluteAdapterPosition
// 기존 선택한 날찌 체크 풀기
selectedYearMonthDay.selectedYearMonth = null
selectedYearMonthDay.selectedDay = null
}
}
}
}
if (startDate.selectedYearMonth.isNullOrEmpty() && startDate.selectedDay.isNullOrEmpty()) {
// 날짜 선택 x 경우 - 현재날짜보다 전날인경우 회색
} else {
// 날짜 선택 o 경우 - 시작날짜 포함 전 날짜들은 회색 처리 +todo("현재날짜보다 전날인경우 회색")
if (item.infoDay.isNotEmpty()) {
}
}
}
}
// 외부(CalendarFragment)에서 클릭 이벤트 처리하기 위함
fun setOnCalendarItemClickListener(listener: OnCalendarItemClickListener) {
onCalendarItemClickListener = listener // listener는 CalendarFragment에서 전달받은 것
}
// 총 몇개의 데이터가 나타나야하는지 RecyclerView에게 알려주기 (viewHolder개수 아님)
override fun getItemCount(): Int {
return daysInfo.size
}
private fun CalendarCellItemBinding.setChecked(selected: Boolean) {
// 선택된 날짜 색 바꾸기
}
private fun CalendarCellItemBinding.setUncheked() {
// 시작 날짜 이전 날짜는 선택 못하게 하기
}
}
interface OnCalendarItemClickListener {
fun onItemClick(item: InfoOfDays, position: Int)
}
파일명 : CalandarFragment.kt
시작하는 날 혹은 종료하는 날 박스를 클릭하면 달력이 나타나요. 아래 코드의 bundle로 시작하는 날과 끝 날짜로 구분해요
package kr.co.gamja.study_hub.feature.studypage.createStudy
import ...
class CalendarFragment : BottomSheetDialogFragment() {
private lateinit var binding: FragmentCalendarBinding
private val viewModel: CreateStudyViewModel by activityViewModels()
private lateinit var selectedDate: LocalDate// 년 월
private lateinit var today: String // 오늘 날짜
private lateinit var currentYearMonth: String // 오늘이 속한달
private lateinit var changedYear: String // 바뀐 년
private lateinit var changedMonth:String // 바뀐 달
private lateinit var formattedDate: String // 포멧한 값
private lateinit var whatDay: String // 시작인지 끝날짜인지 구분 tag(스터디 생성에서 가져옴)
private lateinit var changedYearMonth:String // 지난날짜 회색처리 위한 바뀐 날짜 yyyyMM
var newSelectedYearMonth=InfoOfSelectedDay(null,null) // 선택된 날짜
private lateinit var newStartDate:StartDate// 시작 날짜 < 끝 날짜
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_calendar, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.viewModel = viewModel
binding.lifecycleOwner = viewLifecycleOwner
val receiveBundle = arguments // 이전(스터디 생성: 위 사진) 화면에서 받아오는 값(달력Fragment 하나로 사용하기 때문)
var startYearMonth =""
var startDay = ""
if (receiveBundle != null) {
val value = receiveBundle.getString("whatDayKey")
startYearMonth= receiveBundle.getString("startYearMonth").toString()
startDay=receiveBundle.getString("startDay").toString()
newStartDate=StartDate(startYearMonth,startDay)
if (value != null) whatDay = value
else Log.e(tag, "시작날짜인지 끝인지 못받아옴")
}
if(whatDay=="0"){ // 시작 날짜 선택이면
selectedDate = LocalDate.now()
today = LocalDate.now().dayOfMonth.toString()
currentYearMonth = toYearMonth(LocalDate.now())
}else{ // 끝 날짜 선택이면
val combined = "$startYearMonth$startDay"
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd")
selectedDate =LocalDate.parse(combined,formatter)
today = selectedDate.dayOfMonth.toString()
currentYearMonth = startYearMonth
}
setMonthView() // 달력 업데이트
// 이전 달 보기 버튼
binding.btnLeft.setOnClickListener {
selectedDate = selectedDate.minusMonths(1)
setMonthView() // 달력 업데이트
}
// 다음 달 보기 버튼
binding.btnRight.setOnClickListener {
selectedDate = selectedDate.plusMonths(1)
setMonthView() // 달력 업데이트
}
binding.btnOk.setOnClickListener {
// 날짜 선택 완료
}
}
// ✔️ 달력 업데이트 함수
fun setMonthView() {
binding.txtMonth.setText(monthYearFromDate(selectedDate)) // "yyyy-MM" 달력 상단 년도 월 표시
val newDayList = daysInMonthArray(selectedDate) // 해당 달에 해당하는 "일"들 만들어서 리턴
val adapter = CalendarAdapter(requireContext()).apply {
setOnCalendarItemClickListener(object:OnCalendarItemClickListener{
override fun onItemClick(item: InfoOfDays, position: Int) {
newSelectedYearMonth=InfoOfSelectedDay(item.yearMonthDay,item.infoDay) // 선택된 날짜
formattedDate = "${changedYear}년 ${changedMonth}월 ${item.infoDay}일" // 이전 화면 표시용 날짜
binding.btnOk.isEnabled = true
binding.btnOk.setTextColor(ContextCompat.getColor(requireContext(), R.color.O_50))
}
})
}
val infoOfDaysList:ArrayList<InfoOfDays> =ArrayList()
for(day in newDayList){ // day 는 1,2 ~, daysInMonthArray함수에서 받은 ArrayList<String> -> ArrayList<InfoOfDays> 맞추기 위해
val info = InfoOfDays(yearMonthDay = changedYearMonth, infoDay =day, isSelected = false)
infoOfDaysList.add(info)
}
adapter.daysInfo=infoOfDaysList
adapter.currentYearMonth=currentYearMonth // yyyyMM
adapter.currentDay=today // 오늘 날짜 표시
adapter.selectedYearMonthDay=newSelectedYearMonth // 선택된 년도 달
adapter.startDate=newStartDate // 시작 날짜
binding.recyclerDay.adapter = adapter
binding.recyclerDay.layoutManager = GridLayoutManager(requireContext(), 7)
}
// ✔️ 달력 만들기
fun daysInMonthArray(newDate: LocalDate): ArrayList<String> {
val newDayList = ArrayList<String>() // 달력 "일" 리스트
val newYearWithMonth = toYearMonth(newDate) // yyyyMM 형식으로 변경
changedYear=getOnlyYear(newDate) // yyyy 형식으로 변경
changedMonth=getOnlyMonth(newDate) //MM 형식으로 변경
changedYearMonth=toYearMonth(newDate) // yyyyMM
val newMonth = YearMonth.from(newDate) // 2023-10 형식
val firstDayOfMonth = selectedDate.withDayOfMonth(1)
val lastDayOfMonth = newMonth.lengthOfMonth()
val dayOfWeek = firstDayOfMonth.dayOfWeek.value
// 지난달 조회 불가 < 화살표 예) 202310>202409
if (newYearWithMonth.toInt() > currentYearMonth.toInt()) {
} else {
...
}
// 7*6 자리로 달력 만들기
for (i in 1 until 42) {
if (i <= dayOfWeek || i > lastDayOfMonth + dayOfWeek)
newDayList.add("")
else newDayList.add((i - dayOfWeek).toString())
}
return newDayList
}
}
data class InfoOfDays(
var yearMonthDay:String, // yyyyMM
val infoDay:String,
var isSelected:Boolean
)
필요없는 코드들 최대한 뺐지만... 여전히 복잡해보이네요🥹🥹
코드에 붙인 이모티콘 위주로 봐주시면 됩니다!
'앱개발 > StudyHub 앱 출시 회고' 카테고리의 다른 글
Retrofit 사용해보기 #1 (0) | 2024.12.30 |
---|---|
안드로이드 api 통신_callback함수 (2) | 2024.12.27 |
앱 게시 문제_#심사는 끝났는데 플레이 스토어에 앱이 안보일 때 (0) | 2024.12.27 |
앱 출시 회고_intro (0) | 2024.12.26 |
api 응답 값 에러 시 확인 절차 (0) | 2023.09.21 |