참고 사이트 : http://hatti.tistory.com/entry/android-calendar
최종 소스 : 캘린더 소스
[달력 결과물]
위의 달력을 만들기 위해서 필요한 구성은 이전 월단위 달력 구성과 차이가 없다. 다만, 달력 데이터를 주 단위로 구성하는데 그 차이가 있다.
# 주 단위 달력 데이터 만들기
public void setNumOfWeek(int numOfWeeks) {
this.numOfMonth = numOfMonth;
Calendar calendar = Calendar.getInstance();
// 현재주를 일요일로 설정
calendar.set(Calendar.DAY_OF_WEEK,Calendar.SUNDAY);
// 현재 주에서 주 범위를 설정하기 위해서 시작 주를 세팅
calendar.add(Calendar.WEEK_OF_MONTH, -numOfWeeks);
// 주 범위는 -numofWeeks ~ +numofWeeks 까지 달력 데이터를 세팅
for (int i = 0; i < numOfMonth * 2 + 1; i++) {
listMonthByMillis.add(calendar.getTimeInMillis());
// 1주 씩 증가
calendar.add(Calendar.WEEK_OF_MONTH,1);
}
// 변경된 데이터를 viewPager에 반영
notifyDataSetChanged();
}
다시 한번 달력구성은 아래 그림과 같이 이루어진다. 여기서는 ViewPager를 제외한 나머지 부분의 소스코드에 대해서 알아 본다.
# Fragment로 사용자가 정의한 주단위 달력 ViewGroup을 표시한다.
public class TaTCalendarWeekFragment extends Fragment {
private int position;
private long timeByMillis;
private TaTCalendarWeekFragment.OnFragmentListener onFragmentListener;
private View mRootView;
private TaTCalendarWeekView calendarView;
public void setOnFragmentListener(TaTCalendarWeekFragment.OnFragmentListener onFragmentListener) {
this.onFragmentListener = onFragmentListener;
}
public interface OnFragmentListener{
public void onFragmentListener(View view);
}
public static TaTCalendarWeekFragment newInstance(int position) {
TaTCalendarWeekFragment frg = new TaTCalendarWeekFragment();
Bundle bundle = new Bundle();
bundle.putInt("position", position);
frg.setArguments(bundle);
return frg;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
position = getArguments().getInt("poisition");
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
mRootView = inflater.inflate(R.layout.fragment_tat_calendar_week, null);
calendarView = (TaTCalendarWeekView) mRootView.findViewById(R.id.calendarweekview);
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(timeByMillis);
for (int i = 0; i < 14; i++) {
TaTCalendarWeekItemView child = new TaTCalendarWeekItemView(getActivity().getApplicationContext());
child.setDate(calendar.getTimeInMillis());
if (i < 7) {
child.setDayOfWeek(i);
} else {
calendar.add(Calendar.DATE, 1);
}
calendarView.addView(child);
}
return mRootView;
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
if (isVisibleToUser && onFragmentListener != null && mRootView != null) {
onFragmentListener.onFragmentListener(mRootView);
}
super.setUserVisibleHint(isVisibleToUser);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (getUserVisibleHint()) {
mRootView.post(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
onFragmentListener.onFragmentListener(mRootView);
}
});
}
}
public void setTimeByMillis(long timeByMillis) {
this.timeByMillis = timeByMillis;
}
}
# ViewGroup을 이용한 달력 레이아웃 만들기 :: Fragment에 담을 사용자 ViewGroup
public class TaTCalendarWeekView extends ViewGroup {
private final int mScreenWidth; ///< 스크린 가로 사이즈
private final int mWidthDate; ///< 달력의 한 칸 가로 사이즈
private long mMillis; ///< 달력 시간 데이터
private int mDefaultTextSize = 40; ///< 기본 텍스트 크기
public static String[] DAY_OF_WEEK = null; ///< 요일 리스트[일,월,화,수,목,금,토]
public TaTCalendarWeekView(Context context, AttributeSet attrs) {
super(context,attrs);
mScreenWidth = getResources().getDisplayMetrics().widthPixels;
mWidthDate = mScreenWidth / 7;
DAY_OF_WEEK = getResources().getStringArray(R.array.day_of_week);
}
/**
* 자식 view 사이즈(width/height)를 재 설정하는 함수
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
int maxHeight = 0;
int maxWidth = 0;
int childState = 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;
///< 자식뷰의 사이즈를 측정
measureChild(child,widthMeasureSpec,heightMeasureSpec);
///< 이전 자식뷰 상태를 합쳐서 두 상태의 조합을 반영하는 새로운 정수를 반환
childState = combineMeasuredStates(childState, child.getMeasuredState());
}
// 요일과 일자로 항상 7칸 2줄로 이루어짐(mWidthDate 폰 가로 사이즈 / 7 한 크기)
maxHeight = (int) (2 * (mWidthDate * 0.75));
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
// 제공된 크기와 모드에 따라 측정 규격을 작성합니다.
// MEASURED_SIZE_MASK : getMeasuredWidthAndState()와 getMeasuredWidthAndState()그 실제 측정 된 크기를 제공합니다
// AT_MOST : 사양 측정 모드 : 그것은 지정된 크기까지 원하는대로 아이로 클 수 있습니다.(wrap_content)
int expandSpec = MeasureSpec.makeMeasureSpec(MEASURED_SIZE_MASK, MeasureSpec.AT_MOST);
/**
* onMeasure 메소드를 오버라이드 하면, setMeasuredDimension 메소드가 호출이 되지 않으므로, 직접 호출해줘야 함
* setMeasuredDimension(int width, int height)
* - width : 가로크기
* - height : 세로크기
* resolveSizeAndState(int size, int measureSpec, int childMeasuredState)
* - size : 뷰 가로 크기
* - measureSpec : 부모에 의해서 측정된 값
* - childMeasuredState : 뷰의 자식 크기 정보
* - MEASURED_HEIGHT_STATE_SHIFT : MEASURED_STATE_MASK 높이에 도착하는 같은 단일의 int로 폭과 높이를 모두 결합 기능에 대한 비트
*
* 여기서는 계산된 크기를 부모 뷰에 맞게 사이즈를 조정하도록 값을 설정하였다.
*/
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, expandSpec, childState << MEASURED_HEIGHT_STATE_SHIFT));
// 최종 측정된 높이 값을 레이아웃에 설정한다.
LayoutParams params = getLayoutParams();
params.height = getMeasuredHeight();
}
/**
* 뷰가 그 자식들에게 크기와 위치를 할당할 때 호출됩니다.
* @param b
* @param i
* @param i1
* @param i2
* @param i3
*/
@Override
protected void onLayout(boolean b, int i, int i1, int i2, int i3) {
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 h = 0; h < count; h++) {
View child = getChildAt(h);
if (child.getVisibility() == GONE)
return;
/**
* 제공된 크기와 모드에 따라 측정 규격을 작성합니다.
* childWidth : 계산된 뷰의 가로 크기
* childHeight : 계산된 뷰의 세로 크기
* AT_MOST : 사양 측정 모드 : 그것은 지정된 크기까지 원하는대로 아이로 클 수 있습니다.(wrap_content)
*/
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;
}
/**
* 뷰의 크기 및 위치를 할당하는 메소드 호출
* 크기를 측정하고 나서 호출
* laout(int l, int t, int r, int b)
* - l : 부모기준으로 왼쪽 위치
* - t : 부모기준으로 위쪽 위치
* - r : 부모기준으로 오른쪽 위치
* - b : 부모기준으로 아래 위치
*/
child.layout(curLeft, curTop, curLeft + curWidth, curTop + curHeight);
///< 현재 세로크기는 최대 세로크기보다 클수 없음
if (maxHeight < curHeight) {
maxHeight = curHeight;
}
///< 뷰를 우측으로 정렬하기 위해서 가로크기 만큼 좌페딩값을 더함
curLeft += curWidth;
}
}
public void setDate(long millis) {
mMillis = millis;
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
}
private Paint makePaint(int color) {
Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
p.setColor(color);
p.setTextSize(mDefaultTextSize);
return p;
}
/**
* 선택된 View의 별도의 이벤트를 처리하기 위한 함수
* - 여기서는 선택된 View의 색을 변경
* @param view
*/
public void setCurrentSelectedView(View view) {
if (getParent() instanceof ViewPager) {
ViewPager pager = (ViewPager) getParent();
View tagView = (View) pager.getTag();
if (tagView != null) {
long time = (long) tagView.getTag();
Calendar c = Calendar.getInstance();
c.setTimeInMillis(time);
for (int i = 0; i < pager.getChildCount(); i++) {
for (int j = 0; j < getChildCount(); j++) {
TaTCalendarWeekItemView child = (TaTCalendarWeekItemView) ((TaTCalendarWeekView) pager.getChildAt(i)).getChildAt(j);
if (child == null) {
continue;
}
if (child.isStaticText()) {
continue;
}
if (child.isSameDay((Long) child.getTag(), (Long) tagView.getTag())) {
child.invalidate();
break;
}
}
}
}
if (tagView == view) {
pager.setTag(null);
return;
}
long time = (long) view.getTag();
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(time);
pager.setTag(view);
view.invalidate();
}
}
}
# 사용자 ViewGroup을 생성하기 위한 XML 정의
# View를 이용한 달력의 구성 Cell 그리기
public class TaTCalendarWeekItemView extends View {
Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
Paint mPaintBackground = new Paint(Paint.ANTI_ALIAS_FLAG);
Paint mPaintBackgroundToday = new Paint(Paint.ANTI_ALIAS_FLAG);
Paint mPaintBackgroundEvent = new Paint(Paint.ANTI_ALIAS_FLAG);
private Rect rect;
private long millis;
private int dp11;
private int dp16;
private int dayOfWeek = -1;
private boolean isStaticText = false;
private boolean isTouchMode;
private boolean hasEvent = false;
private int[] mColorEvents;
public TaTCalendarWeekItemView(Context context) {
super(context);
initialize();
}
private void initialize() {
dp11 = (int) dp2px(getContext(),11);
dp16 = (int) dp2px(getContext(),16);
mPaint.setColor(Color.BLACK);
mPaint.setTextSize(dp11);
if (Build.VERSION.SDK_INT >= 23) {
mPaintBackground.setColor(ContextCompat.getColor(getContext(), R.color.colorPrimaryDark));
mPaintBackgroundToday.setColor(ContextCompat.getColor(getContext(),R.color.colorAccent));
mPaintBackgroundEvent.setColor(ContextCompat.getColor(getContext(),R.color.colorPrimary));
}else{
mPaintBackground.setColor(getContext().getResources().getColor(R.color.colorPrimaryDark));
mPaintBackgroundToday.setColor(getContext().getResources().getColor(R.color.colorAccent));
mPaintBackgroundEvent.setColor(getContext().getResources().getColor(R.color.colorPrimary));
}
setClickable(true);
setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
int action = event.getAction();
Log.d("hatti.onTouchEvent", event.getAction() + "");
switch (action) {
case MotionEvent.ACTION_DOWN:
rect = new Rect(v.getLeft(), v.getTop(), v.getRight(), v.getBottom());
isTouchMode = true;
break;
case MotionEvent.ACTION_UP:
if (isTouchMode) {
((TaTCalendarWeekView) getParent()).setCurrentSelectedView(v);
isTouchMode = false;
}
break;
case MotionEvent.ACTION_CANCEL:
isTouchMode = false;
break;
case MotionEvent.ACTION_MOVE:
if (!rect.contains(v.getLeft() + (int) event.getX(), v.getTop() + (int) event.getY())) {
isTouchMode = false;
return true;
}
break;
}
return false;
}
});
setPadding(30, 0, 30, 0);
}
@Override
public void setBackgroundResource(int resid) {
super.setBackgroundResource(resid);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
/**
* Canvas에 중점 구하기.
* Paint는 Canvas에 그리는 작업을 하는 객체
* - ascent 는 baseline 위로의 크기이며 descent 는 밑으로의 크기, 두개를 합치면 높이
*/
int xPos = canvas.getWidth() / 2;
///< 실제 그리는 문자나 숫자의 중간이 중점에 위치하기 위해서 Paint의 높이의 절반 만큼을 뺀다.
int yPos = (int)((canvas.getHeight() /2) - ((mPaint.descent() + mPaint.ascent()) / 2));
///< 문자는 가운데 정렬로 그린다.
mPaint.setTextAlign(Paint.Align.CENTER);
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(millis);
TaTCalendarWeekView taTCalendarWeekView = (TaTCalendarWeekView) getParent();
if(taTCalendarWeekView.getParent() instanceof ViewPager) {
ViewGroup parent = (ViewPager) taTCalendarWeekView.getParent();
TaTCalendarWeekItemView itemView = (TaTCalendarWeekItemView) parent.getTag();
if (!isStaticText && itemView != null && itemView.getTag() != null && itemView.getTag() instanceof Long) {
long millis = (long) itemView.getTag();
if (isSameDay(millis, this.millis)) {
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
canvas.drawRoundRect(xPos - dp16, getHeight() / 2 - dp16, xPos + dp16, getHeight() / 2 + dp16, 50f, 50f, mPaintBackground);
}else{
canvas.drawRect(xPos - dp16, getHeight() / 2 - dp16, xPos + dp16, getHeight() / 2 + dp16, mPaintBackground);
}
}
}
}
if (!isStaticText && isToday(millis)) {
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
canvas.drawRoundRect(xPos - dp16, getHeight() / 2 - dp16, xPos + dp16, getHeight() / 2 + dp16, 50f, 50f, mPaintBackgroundToday);
}else{
canvas.drawRect(xPos - dp16, getHeight() / 2 - dp16, xPos + dp16, getHeight() / 2 + dp16, mPaintBackgroundToday);
}
}
if (isStaticText) {
// 요일 표시
mPaint.setTypeface(Typeface.create((String)null, Typeface.BOLD));
canvas.drawText(TaTCalendarWeekView.DAY_OF_WEEK[dayOfWeek], xPos, yPos, mPaint);
} else {
// 날짜 표시
mPaint.setTypeface(Typeface.create(Typeface.DEFAULT_BOLD, Typeface.NORMAL));
mPaint.setColor(Color.BLACK);
canvas.drawText(calendar.get(Calendar.DATE) + "", xPos, yPos, mPaint);
}
if (hasEvent) {
if (Build.VERSION.SDK_INT >= 23) {
mPaintBackgroundEvent.setColor(ContextCompat.getColor(getContext(),mColorEvents[0]));
}else{
mPaintBackgroundEvent.setColor(getResources().getColor(mColorEvents[0]));
}
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
canvas.drawRoundRect(xPos - 5, getHeight() / 2 + 20, xPos + 5, getHeight() / 2 + 30, 50f, 50f, mPaintBackground);
}else{
canvas.drawRect(xPos - 5, getHeight() / 2 + 20, xPos + 5, getHeight() / 2 + 30, mPaintBackground);
}
}
}
private boolean isToday(long millis) {
Calendar cal1 = Calendar.getInstance();
return isSameDay(cal1.getTimeInMillis(), millis);
}
public void setDate(long millis) {
this.millis = millis;
setTag(millis);
}
public void setDayOfWeek(int dayOfWeek) {
this.dayOfWeek = dayOfWeek;
isStaticText = true;
}
public void setEvent(int... resid) {
hasEvent = true;
mColorEvents = resid;
}
public boolean isStaticText() {
return isStaticText;
}
public boolean isSameDay(long millis1, long millis2) {
Calendar cal1 = Calendar.getInstance();
Calendar cal2 = Calendar.getInstance();
cal1.setTimeInMillis(millis1);
cal2.setTimeInMillis(millis2);
return (cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) && cal1.get(Calendar.MONTH) == cal2.get(Calendar.MONTH) && cal1.get(Calendar.DATE) == cal2.get(Calendar.DATE));
}
public static float dp2px(Context context, float dp) {
return dp * context.getResources().getDisplayMetrics().density;
}
}


안녕하세요 혹시 위의 주간달력 소스 전체를 얻을수 있을까요?? 코드만 보고 하려니 힘드네요.. ㅠㅜ 어댑터에서 변경사항이 더 있는거 같은데 어딜 손대야할지 잘 모르겠네요 ㅠㅜㅠㅜ 소스코드 공유될까요?? 재배포는 하지않고 학습용으로 쓰겠습니다.
답글삭제제가 블로그 사용자주 안해서 댓글을 이제 보았네요...지금도 필요하시면 메일로 보내드리겠습니다.
삭제안녕하세요. 혹시 저도 소스코드 공유 부탁드릴 수 있을까요?
답글삭제블로그에 소스 코드 공유 해놨습니다. 다운로드 되는지 확인해보세요.
삭제아하 제가 제대로 확인을 못했었네요.
삭제감사합니다. 공부하는데 참고하겠습니다.
정말 감사합니다.