Android 實(shí)現(xiàn)簽到足跡功能
UI 妹紙又給了個(gè)圖叫我做,我一看是這樣的:

我們首先把這個(gè)控件劃分成 幾個(gè)部分:
1.底下部分的直線 :
2.左右兩邊的半圓弧度 :
3.線上面的小圖標(biāo) :
4.最后的文字說(shuō)明 :
首先我們把線畫出來(lái),大概這個(gè)樣子

我們這里根據(jù)一個(gè)月得總天數(shù),和一條線上需要畫七個(gè)圖,計(jì)算出總共需要畫出的線條數(shù),以及畫出左邊和右邊的弧度,根據(jù)當(dāng)前線是單數(shù)還是雙數(shù),來(lái)計(jì)算出是否是左半邊的弧度,還是右半邊的弧度,以及是否是最后的一條線,因?yàn)樽詈笠粭l線不需要畫弧度。
代碼如下:
@Override
protected void onDraw(Canvas canvas)
{
paint.setColor(backColor);
paint.setStrokeWidth(strokeWidth);
int rowCount = (monthDays % 7 == 0 ? monthDays / 7 : monthDays / 7 + 1);
int rowHeigh = height / (rowCount);
int startX = 0 + rowHeigh / 2;
int endX = width - rowHeigh / 2;
int days = 0;
for (int a = 0; a < rowCount; a++)
{
if (a + 1 == rowCount)
{
endX = (endX - startX) / 7 * (monthDays % 7) + checkBitmap.getWidth() / 2;
}
paint.setStrokeWidth(strokeWidth);
int y = rowHeigh * a + rowHeigh / 2;
canvas.drawLine(startX, y, endX, y, paint);
paint.setColor(rashColor);
paint.setStrokeWidth(1);
canvas.drawLine(startX, y, endX, y, paint);
// 這里是來(lái)判斷,是否需要畫出左半邊還是右半邊的半圓弧度?
if (a % 2 != 0)
{
if (a + 1 != rowCount)
{
drawLeftOrRightArc(true, canvas, 0 + strokeWidth, y, 0 + rowHeigh + strokeWidth, y + rowHeigh);
}
} else
{
if (a + 1 != rowCount)
{
drawLeftOrRightArc(false, canvas, endX - rowHeigh / 2 - strokeWidth, y, endX + rowHeigh / 2 - strokeWidth, y + rowHeigh);
}
}
}
}
然后再在線上畫出禮物數(shù)量
// 這里是來(lái)判斷,本次這根線上畫出的禮物的點(diǎn),以及順序是順畫,還是倒畫出。
bitmapList.clear();
for (int b = 0; b < (a + 1 == rowCount ? (monthDays % 7) : 7); b++)
{
days++;
if (days <= signInCount)
{
if (days == 3 || days == 8 || days == 14 || days == 21 || days == monthDays)
{
bitmapList.add(a % 2 != 0 ? 0 : bitmapList.size(), openGiftBitmap);
} else
{
bitmapList.add(a % 2 != 0 ? 0 : bitmapList.size(), checkBitmap);
}
} else
{
if (days == 3 || days == 8 || days == 14 || days == 21 || days == monthDays)
{
bitmapList.add(a % 2 != 0 ? 0 : bitmapList.size(), closeGiftBitmap);
} else
{
bitmapList.add(a % 2 != 0 ? 0 : bitmapList.size(), uncheckBitmap);
}
}
}
這里有一個(gè)需要注意的地方,就是,在線為雙數(shù)的時(shí)候,這時(shí)候禮物的排列是需要反過(guò)來(lái)排列的,我這里使用了一個(gè)LinkedList來(lái)保存禮物的排列順序,然后我們通過(guò)計(jì)算平均數(shù),計(jì)算出每個(gè)禮物的位置。
/**
* 畫出的按路線上的圖片,勾選,禮物
* @param bitmapList
* @param startX
* @param endX
* @param y
* @param canvas
*/
private void drawImgs(List<Bitmap> bitmapList, float startX, float endX, float y, Canvas canvas)
{
startX = startX - bitmapList.get(0).getWidth() / 2;
int count = bitmapList.size();
float bitmap_width = (endX - startX) / (count - 1);
for (int a = 0; a < count; a++)
{
Bitmap bitmap = bitmapList.get(a);
canvas.drawBitmap(bitmap, startX + (bitmap_width * a), y - bitmap.getHeight() / 2, paint);
}
}
這里也有一個(gè)需要注意的地方,就是,當(dāng)最后一條線是短的時(shí)候,這個(gè)時(shí)候,你的禮物的排列需要按照那條線的開始位置和結(jié)束位置來(lái)平均計(jì)算每個(gè)禮物的位置。
最后,我們?cè)谧詈笠粭l線最后的位置,畫出文字
/**
* 畫出文字
* @param canvas
* @param y
* @param x
*/
private void drawText(Canvas canvas, float y, float x)
{
int oldColor = paint.getColor();
Paint.Style old_style = paint.getStyle();
paint.setStyle(Paint.Style.FILL);
paint.setColor(textColor);
String drawText = "已累計(jì)簽到"+signInCount+"天";
paint.setTextSize(DensityUtil.sp2px(getContext(), 15));
int textHeigh = getStringHeight(drawText);
int textWidth = getStringWidth(drawText);
canvas.drawText(drawText, x + textWidth/2, y + textHeigh / 2, paint);
paint.setColor(oldColor);
paint.setStyle(old_style);
}

