
我喜欢Dribbble 。 有许多很棒的设计项目。 但是,如果您是一名开发人员,那么当您开始考虑如何实现这种炫酷设计时,绝妙的感觉通常会很快被绝望所取代。
在本文中,我将向您展示这种设计及其实现的示例,但在此之前,让我们先谈一下整体解决问题的方法。
最简单的方法是使用满足我们需求的某种库。 现在,请不要误会我的意思,我是“不要重新发明轮子”方法的坚定支持者。 有很棒的开源库,当我需要上传图像或实现REST API时, Glide / Picasso和Retrofit将对我有很大帮助。
但是,当您需要实施一些非常规设计时,这并不总是最佳选择。 您将需要花费时间寻找一个良好的,受支持的库,该库将执行类似的操作。 然后,您需要查看代码以确保在其中编写了足够的内容。 您将需要花费更多的时间来理解可以在任务中使用库的设置和配置。 坦白说,很可能该库无法100%满足您的需求,您需要与设计师进行一些折衷。
因此,我说创建自己的View
组件通常更容易,更好。 当我说“本地View
组件”时,我的意思是扩展View
类,覆盖onDraw()
方法,并使用Paint
和Canvas
绘制View
组件。 如果您以前没有做过,这似乎很可怕,因为这些类具有许多方法和属性,但是您可以专注于主要的方法和属性:
canvas.drawRect()
-指定角的坐标并绘制一个矩形;
canvas.drawRoundRect()
-可选地指定半径,矩形的角将被圆化;
canvas.drawPath()
是一种使用线条和曲线创建自己的形状的更复杂但更强大的方法;
canvas.drawText()
-用于在画布上绘制文本(使用Paint
可以控制大小,颜色和其他属性);
canvas.drawCircle()
-指定中心点和半径并得到一个圆;
canvas.drawArc()
-指定边界矩形,以及绘制弧的起始和转角;
paint.style
指示绘制的形状是填充,带圆圈还是两者都填充;
paint.color
指示颜色(包括透明度);
paint.strokeWidth
控制笔触形状的宽度;
paint.pathEffect
允许您影响绘制图形的几何形状;
paint.shader
允许您绘制渐变。
请记住,有时您可能需要使用其他API,但是即使掌握了这些方法,也可以绘制非常复杂的形状。
实际例子
这是Pepper为我们提供的设计:

这里有很多有趣的事情,但让我们将它们分成小块。
步骤1.计算标记位置
private fun calcPositions(markers: List<Marker>) { val max = markers.maxBy { it.value } val min = markers.minBy { it.value } pxPerUnit = chartHeight / (max - min) zeroY = max * pxPerUnit + paddingTop val step = (width - 2 * padding - scalesWidth) / (markers.size - 1) for ((i, marker) in markers.withIndex()) { val x = step * i + paddingLeft val y = zeroY - entry.value * pxPerUnit marker.currentPos.x = x marker.currentPos.y = y } }
我们找到最小值和最大值,计算每像素的比例,标记之间的水平步长以及X和Y位置。
步骤2.绘制渐变

我们创建一个从左边缘开始的形状,在每个标记之间画一条线,并在起点处结束该形状。 然后使用带有渐变着色器的颜料绘制此形状。
步骤3.绘制网格

我们自定义油漆,使其破折号。 然后,我们使用特殊的Kotlin语言周期,该周期使我们能够以7为步长(一周中的天数)遍历标记。 对于每个标记,我们取X坐标,并从图的顶部到zeroY
绘制一条垂直虚线。
步骤4.绘制图形和标记

private fun drawLineAndMarkers(canvas: Canvas) { var previousMarker: Marker? = null for (marker in markers) { if (previousMarker != null) {
我们遍历标记,为每个标记绘制一个实心圆,并从上一个标记到当前标记画一条简单的线。
步骤5.绘制星期按钮

private fun drawWeeks(canvas: Canvas) { for ((i, week) in weeks.withIndex()) { textPaint.getTextBounds(week, 0, week.length, rect) val x = middle(i) val y = zeroY + rect.height() val halfWidth = rect.width() / 2f val halfHeight = rect.height() / 2f val left = x - halfWidth - padding val top = y - halfHeight - padding val right = x + halfWidth + padding val bottom = y + halfHeight + padding rect.set(left, top, right, bottom) paint.color = bgColor paint.style = FILL canvas.drawRoundRect(rect, radius, radius, paint) paint.color = strokeColor paint.style = STROKE canvas.drawRoundRect(rect, radius, radius, paint) canvas.drawText(week, x, keyY, textPaint) } }
我们遍历一周的标记,找到一周中的X坐标,然后开始分层绘制按钮:首先,我们绘制带有圆角的背景,然后绘制边框,最后绘制文本。 我们在绘制每一层之前调整油漆。
步骤6.在右侧绘制数字标记

private fun drawGraduations(canvas: Canvas) { val x = markers.last().currentPos.x + padding for (value in graduations) { val y = zeroY - scale * pxPerUnit val formatted = NumberFormat.getIntegerInstance().format(value) canvas.drawText(formatted, x, y, textPaint) } }
X坐标是最后一个标记加上一些缩进的位置。 使用每单位像素的比率计算Y坐标。 我们将数字格式化为字符串(如有必要,添加千位分隔符)并绘制文本。
就是这样,现在我们的onDraw()
将如下所示:
override fun onDraw(canvas: Canvas) { drawGradient(canvas) drawGuidelines(canvas) drawLineAndMarkers(canvas) drawWeeks(canvas) drawGraduations(canvas) }
结合各层将为我们提供所需的结果:

总结
- 不要害怕创建自己的
View
组件(如有必要)。 - 了解基本的
Canvas
和Paint
API。 - 将您的设计分成几个小层,并分别进行绘制。
关于最后一点,对我而言,这是总体上最好的编程课程之一:当面对大型复杂任务时,将其分解为更小,更简单的任务。