// Copyright 2012 Square, Inc. package com.marshalchen.common.uimodule.timessquare; import android.content.Context; import android.content.res.ColorStateList; import android.graphics.Canvas; import android.graphics.Paint; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; import com.marshalchen.common.uimodule.R; import com.marshalchen.common.commonUtils.logUtils.Logs; import static android.view.View.MeasureSpec.*; /** * ViewGroup that draws a grid of calendar cells. All children must be {@link CalendarRowView}s. * The first row is assumed to be a header and no divider is drawn above it. */ public class CalendarGridView extends ViewGroup { /** * The grid lines don't exactly line up on certain devices (Nexus 7, Nexus 5). Fudging the * co-ordinates by half a point seems to fix this without breaking other devices. * */ private static final float FLOAT_FUDGE = 0.5f; private final Paint dividerPaint = new Paint(); private int oldWidthMeasureSize; private int oldNumRows; public CalendarGridView(Context context, AttributeSet attrs) { super(context, attrs); dividerPaint.setColor(getResources().getColor(R.color.calendar_divider)); } public void setDividerColor(int color) { dividerPaint.setColor(color); } public void setDayBackground(int resId) { for (int i = 1; i < getChildCount(); i++) { ((CalendarRowView) getChildAt(i)).setCellBackground(resId); } } public void setDayTextColor(int resId) { for (int i = 0; i < getChildCount(); i++) { ColorStateList colors = getResources().getColorStateList(resId); ((CalendarRowView) getChildAt(i)).setCellTextColor(colors); } } public void setHeaderTextColor(int color) { ((CalendarRowView) getChildAt(0)).setCellTextColor(color); } @Override public void addView(View child, int index, LayoutParams params) { if (getChildCount() == 0) { ((CalendarRowView) child).setIsHeaderRow(true); } super.addView(child, index, params); } @Override protected void dispatchDraw(Canvas canvas) { super.dispatchDraw(canvas); final ViewGroup row = (ViewGroup) getChildAt(1); int top = row.getTop(); int bottom = getBottom(); // Left side border. final int left = row.getChildAt(0).getLeft() + getLeft(); canvas.drawLine(left + FLOAT_FUDGE, top, left + FLOAT_FUDGE, bottom, dividerPaint); // Each cell's right-side border. for (int c = 0; c < 7; c++) { float x = left + row.getChildAt(c).getRight() - FLOAT_FUDGE; canvas.drawLine(x, top, x, bottom, dividerPaint); } } @Override protected boolean drawChild(Canvas canvas, View child, long drawingTime) { final boolean retVal = super.drawChild(canvas, child, drawingTime); // Draw a bottom border. final int bottom = child.getBottom() - 1; canvas.drawLine(child.getLeft(), bottom, child.getRight() - 2, bottom, dividerPaint); return retVal; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { Logs.d("Grid.onMeasure w=%s h=%s"+MeasureSpec.toString(widthMeasureSpec)+ MeasureSpec.toString(heightMeasureSpec)); int widthMeasureSize = MeasureSpec.getSize(widthMeasureSpec); if (oldWidthMeasureSize == widthMeasureSize) { Logs.d("SKIP Grid.onMeasure"); setMeasuredDimension(getMeasuredWidth(), getMeasuredHeight()); return; } long start = System.currentTimeMillis(); oldWidthMeasureSize = widthMeasureSize; int cellSize = widthMeasureSize / 7; // Remove any extra pixels since /7 is unlikely to give whole nums. widthMeasureSize = cellSize * 7; int totalHeight = 0; final int rowWidthSpec = makeMeasureSpec(widthMeasureSize, EXACTLY); final int rowHeightSpec = makeMeasureSpec(cellSize, EXACTLY); for (int c = 0, numChildren = getChildCount(); c < numChildren; c++) { final View child = getChildAt(c); if (child.getVisibility() == View.VISIBLE) { if (c == 0) { // It's the header: height should be wrap_content. measureChild(child, rowWidthSpec, makeMeasureSpec(cellSize, AT_MOST)); } else { measureChild(child, rowWidthSpec, rowHeightSpec); } totalHeight += child.getMeasuredHeight(); } } final int measuredWidth = widthMeasureSize + 2; // Fudge factor to make the borders show up. setMeasuredDimension(measuredWidth, totalHeight); Logs.d("Grid.onMeasure %d ms"+(System.currentTimeMillis() - start)); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { long start = System.currentTimeMillis(); top = 0; for (int c = 0, numChildren = getChildCount(); c < numChildren; c++) { final View child = getChildAt(c); final int rowHeight = child.getMeasuredHeight(); child.layout(left, top, right, top + rowHeight); top += rowHeight; } Logs.d("Grid.onLayout %d ms"+(System.currentTimeMillis() - start)); } public void setNumRows(int numRows) { if (oldNumRows != numRows) { // If the number of rows changes, make sure we do a re-measure next time around. oldWidthMeasureSize = 0; } oldNumRows = numRows; } }