好了,這就是所有的思路。下面貼一下最新完整代碼:
package com.sjl.keeplive.track;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
import com.sjl.keeplive.R;
import java.util.LinkedList;
import java.util.List;
public class SignInView extends View {
private int width, height;
private int monthDays = 31;//本月有31天
private Paint paint;
private RectF oval = new RectF();
private float strokeWidth = 10;
private Bitmap checkBitmap, uncheckBitmap, closeGiftBitmap, openGiftBitmap;
private int backColor = Color.parseColor("#C3DEEA"),
rashColor = Color.parseColor("#B2CADB"),
textColor = Color.parseColor("#60ADE5");
private List<Bitmap> bitmapList = new LinkedList<>();
private int signInCount = 9;
public SignInView(Context context) {
this(context, null);
}
public SignInView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public SignInView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
paint = new Paint();
paint.setAntiAlias(true);
strokeWidth = DensityUtil.dip2px(6);
checkBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.icon_sign_in_check_img);
uncheckBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.icon_sign_in_uncheck_img);
closeGiftBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.icon_close_gift_img);
openGiftBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.icon_open_gift_img);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
height = MeasureSpec.getSize(heightMeasureSpec);
width = MeasureSpec.getSize(widthMeasureSpec);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
/**
* 設(shè)置本月天數(shù)
*
* @param monthDays
*/
public void setMonthDays(int monthDays) {
this.monthDays = monthDays;
if (monthDays == 0) {
this.monthDays = 31;
}
postInvalidate();
}
/**
* 設(shè)置一共簽到了幾天
*
* @param days
*/
public void setProgress(int days) {
this.signInCount = days;
postInvalidate();
}
@Override
protected void onDraw(Canvas canvas) {
paint.setColor(backColor);
paint.setStrokeWidth(strokeWidth);
int rowCount = (monthDays % 7 == 0 ? monthDays / 7 : monthDays / 7 + 1);
int rowHeigh = height / (rowCount);
int startX = 0 + rowHeigh / 2;
int endX = width - rowHeigh / 2;
int days = 0;
for (int a = 0; a < rowCount; a++) {
if (a + 1 == rowCount) {
endX = (endX - startX) / 7 * (monthDays % 7 == 0 ? 7 : (monthDays % 7)) + checkBitmap.getWidth() / 2;
}
paint.setStrokeWidth(strokeWidth);
int y = rowHeigh * a + rowHeigh / 2;
canvas.drawLine(startX, y, endX, y, paint);
paint.setColor(rashColor);
paint.setStrokeWidth(1);
canvas.drawLine(startX, y, endX, y, paint);
// 這里是來(lái)判斷,是否需要畫出左半邊還是右半邊的半圓弧度?
if (a % 2 != 0) {
if (a + 1 != rowCount) {
drawLeftOrRightArc(true, canvas, 0 + strokeWidth, y, 0 + rowHeigh + strokeWidth, y + rowHeigh);
}
} else {
if (a + 1 != rowCount) {
drawLeftOrRightArc(false, canvas, endX - rowHeigh / 2 - strokeWidth, y, endX + rowHeigh / 2 - strokeWidth, y + rowHeigh);
}
}
// 這里是來(lái)判斷,本次這根線上畫出的禮物的點(diǎn),以及順序是順畫,還是倒畫出。
bitmapList.clear();
int lastDay = (monthDays % 7) == 0 ? 7 : (monthDays % 7);
for (int b = 0; b < (a + 1 == rowCount ? (lastDay) : 7); b++) {
days++;
if (days <= signInCount) {
if (days == 3 || days == 8 || days == 14 || days == 21 || days == monthDays) {
bitmapList.add(a % 2 != 0 ? 0 : bitmapList.size(), openGiftBitmap);
} else {
bitmapList.add(a % 2 != 0 ? 0 : bitmapList.size(), checkBitmap);
}
} else {
if (days == 3 || days == 8 || days == 14 || days == 21 || days == monthDays) {
bitmapList.add(a % 2 != 0 ? 0 : bitmapList.size(), closeGiftBitmap);
} else {
bitmapList.add(a % 2 != 0 ? 0 : bitmapList.size(), uncheckBitmap);
}
}
}
drawImgs(bitmapList, startX, endX, y, canvas);
}
super.onDraw(canvas);
}
/**
* 畫出的按路線上的圖片,勾選,禮物
*
* @param bitmapList
* @param startX
* @param endX
* @param y
* @param canvas
*/
private void drawImgs(List<Bitmap> bitmapList, float startX, float endX, float y, Canvas canvas) {
if (!bitmapList.isEmpty()) {
startX = startX - bitmapList.get(0).getWidth() / 2;
int count = bitmapList.size();
float bitmap_width = (endX - startX) / (count - 1);
for (int a = 0; a < count; a++) {
Bitmap bitmap = bitmapList.get(a);
canvas.drawBitmap(bitmap, startX + (bitmap_width * a), y - bitmap.getHeight() / 2, paint);
}
}
}
/**
* 這里畫出左邊半圓弧,還是右邊半圓弧
*
* @param isLeft
* @param canvas
* @param left
* @param top
* @param right
* @param bottom
*/
private void drawLeftOrRightArc(boolean isLeft, Canvas canvas, float left, float top, float right, float bottom) {
paint.setStrokeWidth(strokeWidth);
paint.setColor(backColor);
if (isLeft) {
paint.setStyle(Paint.Style.STROKE);
oval.setEmpty();
oval.set(left, top, right, bottom);
canvas.drawArc(oval, 90, 180, false, paint);
paint.setStrokeWidth(1);
paint.setColor(rashColor);
canvas.drawArc(oval, 90, 180, false, paint);
} else {
paint.setStyle(Paint.Style.STROKE);
oval.setEmpty();
oval.set(left, top, right, bottom);
canvas.drawArc(oval, 270, 180, false, paint);
paint.setStrokeWidth(1);
paint.setColor(rashColor);
canvas.drawArc(oval, 270, 180, false, paint);
}
paint.setStrokeWidth(strokeWidth);
paint.setColor(backColor);
}
}
布局文件使用:
<com.sjl.keeplive.track.SignInView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="visible"/>
項(xiàng)目地址:
鏈接:https://pan.baidu.com/s/1IUh9og2T3IlxeXhaLLOKGg
提取碼:thoc
由于demo集合比較多,單這篇看下面代碼即可:

