하지만, 색을 칠하거나, 문자 또는 이미지를 달력안에 표시 하기 위해서는 많은 부분을 손 되어야 한다.
앞으로 만들 서비스는 달력이 기본이 되는 서비스 이므로 직접 달력을 구현하고자 검색한 결과
아래 사이트에 괜찮은 소스가 있어, 참고하였다.
참고 사이트 : http://hatti.tistory.com/entry/android-calendar
최종 소스 : 캘린더 소스
[달력 결과물]
달력을 구현하기 위해서 사용된 항목
1. ViewPager & FragmentStatePagerAdapter
- https://developer.android.com/reference/android/support/v4/view/ViewPager.html
- https://developer.android.com/reference/android/support/v4/app/FragmentStatePagerAdapter.html
- 좌우스크롤 시 달력의 월을 변경하기 위해서 ViewPager를 사용한다.
- 초기 달력 데이터를 생성하기 위해서 FragmentStatePagerAdapter에서 달력 데이터를 생성한다. 참고사이트의 소스에서는 25개월 치를 미리 생성한다.
private HashMapfrgMap; private ArrayList listMonthByMillis = new ArrayList<>(); private int numOfMonth; @Override public Fragment getItem(int position) { FrgCalendar frg = null; if (frgMap.size() > 0) { frg = frgMap.get(position); } if (frg == null) { frg = FrgCalendar.newInstance(position); frg.setOnFragmentListener(onFragmentListener); frgMap.put(position, frg); } frg.setTimeByMillis(listMonthByMillis.get(position)); return frg; } @Override public int getCount() { return listMonthByMillis.size(); } // 초기 달력 데이터 생성 메소드 public void setNumOfMonth(int numOfMonth) { this.numOfMonth = numOfMonth; Calendar calendar = Calendar.getInstance(); calendar.add(Calendar.MONTH, -numOfMonth); calendar.set(Calendar.DATE, 1); for (int i = 0; i < numOfMonth * 2 + 1; i++) { listMonthByMillis.add(calendar.getTimeInMillis()); calendar.add(Calendar.MONTH, 1); } notifyDataSetChanged(); }
// Fragment에서 ViewGroup 및 View 생성 :: ViewGroup은 XML 설정하거나, 직접 생성해줘야 함 protected void initView() { Calendar calendar = Calendar.getInstance(); calendar.setTimeInMillis(timeByMillis); calendar.set(Calendar.DATE, 1); // 1일의 요일 int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK); //이달의 마지막 날 int maxDateOfMonth = calendar.getActualMaximum(Calendar.DAY_OF_MONTH); calendarView.initCalendar(dayOfWeek, maxDateOfMonth); // 요일 및 날짜 만큼 View를 생성하여 ViewGroup에 추가하는 로직 for (int i = 0; i < maxDateOfMonth + 7; i++) { CalendarItemView child = new CalendarItemView(getActivity().getApplicationContext()); if (i == 20) { child.setEvent(R.color.colorPrimaryDark); } child.setDate(calendar.getTimeInMillis()); if (i < 7) { child.setDayOfWeek(i); } else { calendar.add(Calendar.DATE, 1); } calendarView.addView(child); } }2. ViewGroup
- https://developer.android.com/training/basics/firstapp/building-ui.html - http://bcho.tistory.com/1043
ViewGroup에서는 onMeasure() - 크기설정 메소드와 onLayout() - 좌표설정 메소드를 오버라이드 해서 View를 ViewGroup에 그린다.
아래 사이트 참조
- http://i5on9i.blogspot.kr/2013/05/android-view-onmeasure-onlayout.html
- http://kingorihouse.tumblr.com/post/86806256119/android-viewgroup-%EC%A7%81%EC%A0%91-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0
여기서 각 View의 크기와 좌표를 계산하기 위해서 사용되는 변수는 ViewPager에서 생성한 달력 데이터를 이용한다.
public CalendarView(Context context, AttributeSet attrs) { super(context, attrs); mScreenWidth = getResources().getDisplayMetrics().widthPixels; ///< 스마트폰의 가로 화면 사이즈 mWidthDate = mScreenWidth / 7; // 스마트폰 화면 사이즈에서 7(월~일 요일)로 나누어 View의 Width를 구함 DAY_OF_WEEK = getResources().getStringArray(R.array.day_of_week); } // View의 크기를 설정함 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int count = getChildCount(); // Measurement will ultimately be computing these values. int maxHeight = 0; int maxWidth = 0; int childState = 0; int mLeftWidth = 0; int rowCount = 0; Calendar calendar = Calendar.getInstance(); calendar.setTimeInMillis(mMillis); for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child.getVisibility() == GONE) continue; // Measure the child. measureChild(child, widthMeasureSpec, heightMeasureSpec); maxWidth += Math.max(maxWidth, child.getMeasuredWidth()); mLeftWidth += child.getMeasuredWidth(); if ((mLeftWidth / mScreenWidth) > rowCount) { maxHeight += child.getMeasuredHeight(); rowCount++; } else { maxHeight = Math.max(maxHeight, child.getMeasuredHeight()); } childState = combineMeasuredStates(childState, child.getMeasuredState()); } maxHeight = (int) (Math.ceil((count + mDateOfWeek - 1) / 7d) * (mWidthDate * 0.75));// 요일중 일요일이 1부터 시작하므로 1을 빼줌 maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth()); int expandSpec = MeasureSpec.makeMeasureSpec(MEASURED_SIZE_MASK, MeasureSpec.AT_MOST); setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), resolveSizeAndState(maxHeight, expandSpec, childState << MEASURED_HEIGHT_STATE_SHIFT)); LayoutParams params = getLayoutParams(); params.height = getMeasuredHeight(); } // View의 위치를 설정함 @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { final int count = getChildCount(); int curWidth, curHeight, curLeft, curTop, maxHeight; final int childLeft = this.getPaddingLeft(); final int childTop = this.getPaddingTop(); final int childRight = this.getMeasuredWidth() - this.getPaddingRight(); final int childBottom = this.getMeasuredHeight() - this.getPaddingBottom(); final int childWidth = childRight - childLeft; final int childHeight = childBottom - childTop; maxHeight = 0; curLeft = childLeft; curTop = childTop; for (int i = 0; i < count; i++) { View child = getChildAt(i); if (child.getVisibility() == GONE) return; child.measure(MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.AT_MOST)); curWidth = mWidthDate; curHeight = (int) (mWidthDate * 0.75); if (curLeft + curWidth >= childRight) { curLeft = childLeft; curTop += maxHeight; maxHeight = 0; } if (i == 7) { curLeft = (mDateOfWeek - 1) * curWidth; } child.layout(curLeft, curTop, curLeft + curWidth, curTop + curHeight); if (maxHeight < curHeight) { maxHeight = curHeight; } curLeft += curWidth; } } // 변경된 달력 정보로 초기 값을 변경함 public void setDate(long millis) { mMillis = millis; Calendar calendar = Calendar.getInstance(); calendar.setTimeInMillis(millis); calendar.set(Calendar.DATE, 1); mDateOfWeek = calendar.get(Calendar.DAY_OF_WEEK); mMaxtDateOfMonth = calendar.getActualMaximum(Calendar.DAY_OF_MONTH); invalidate(); }3. View
- https://developer.android.com/reference/android/view/View.html
View에서는 onDraw() 메소드를 오버라이드 해서 canvas에 달력 데이터를 그리고, 색을 칠하거나 하는 작업을 한다.
// Canvas에 달력 데이터를 그림 @TargetApi(Build.VERSION_CODES.LOLLIPOP) @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); int xPos = (canvas.getWidth() / 2); int yPos = (int) ((canvas.getHeight() / 2) - ((mPaint.descent() + mPaint.ascent()) / 2)); mPaint.setTextAlign(Paint.Align.CENTER); Calendar calendar = Calendar.getInstance(); calendar.setTimeInMillis(millis); CalendarView calendarView = (CalendarView) getParent(); if (calendarView.getParent() instanceof ViewPager) { ViewGroup parent = (ViewPager) calendarView.getParent(); CalendarItemView tagView = (CalendarItemView) parent.getTag(); if (!isStaticText && tagView != null && tagView.getTag() != null && tagView.getTag() instanceof Long) { long millis = (long) tagView.getTag(); if (isSameDay(millis, this.millis)) { canvas.drawRoundRect(xPos - dp16, getHeight() / 2 - dp16, xPos + dp16, getHeight() / 2 + dp16, 50f, 50f, mPaintBackground); } } } if (!isStaticText && isToday(millis)) { canvas.drawRoundRect(xPos - dp16, getHeight() / 2 - dp16, xPos + dp16, getHeight() / 2 + dp16, 50f, 50f, mPaintBackgroundToday); } if (isStaticText) { // 요일 표시 canvas.drawText(CalendarView.DAY_OF_WEEK[dayOfWeek], xPos, yPos, mPaint); } else { // 날짜 표시 canvas.drawText(calendar.get(Calendar.DATE) + "", xPos, yPos, mPaint); } if (hasEvent) { mPaintBackgroundEvent.setColor(getResources().getColor(mColorEvents[0])); canvas.drawRoundRect(xPos - 5, getHeight() / 2 + 20, xPos + 5, getHeight() / 2 + 30, 50f, 50f, mPaintBackground); } }
질문드립니다..
답글삭제혹시 위젯에서도 onDraw를 사용해서 월간 달력 그릴 수 있나요??