创建自己的View组件的实际示例

创建自己的View组件的实际示例


我喜欢Dribbble 。 有许多很棒的设计项目。 但是,如果您是一名开发人员,那么当您开始考虑如何实现这种炫酷设计时,绝妙的感觉通常会很快被绝望所取代。


在本文中,我将向您展示这种设计及其实现的示例,但在此之前,让我们先谈一下整体解决问题的方法。


最简单的方法是使用满足我们需求的某种库。 现在,请不要误会我的意思,我是“不要重新发明轮子”方法的坚定支持者。 有很棒的开源库,当我需要上传图像或实现REST API时, Glide / PicassoRetrofit将对我有很大帮助。


但是,当您需要实施一些非常规设计时,这并不总是最佳选择。 您将需要花费时间寻找一个良好的,受支持的库,该库将执行类似的操作。 然后,您需要查看代码以确保在其中编写了足够的内容。 您将需要花费更多的时间来理解可以在任务中使用库的设置和配置。 坦白说,很可能该库无法100%满足您的需求,您需要与设计师进行一些折衷。


因此,我说创建自己的View组件通常更容易,更好。 当我说“本地View组件”时,我的意思是扩展View类,覆盖onDraw()方法,并使用PaintCanvas绘制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.绘制渐变


渐变色


 // prepare the gradient paint val colors = intArrayOf(colorStart, colorEnd)) val gradient = LinearGradient( 0f, paddingTop, 0f, zeroY, colors, null, CLAMP ) gradientPaint.style = FILL gradientPaint.shader = gradient private fun drawGradient(canvas: Canvas) { path.reset() path.moveTo(paddingLeft, zeroY) for (marker in markers) { path.lineTo(marker.targetPos.x, entry.targetPos.y) } // close the path path.lineTo(markers.last().targetPos.x, zeroY) path.lineTo(paddingLeft, zeroY) canvas.drawPath(path, gradientPaint) } 

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


步骤3.绘制网格


网眼


 // prepare the guideline paint dottedPaint.style = STROKE dottedPaint.strokeWidth = DOTTED_STROKE_WIDTH_DP dottedPaint.pathEffect = DashPathEffect(floatArrayOf(INTERVAL, INTERVAL), 0f) private fun drawGuidelines(canvas: Canvas) { val first = findFirstDayOfWeekInMonth(markers) for (i in first..markers.lastIndex step 7) { val marker = markers[i] guidelinePath.reset() guidelinePath.moveTo(entry.currentPos.x, paddingTop) guidelinePath.lineTo(entry.currentPos.x, zeroY) canvas.drawPath(guidelinePath, dottedPaint) } } 

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


步骤4.绘制图形和标记


图表和标记


 private fun drawLineAndMarkers(canvas: Canvas) { var previousMarker: Marker? = null for (marker in markers) { if (previousMarker != null) { // draw the line val p1 = previousMarker.currentPos val p2 = marker.currentPos canvas.drawLine(p1.x, p1.y, p2.x, p2.y, strokePaint) } previousMarker = marker // draw the marker canvas.drawCircle( marker.currentPos.x, marker.currentPos.y, pointRadius, pointPaint ) } } 

我们遍历标记,为每个标记绘制一个实心圆,并从上一个标记到当前标记画一条简单的线。


步骤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) } 

结合各层将为我们提供所需的结果:


结果


总结


  1. 不要害怕创建自己的View组件(如有必要)。
  2. 了解基本的CanvasPaint API。
  3. 将您的设计分成几个小层,并分别进行绘制。

关于最后一点,对我而言,这是总体上最好的编程课程之一:当面对大型复杂任务时,将其分解为更小,更简单的任务。

Source: https://habr.com/ru/post/zh-CN433782/


All Articles