自定义View之签到足迹控件

时间:2022-06-01
本文章向大家介绍自定义View之签到足迹控件,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

大家好我叫王大锤,今天 UI 妹纸又给了个图叫我做,我一看是这样的:

输入图片说明 我的内心是这样的:

输入图片说明


尽管嘴上说着不愿意,但是身体还是很诚实,开始撸代码了。

我们首先把这个控件划分成 1个 部分:

  • 1.底下部分的直线
  • 2.左右两边的半圆弧度
  • 3.线上面的小图标
  • 4.最后的文字说明

我不管,我就要说一个

输入图片说明

首先我们把线画出来,大概这个样子

输入图片说明

    @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);
            // 这里是来判断,是否需要画出左半边还是右半边的半圆弧度?
            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);
                }
            }
        }
    }

我们这里根据一个月得总天数,和一条线上需要画七个图,计算出总共需要画出的线条数,以及画出左边和右边的弧度,根据当前线是单数还是双数,来计算出是否是左半边的弧度,还是右半边的弧度,以及是否是最后的一条线,因为最后一条线不需要画弧度。


然后再在线上画出礼物数量

            // 这里是来判断,本次这根线上画出的礼物的点,以及顺序是顺画,还是倒画出。
            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);
                    }
                }
            }

这里有一个需要注意的地方,就是,在线为双数的时候,这时候礼物的排列是需要反过来排列的,我这里使用了一个LinkedList来保存礼物的排列顺序,然后我们通过计算平均数,计算出每个礼物的位置。

    /**
     * 画出的按路线上的图片,勾选,礼物
     * @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);
        }
    }

这里也有一个需要注意的地方,就是,当最后一条线是短的时候,这个时候,你的礼物的排列需要按照那条线的开始位置和结束位置来平均计算每个礼物的位置。


最后,我们在最后一条线最后的位置,画出文字

/**
     * 画出文字
     * @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 = "已累计签到"+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);
    }

输入图片说明


好了,这就是所有的思路。下面贴一下最新完整代码(更新时间 2018年2月5日):

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.yongxing.QianJR.R;
import com.yongxing.QianJR.utils.DensityUtil;

import java.util.LinkedList;
import java.util.List;

/**
 * Created by xiaolei on 2017/11/6.
 */

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(context, 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);
    }

    /**
     * 设置本月天数
     *
     * @param monthDays
     */
    public void setMonthDays(int monthDays)
    {
        this.monthDays = monthDays;
        if (monthDays == 0)
        {
            this.monthDays = 31;
        }
        postInvalidate();
    }

    /**
     * 设置一共签到了几天
     *
     * @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);
            // 这里是来判断,是否需要画出左半边还是右半边的半圆弧度?
            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);
                }
            }

            // 这里是来判断,本次这根线上画出的礼物的点,以及顺序是顺画,还是倒画出。
            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);
    }
}

THE END