以上就是Android 實(shí)現(xiàn)簽到足跡功能的詳細(xì)內(nèi)容,更多關(guān)于Android 簽到功能的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
EditText限制輸入數(shù)字,精確到小數(shù)點(diǎn)后1位的設(shè)置方法
下面小編就為大家?guī)?lái)一篇EditText限制輸入數(shù)字,精確到小數(shù)點(diǎn)后1位的設(shè)置方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-04-04
android實(shí)現(xiàn)始終顯示overflow菜單的方法
這篇文章主要介紹了android實(shí)現(xiàn)始終顯示overflow菜單的方法,需要的朋友可以參考下2014-07-07
Android應(yīng)用的多語(yǔ)言支持的實(shí)現(xiàn)方法
本篇文章主要介紹了Android應(yīng)用的多語(yǔ)言支持的實(shí)現(xiàn)方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06
Android實(shí)現(xiàn)自定義加載框的代碼示例
本篇文章主要介紹了Android實(shí)現(xiàn)自定義加載框的代碼示例,App在與服務(wù)器進(jìn)行網(wǎng)絡(luò)交互的時(shí)候,有個(gè)提示加載框,有興趣的可以了解一下。2017-02-02
Retrofit Rxjava實(shí)現(xiàn)圖片下載、保存并展示實(shí)例
本篇文章主要介紹了Retrofit Rxjava實(shí)現(xiàn)圖片下載、保存并展示實(shí)例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06
Android之采用execSQL與rawQuery方法完成數(shù)據(jù)的添刪改查操作詳解
本篇文章是對(duì)用execSQL與rawQuery方法完成數(shù)據(jù)的添刪改查操作進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-06-06
Android的OkHttp包處理用戶認(rèn)證的代碼實(shí)例分享
OkHttp包(GitHub主頁(yè)github.com/square/okhttp)是一款高人氣安卓HTTP支持包,這里我們來(lái)看一下Android的OkHttp包處理用戶認(rèn)證的代碼實(shí)例分享:2016-07-07
使用Android開發(fā)接入第三方原生SDK實(shí)現(xiàn)微信登錄
這篇文章主要介紹了使用Android開發(fā)接入第三方原生SDK實(shí)現(xiàn)微信登錄,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-03-03
Android studio 運(yùn)行main 函數(shù)的方法
這篇文章主要介紹了Android studio 運(yùn)行main 函數(shù)的方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09

