自定義View:仿GitHub的提交活躍表格
說明
本文可能需要一些基礎知識點,如Canvas,Paint,Path,Rect等類的基本使用,建議不熟悉的同學可以學習GcsSloop安卓自定義View教程目錄,會幫助很大。
上圖就是github的提交表格,直觀來看可以分為幾個部分進行繪製:
(1)各個月份的小方格子,並且色彩根據提交次數變化,由淺到深
(2)右下邊的顏色標誌,我們右對齊就可以了
(3)左邊的星期,原圖是從週日畫到週六,我們從週一畫到週日
(4)上面的月份,我們只畫出1-12月
(5)點選時候彈出當天的提交情況,由一個小三角和圓角矩形組成
需要解決的計算問題:
(1)生成任意一年的所有天,包含年月日周,提交次數,色塊顏色,座標
(1)一年中所有的小方格子座標
(2)右下邊顏色標誌座標
(3)左邊星期座標
(4)上面月份座標
(5)點選彈出的提示框和文字座標
生成某年所有天數
每天的資訊我們需要封裝成一個類,程式碼如下:
/**
* Created by Administrator on 2017/1/13.
* 封裝每天的屬性,方便在繪製的時候進行計算
*/
public class Day implements Serializable{
/**年**/
public int year;
/**月**/
public int month;
/**日**/
public int date;
/**周幾**/
public int week;
/**貢獻次數,預設0**/
public int contribution = 0;
/**預設顏色,根據提交次數改變**/
public int colour = 0xFFEEEEEE;
/**方格座標,左上點,右下點,確定矩形範圍**/
public float startX;
public float startY;
public float endX;
public float endY;
@Override
public String toString() {
//這裡直接在彈出框中顯示
return ""+year+"年"+month+"月" +date+"日周"+week+","+contribution+"次";
}
}
要想先繪製表格,需要計算出所有的天,這裡計算一年中所有的天,我們通過從當年1月1日算起,到12月31日,因為星期是連續的,所以我們需要我們提供某年的1月1日是周幾,比如2016年1月1日是周5,這裡必要的引數是2016和周5,那麼我們用一個類來實現該方法,程式碼如下:
public class DateFactory {
/**平年map,對應月份和天數**/
private static HashMap<Integer,Integer> monthMap = new LinkedHashMap<>(12);
/**閏年map,對應月份和天數**/
private static HashMap<Integer,Integer> leapMonthMap = new LinkedHashMap<>(12);
static {
//初始化map,只有2月份不同
monthMap.put(1,31);leapMonthMap.put(1,31);
monthMap.put(2,28);leapMonthMap.put(2,29);
monthMap.put(3,31);leapMonthMap.put(3,31);
monthMap.put(4,30);leapMonthMap.put(4,30);
monthMap.put(5,31);leapMonthMap.put(5,31);
monthMap.put(6,30);leapMonthMap.put(6,30);
monthMap.put(7,31);leapMonthMap.put(7,31);
monthMap.put(8,31);leapMonthMap.put(8,31);
monthMap.put(9,30);leapMonthMap.put(9,30);
monthMap.put(10,31);leapMonthMap.put(10,31);
monthMap.put(11,30);leapMonthMap.put(11,30);
monthMap.put(12,31);leapMonthMap.put(12,31);
}
/**
* 輸入年份和1月1日是周幾
* 閏年為366天,平年為365天
* @param year 年份
* @param weekday 該年1月1日為周幾
* @return 該年1月1日到12月31日所有的天數
*/
public static List<Day> getDays(int year, int weekday) {
List<Day> days = new ArrayList<>();
boolean isLeapYear = isLeapYear(year);
int dayNum = isLeapYear ? 366 : 365;
Day day;
int lastWeekday = weekday;
for (int i = 1; i <= dayNum; i++) {
day = new Day();
day.year = year;
//計算當天為周幾,如果大於7就重置1
day.week = lastWeekday<= 7 ? lastWeekday : 1;
//計算當天為幾月幾號
int[] monthAndDay = getMonthAndDay(isLeapYear, i);
day.month = monthAndDay[0];
day.date = monthAndDay[1];
//記錄下昨天是周幾並+1
lastWeekday = day.week;
lastWeekday++;
days.add(day);
}
checkDays(days);
return days;
}
/**
* 獲取月和日
* @param isLeapYear 是否閏年
* @param currentDay 當前天數
* @return 包含月和天的陣列
*/
public static int[] getMonthAndDay(boolean isLeapYear,int currentDay) {
HashMap<Integer,Integer> maps = isLeapYear?leapMonthMap:monthMap;
Set<Map.Entry<Integer,Integer>> set = maps.entrySet();
int count = 0;
Map.Entry<Integer, Integer> month = null;
for (Map.Entry<Integer, Integer> entry : set) {
count+=entry.getValue();
if (currentDay<=count){
month = entry;
break;
}
}
if (month == null){
throw new IllegalStateException("未找到所在的月份");
}
int day = month.getValue()-(count-currentDay);
return new int[]{month.getKey(),day};
}
/**
* 判斷是閏年還是平年
* @param year 年份
* @return true 為閏年
*/
public static boolean isLeapYear(int year) {
return year % 4 == 0 && year % 100 != 0 || year % 400 == 0;
}
/**
* 檢測生成的天數是否正常
* @param days
*/
private static void checkDays(List<Day> days) {
if (days == null) {
throw new IllegalArgumentException("天數為空");
}
if (days.size() != 365 && days.size() != 366) {
throw new IllegalArgumentException("天數異常:" + days.size());
}
}
public static void main(String[] args){
//test
List<Day> days = DateFactory.getDays(2016, 5);
for (int i = 0; i < days.size(); i++) {
System.out.println(days.get(i).toString());
}
}
}
具體的計算邏輯可以看看程式碼,不是很難,這樣我們就能得到某年的所有天。
繪製天數格子
因為該view比較長,所以需要橫屏顯示,方便起見,這裡我們也不再進行view的測量計算,也不再進行自定義屬性,只關注其核心邏輯即可。
首先我們需要將需要的成員變數定義出來:
/**灰色方格的預設顏色**/
private final static int DEFAULT_BOX_COLOUR = 0xFFEEEEEE;
/**提交次數顏色值**/
private final static int[] COLOUR_LEVEL =
new int[]{0xFF1E6823, 0xFF44A340, 0xFF8CC665, 0xFFD6E685, DEFAULT_BOX_COLOUR};
/**星期**/
private String[] weeks = new String[]{"Mon", "Wed", "Fri", "Sun"};
/**月份**/
private String[] months =
new String[]{"Jan", "Feb", "Mar", "Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"};
/**預設的padding,繪製的時候不貼邊畫**/
private int padding = 24;
/**小方格的預設邊長**/
private int boxSide = 8;
/**小方格間的預設間隔**/
private int boxInterval = 2;
/**所有周的列數**/
private int column = 0;
private List<Day> mDays;//一年中所有的天
private Paint boxPaint;//方格畫筆
private Paint textPaint;//文字畫筆
private Paint infoPaint;//彈出框畫筆
private Paint.FontMetrics metrics;//測量文字
private float downX;//按下的點的X座標
private float downY;//按下的點的Y座標
private Day clickDay;//按下所對應的天
這些提取的變數是慢慢增加的,在自定義的時候一下想不全的時候可以先寫,等用到某些變數的時候就提取出來。
然後我們初始化一下資料:
public GitHubContributionView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView();
}
public void initView() {
mDays = DateFactory.getDays(2016, 5);
//方格畫筆
boxPaint = new Paint();
boxPaint.setStyle(Paint.Style.FILL);
boxPaint.setStrokeWidth(2);
boxPaint.setColor(DEFAULT_BOX_COLOUR);
boxPaint.setAntiAlias(true);
//文字畫筆
textPaint = new Paint();
textPaint.setStyle(Paint.Style.FILL);
textPaint.setColor(Color.GRAY);
textPaint.setTextSize(12);
textPaint.setAntiAlias(true);
//彈出的方格資訊畫筆
infoPaint = new Paint();
infoPaint.setStyle(Paint.Style.FILL);
infoPaint.setColor(0xCC888888);
infoPaint.setTextSize(12);
infoPaint.setAntiAlias(true);
//將預設值轉換px
padding = UI.dp2px(getContext(), padding);
boxSide = UI.dp2px(getContext(), boxSide);
metrics = textPaint.getFontMetrics();
}
這裡我們以2016年來舉例,mDays就是獲取2016年的所有天的集合(引數可以當作自定義屬性提取出來),相關的Paint也已經初始化好了,接下來就需要在onDraw方法裡畫,先畫所有的方格子和月份標誌:
/**
* 畫出1-12月方格小塊和上面的月份
* @param canvas 畫布
*/
private void drawBox(Canvas canvas) {
//方格的左上右下座標
float startX, startY, endX, endY;
//起始月份為1月
int month = 1;
for (int i = 0; i < mDays.size(); i++) {
Day day = mDays.get(i);
if (i == 0){
//畫1月的文字標記,座標應該是x=padding,y=padding-boxSide/2(間隙),y座標在表格上面一點
canvas.drawText(months[0],padding,padding-boxSide/2,textPaint);
}
if (day.week == 1 && i != 0) {
//如果當天是周1,那麼說明增加了一列
column++;
//如果列首的月份有變化,那麼說明需要畫月份
if (day.month>month){
month = day.month;
//月份文字的座標計算,x座標在變化,而y座標都是一樣的,boxSide/2(間隙)
canvas.drawText(months[month-1],padding+column*(boxSide+boxInterval),padding-boxSide/2,textPaint);
}
}
//計算方格座標點,x座標隨列數的增多而增加,y座標隨行數的增多而變化
startX = padding + column * (boxSide + boxInterval);
startY = padding + (day.week - 1) * (boxSide + boxInterval);
endX = startX + boxSide;
endY = startY + boxSide;
//將該方格的座標儲存下來,這樣可以在點選方格的時候計算彈框的座標
day.startX = startX;
day.startY = startY;
day.endX = endX;
day.endY = endY;
//給畫筆設定當前天的顏色
boxPaint.setColor(day.colour);
canvas.drawRect(startX, startY, endX, endY, boxPaint);
}
boxPaint.setColor(DEFAULT_BOX_COLOUR);//恢復預設顏色
}
這裡主要是注意下行數列數的變化和月份座標的計算,格子畫好了。
繪製星期文字
我們再畫左邊的星期文字:
/**
* 畫左側的星期
* @param canvas 畫布
*/
private void drawWeek(Canvas canvas) {
//文字是左對齊,所以找出最長的字
float textLength = 0;
for (String week : weeks) {
float tempLength = textPaint.measureText(week);
if (textLength < tempLength) {
textLength = tempLength;
}
}
//依次畫出星期文字,座標點x=padding-文字長度-文字和方格的間隙,y座標隨行數變化
canvas.drawText(weeks[0], padding - textLength - 2, padding + boxSide - metrics.descent, textPaint);
canvas.drawText(weeks[1], padding - textLength - 2, padding + 3 * (boxSide + boxInterval) - metrics.descent, textPaint);
canvas.drawText(weeks[2], padding - textLength - 2, padding + 5 * (boxSide + boxInterval) - metrics.descent, textPaint);
canvas.drawText(weeks[3], padding - textLength - 2, padding + 7 * (boxSide + boxInterval) - metrics.descent, textPaint);
}
繪製顏色深淺標誌
然後根據表格的高度再畫出右下邊的顏色深淺標誌:
/**
* 畫出右下角的顏色深淺標誌,因為是右對齊的所以需要從右往左畫
* @param canvas 畫布
*/
private void drawTag(Canvas canvas) {
//首先計算出兩個文字的長度
float moreLength = textPaint.measureText("More");
float lessLength = textPaint.measureText("Less");
//畫 More 文字,x座標=padding+(列數+1)*(方格邊長+方格間隙)-一個方格間隙-文字長度
float moreX = padding + (column + 1) * (boxSide + boxInterval) - boxInterval - moreLength;
//y座標=padding+(方格行數+1,和表格底部有些距離)*(方格邊長+方格間隙)+字型的ascent高度
float moreY = padding + 8 * (boxSide + boxInterval) + Math.abs(metrics.ascent);
canvas.drawText("More", moreX, moreY, textPaint);
//畫深淺色塊,座標根據上面的More依次計算就可以了
float interval = boxSide - 2;//文字和色塊間的距離
float leftX = moreX - interval - boxSide;
float topY = moreY - boxSide;
float rightX = moreX - interval;
float bottomY = moreY;//色塊的Y座標是一樣的
for (int i = 0; i < COLOUR_LEVEL.length; i++) {
boxPaint.setColor(COLOUR_LEVEL[i]);
canvas.drawRect(leftX - i * (boxSide + boxInterval), topY, rightX - i * (boxSide + boxInterval), bottomY, boxPaint);
}
//最後畫 Less 文字,原理同上
canvas.drawText("Less", leftX - 4 * (boxSide + boxInterval) - interval - lessLength, moreY, textPaint);
}
這樣整個表格主體繪製完成。
處理點選事件
接下來要處理點選事件,判斷點選的座標如果在方格內,那麼彈出對於的文字框,先處理點選事件:
@Override
public boolean onTouchEvent(MotionEvent event) {
//獲取ACTION_DOWN的座標,用來判斷點在哪天,並彈出·
if (MotionEvent.ACTION_DOWN == event.getAction()) {
downX = event.getX();
downY = event.getY();
findClickBox();
}
//這裡因為我們只是記錄座標點,不對事件進行攔截所以預設返回
return super.onTouchEvent(event);
}
判斷是否在方格內:
/**
* 判斷是否點選在方格內
*/
private void findClickBox() {
for (Day day : mDays) {
//檢測點選的座標如果在方格內,則彈出資訊提示
if (downX >= day.startX && downX <= day.endX && downY >= day.startY && downY <= day.endY) {
clickDay = day;//紀錄點擊的哪天
break;
}
}
//點選完要重新整理,這樣每次點選不同的方格,彈窗就可以在相應的位置顯示
refreshView();
}
/**
* 點選彈出文字提示
*/
private void refreshView() {
invalidate();
}
繪製彈出文字框
然後看看彈出文字框的繪製:
/**
* 畫方格上的文字彈框
* @param canvas 畫布
*/
private void drawPopupInfo(Canvas canvas) {
if (clickDay != null) {//點選的天不為null時候才畫
//先根據方格來畫出一個小三角形,座標就是方格的中間
Path infoPath = new Path();
//先從方格中心
infoPath.moveTo(clickDay.startX + boxSide / 2, clickDay.startY + boxSide / 2);
//然後是方格的左上點
infoPath.lineTo(clickDay.startX, clickDay.startY);
//然後是方格的右上點
infoPath.lineTo(clickDay.endX, clickDay.startY);
//畫出三角
canvas.drawPath(infoPath,infoPaint);
//畫三角上的圓角矩形
textPaint.setColor(Color.WHITE);
//得到當天的文字資訊
String popupInfo = clickDay.toString();
System.out.println(popupInfo);
//計算文字的高度和長度用以確定矩形的大小
float infoHeight = metrics.descent - metrics.ascent;
float infoLength = textPaint.measureText(popupInfo);
Log.e("height",infoHeight+"");
Log.e("length",infoLength+"");
//矩形左上點應該是x=當前天的x+邊長/2-(文字長度/2+文字和框的間隙)
float leftX = (clickDay.startX + boxSide / 2 ) - (infoLength / 2 + boxSide);
//矩形左上點應該是y=當前天的y+邊長/2-(文字高度+上下文字和框的間隙)
float topY = clickDay.startY-(infoHeight+2*boxSide);
//矩形的右下點應該是x=leftX+文字長度+文字兩邊和矩形的間距
float rightX = leftX+infoLength+2*boxSide;
//矩形的右下點應該是y=當前天的y
float bottomY = clickDay.startY;
System.out.println(""+leftX+"/"+topY+"/"+rightX+"/"+bottomY);
RectF rectF = new RectF(leftX, topY, rightX, bottomY);
canvas.drawRoundRect(rectF,4,4,infoPaint);
//繪製文字,x=leftX+文字和矩形間距,y=topY+文字和矩形上面間距+文字頂到基線高度
canvas.drawText(popupInfo,leftX+boxSide,topY+boxSide+Math.abs(metrics.ascent),textPaint);
clickDay = null;//重新置空,保證點選方格外資訊消失
textPaint.setColor(Color.GRAY);//恢復畫筆顏色
}
}
這樣主體邏輯完成,但需要開放設定某天提交次數的方法:
/**
* 設定某天的次數
* @param year 年
* @param month 月
* @param day 日
* @param contribution 次數
*/
public void setData(int year,int month,int day,int contribution){
//先找到是第幾天,為了方便不做引數檢測了
for (Day d : mDays) {
if (d.year == year && d.month == month && d.date == day){
d.contribution = contribution;
d.colour = getColour(contribution);
break;
}
}
refreshView();
}
/**
* 根據提交次數來獲取顏色值
* @param contribution 提交的次數
* @return 顏色值
*/
private int getColour(int contribution){
int colour = 0;
if (contribution <= 0){
colour = COLOUR_LEVEL[4];
}
if (contribution == 1){
colour = COLOUR_LEVEL[3];
}
if (contribution == 2){
colour = COLOUR_LEVEL[2];
}
if (contribution == 3){
colour = COLOUR_LEVEL[1];
}
if (contribution >= 4){
colour = COLOUR_LEVEL[0];
}
return colour;
}
好了,所有邏輯完成,主要涉及到一些計算,完整程式碼:
/**
* Created by Administrator on 2017/1/13.
* 仿GitHub的提交活躍表
* 橫屏使用
*/
public class GitHubContributionView extends View {
/**灰色方格的預設顏色**/
private final static int DEFAULT_BOX_COLOUR = 0xFFEEEEEE;
/**提交次數顏色值**/
private final static int[] COLOUR_LEVEL =
new int[]{0xFF1E6823, 0xFF44A340, 0xFF8CC665, 0xFFD6E685, DEFAULT_BOX_COLOUR};
/**星期**/
private String[] weeks = new String[]{"Mon", "Wed", "Fri", "Sun"};
/**月份**/
private String[] months =
new String[]{"Jan", "Feb", "Mar", "Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"};
/**預設的padding,繪製的時候不貼邊畫**/
private int padding = 24;
/**小方格的預設邊長**/
private int boxSide = 8;
/**小方格間的預設間隔**/
private int boxInterval = 2;
/**所有周的列數**/
private int column = 0;
private List<Day> mDays;//一年中所有的天
private Paint boxPaint;//方格畫筆
private Paint textPaint;//文字畫筆
private Paint infoPaint;//彈出框畫筆
private Paint.FontMetrics metrics;//測量文字
private float downX;//按下的點的X座標
private float downY;//按下的點的Y座標
private Day clickDay;//按下所對應的天
public GitHubContributionView(Context context) {
this(context, null);
}
public GitHubContributionView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public GitHubContributionView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView();
}
public void initView() {
mDays = DateFactory.getDays(2016, 5);
//方格畫筆
boxPaint = new Paint();
boxPaint.setStyle(Paint.Style.FILL);
boxPaint.setStrokeWidth(2);
boxPaint.setColor(DEFAULT_BOX_COLOUR);
boxPaint.setAntiAlias(true);
//文字畫筆
textPaint = new Paint();
textPaint.setStyle(Paint.Style.FILL);
textPaint.setColor(Color.GRAY);
textPaint.setTextSize(12);
textPaint.setAntiAlias(true);
//彈出的方格資訊畫筆
infoPaint = new Paint();
infoPaint.setStyle(Paint.Style.FILL);
infoPaint.setColor(0xCC888888);
infoPaint.setTextSize(12);
infoPaint.setAntiAlias(true);
//將預設值轉換px
padding = UI.dp2px(getContext(), padding);
boxSide = UI.dp2px(getContext(), boxSide);
metrics = textPaint.getFontMetrics();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
column = 0;
canvas.save();
drawBox(canvas);
drawWeek(canvas);
drawTag(canvas);
drawPopupInfo(canvas);
canvas.restore();
}
/**
* 畫出1-12月方格小塊和上面的月份
* @param canvas 畫布
*/
private void drawBox(Canvas canvas) {
//方格的左上右下座標
float startX, startY, endX, endY;
//起始月份為1月
int month = 1;
for (int i = 0; i < mDays.size(); i++) {
Day day = mDays.get(i);
if (i == 0){
//畫1月的文字標記,座標應該是x=padding,y=padding-boxSide/2(間隙),y座標在表格上面一點
canvas.drawText(months[0],padding,padding-boxSide/2,textPaint);
}
if (day.week == 1 && i != 0) {
//如果當天是周1,那麼說明增加了一列
column++;
//如果列首的月份有變化,那麼說明需要畫月份
if (day.month>month){
month = day.month;
//月份文字的座標計算,x座標在變化,而y座標都是一樣的,boxSide/2(間隙)
canvas.drawText(months[month-1],padding+column*(boxSide+boxInterval),padding-boxSide/2,textPaint);
}
}
//計算方格座標點,x座標一致隨列數的增多而增加,y座標隨行數的增多而變化
startX = padding + column * (boxSide + boxInterval);
startY = padding + (day.week - 1) * (boxSide + boxInterval);
endX = startX + boxSide;
endY = startY + boxSide;
//將該方格的座標儲存下來,這樣可以在點選方格的時候計算彈框的座標
day.startX = startX;
day.startY = startY;
day.endX = endX;
day.endY = endY;
//給畫筆設定當前天的顏色
boxPaint.setColor(day.colour);
canvas.drawRect(startX, startY, endX, endY, boxPaint);
}
boxPaint.setColor(DEFAULT_BOX_COLOUR);//恢復預設顏色
}
/**
* 畫左側的星期
* @param canvas 畫布
*/
private void drawWeek(Canvas canvas) {
//文字是左對齊,所以找出最長的字
float textLength = 0;
for (String week : weeks) {
float tempLength = textPaint.measureText(week);
if (textLength < tempLength) {
textLength = tempLength;
}
}
//依次畫出星期文字,座標點x=padding-文字長度-文字和方格的間隙,y座標隨行數變化
canvas.drawText(weeks[0], padding - textLength - 2, padding + boxSide - metrics.descent, textPaint);
canvas.drawText(weeks[1], padding - textLength - 2, padding + 3 * (boxSide + boxInterval) - metrics.descent, textPaint);
canvas.drawText(weeks[2], padding - textLength - 2, padding + 5 * (boxSide + boxInterval) - metrics.descent, textPaint);
canvas.drawText(weeks[3], padding - textLength - 2, padding + 7 * (boxSide + boxInterval) - metrics.descent, textPaint);
}
/**
* 畫出右下角的顏色深淺標誌,因為是右對齊的所以需要從右往左畫
* @param canvas 畫布
*/
private void drawTag(Canvas canvas) {
//首先計算出兩個文字的長度
float moreLength = textPaint.measureText("More");
float lessLength = textPaint.measureText("Less");
//畫 More 文字,x座標=padding+(列數+1)*(方格邊長+方格間隙)-一個方格間隙-文字長度
float moreX = padding + (column + 1) * (boxSide + boxInterval) - boxInterval - moreLength;
//y座標=padding+(方格行數+1,和表格底部有些距離)*(方格邊長+方格間隙)+字型的ascent高度
float moreY = padding + 8 * (boxSide + boxInterval) + Math.abs(metrics.ascent);
canvas.drawText("More", moreX, moreY, textPaint);
//畫深淺色塊,座標根據上面的More依次計算就可以了
float interval = boxSide - 2;//文字和色塊間的距離
float leftX = moreX - interval - boxSide;
float topY = moreY - boxSide;
float rightX = moreX - interval;
float bottomY = moreY;//色塊的Y座標是一樣的
for (int i = 0; i < COLOUR_LEVEL.length; i++) {
boxPaint.setColor(COLOUR_LEVEL[i]);
canvas.drawRect(leftX - i * (boxSide + boxInterval), topY, rightX - i * (boxSide + boxInterval), bottomY, boxPaint);
}
//最後畫 Less 文字,原理同上
canvas.drawText("Less", leftX - 4 * (boxSide + boxInterval) - interval - lessLength, moreY, textPaint);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
//獲取點選時候的座標,用來判斷點在哪天,並彈出·
if (MotionEvent.ACTION_DOWN == event.getAction()) {
downX = event.getX();
downY = event.getY();
findClickBox();
}
return super.onTouchEvent(event);
}
/**
* 判斷是否點選在方格內
*/
private void findClickBox() {
for (Day day : mDays) {
//檢測點選的座標如果在方格內,則彈出資訊提示
if (downX >= day.startX && downX <= day.endX && downY >= day.startY && downY <= day.endY) {
clickDay = day;//紀錄點擊的哪天
break;
}
}
//點選完要重新整理,這樣每次點選不同的方格,彈窗就可以在相應的位置顯示
refreshView();
}
/**
* 點選彈出文字提示
*/
private void refreshView() {
invalidate();
}
/**
* 畫方格上的文字彈框
* @param canvas 畫布
*/
private void drawPopupInfo(Canvas canvas) {
if (clickDay != null) {
//先根據方格來畫出一個小三角形,座標就是方格的中間
Path infoPath = new Path();
//先從方格中心
infoPath.moveTo(clickDay.startX + boxSide / 2, clickDay.startY + boxSide / 2);
//然後是方格的左上點
infoPath.lineTo(clickDay.startX, clickDay.startY);
//然後是方格的右上點
infoPath.lineTo(clickDay.endX, clickDay.startY);
//畫出三角
canvas.drawPath(infoPath,infoPaint);
//畫三角上的圓角矩形
textPaint.setColor(Color.WHITE);
//得到當天的文字資訊
String popupInfo = clickDay.toString();
System.out.println(popupInfo);
//計算文字的高度和長度用以確定矩形的大小
float infoHeight = metrics.descent - metrics.ascent;
float infoLength = textPaint.measureText(popupInfo);
Log.e("height",infoHeight+"");
Log.e("length",infoLength+"");
//矩形左上點應該是x=當前天的x+邊長/2-(文字長度/2+文字和框的間隙)
float leftX = (clickDay.startX + boxSide / 2 ) - (infoLength / 2 + boxSide);
//矩形左上點應該是y=當前天的y+邊長/2-(文字高度+上下文字和框的間隙)
float topY = clickDay.startY-(infoHeight+2*boxSide);
//矩形的右下點應該是x=leftX+文字長度+文字兩邊和矩形的間距
float rightX = leftX+infoLength+2*boxSide;
//矩形的右下點應該是y=當前天的y
float bottomY = clickDay.startY;
System.out.println(""+leftX+"/"+topY+"/"+rightX+"/"+bottomY);
RectF rectF = new RectF(leftX, topY, rightX, bottomY);
canvas.drawRoundRect(rectF,4,4,infoPaint);
//繪製文字,x=leftX+文字和矩形間距,y=topY+文字和矩形上面間距+文字頂到基線高度
canvas.drawText(popupInfo,leftX+boxSide,topY+boxSide+Math.abs(metrics.ascent),textPaint);
clickDay = null;//重新置空,保證點選方格外資訊消失
textPaint.setColor(Color.GRAY);//恢復畫筆顏色
}
}
/**
* 設定某天的次數
* @param year 年
* @param month 月
* @param day 日
* @param contribution 次數
*/
public void setData(int year,int month,int day,int contribution){
//先找到是第幾天,為了方便不做引數檢測了
for (Day d : mDays) {
if (d.year == year && d.month == month && d.date == day){
d.contribution = contribution;
d.colour = getColour(contribution);
break;
}
}
refreshView();
}
/**
* 根據提交次數來獲取顏色值
* @param contribution 提交的次數
* @return 顏色值
*/
private int getColour(int contribution){
int colour = 0;
if (contribution <= 0){
colour = COLOUR_LEVEL[4];
}
if (contribution == 1){
colour = COLOUR_LEVEL[3];
}
if (contribution == 2){
colour = COLOUR_LEVEL[2];
}
if (contribution == 3){
colour = COLOUR_LEVEL[1];
}
if (contribution >= 4){
colour = COLOUR_LEVEL[0];
}
return colour;
}
}
這樣弄個佈局測試下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
>
<com.franky.custom.view.GitHubContributionView
android:id="@+id/cc_chart"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</LinearLayout>
隨機弄些資料:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
GitHubContributionView github = (GitHubContributionView) findViewById(R.id.cc_chart);
github.setData(2016,12,9,2);
github.setData(2016,11,9,1);
github.setData(2016,10,5,10);
github.setData(2016,8,9,3);
github.setData(2016,4,20,2);
github.setData(2016,12,13,3);
github.setData(2016,12,14,3);
github.setData(2016,2,15,4);
}
}
效果
gif沒有錄好,看看圖片效果: