하지만, 색을 칠하거나, 문자 또는 이미지를 달력안에 표시 하기 위해서는 많은 부분을 손 되어야 한다.
앞으로 만들 서비스는 달력이 기본이 되는 서비스 이므로 직접 달력을 구현하고자 검색한 결과
아래 사이트에 괜찮은 소스가 있어, 참고하였다.
참고 사이트 : 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를 사용해서 월간 달력 그릴 수 있나요??