- 计算位置
为了正确绘制自定义视图,需要知道它的大小,
View
提供多种测量处理方法,大部分方法都不需要被替换。如果您的视图不需要对其大小进行特殊控制,您只需替换一个方法,即onSizeChanged()
,在onSizeChanged()
中计算位置、尺寸以及其他与视图大小相关的任何值.
override fun onSizeChanged(width: Int, height: Int, oldWidth: Int, oldHeight: Int) {
radius = (min(width, height) / 2.0 * 0.8).toFloat()
}
- 定义
computeXYForSpeed
,计算x,y坐标轴
private fun PointF.computeXYForSpeed(pos: FanSpeed, radius: Float) {
// Angles are in radians.
val startAngle = Math.PI * (9 / 8.0)
val angle = startAngle + pos.ordinal * (Math.PI / 4)
x = (radius * cos(angle)).toFloat() + width / 2
y = (radius * sin(angle)).toFloat() + height / 2
}
- override
onDraw
在调用onDraw
方法之前,必须先创建Paint
对象
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
}
设置画笔对象属性
paint.color = if (fanSpeed == FanSpeed.OFF) Color.GRAY else Color.GREEN
- 画背景
drawCircle()
//Draw the dial
canvas.drawCircle((width / 2).toFloat(), (height / 2).toFloat(), radius, paint)
- 绘制小圆
val markerRadius = radius + RADIUS_OFFSET_INDICATOR
pointPosition.computeXYForSpeed(fanSpeed, markerRadius)
paint.color = Color.BLACK
canvas.drawCircle(pointPosition.x, pointPosition.y, radius/12, paint)
- 绘制文字
val labelRadius = radius + RADIUS_OFFSET_LABEL
for (i in FanSpeed.values()) {
pointPosition.computeXYForSpeed(i, labelRadius)
val label = resources.getString(i.label)
canvas.drawText(label, pointPosition.x, pointPosition.y, paint)
}
- 完成
onDraw
如下:
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// Set dial background color based on the selection.
paint.color = when (fanSpeed) {
FanSpeed.OFF -> Color.GRAY
FanSpeed.LOW -> fanSpeedLowColor
FanSpeed.MEDIUM -> fanSpeedMediumColor
FanSpeed.HIGH -> fanSpeedMaxColor
}
// Draw the dial. 背景
canvas.drawCircle((width / 2).toFloat(), (height / 2).toFloat(), radius, paint)
// Draw the indicator circle.
val markerRadius = radius + RADIUS_OFFSET_INDICATOR
pointPosition.computeXYForSpeed(fanSpeed, markerRadius)
paint.color = Color.BLACK
canvas.drawCircle(pointPosition.x, pointPosition.y, radius/12, paint)
// Draw the text labels.
val labelRadius = radius + RADIUS_OFFSET_LABEL
for (i in FanSpeed.values()) {
pointPosition.computeXYForSpeed(i, labelRadius)
val label = resources.getString(i.label)
canvas.drawText(label, pointPosition.x, pointPosition.y, paint)
}
}
DialView.kt
private enum class FanSpeed(val label: Int) {
OFF(R.string.fan_off),
LOW(R.string.fan_low),
MEDIUM(R.string.fan_medium),
HIGH(R.string.fan_high);
fun next() = when (this) {
OFF -> LOW
LOW -> MEDIUM
MEDIUM -> HIGH
HIGH -> OFF
}
}
private const val RADIUS_OFFSET_LABEL = 30 //Offset from dial radius to draw text label 文本半径
private const val RADIUS_OFFSET_INDICATOR = -35 //Offset from dial radius to draw indicator
class DialView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
// Paint styles used for rendering are initialized here. This
// is a performance optimization, since onDraw() is called
// for every screen refresh.
style = Paint.Style.FILL
textAlign = Paint.Align.CENTER
textSize = 55.0f
typeface = Typeface.create("", Typeface.BOLD)
}
private var radius = 0.0f // Radius of the circle. 半径
private var fanSpeed = FanSpeed.OFF // The active selection.
//Point at which to draw label and indicator circle position. PointF is a point
//with floating-point coordinates.
private val pointPosition: PointF = PointF(0.0f, 0.0f)
private val fanSpeedLowColor:Int
private val fanSpeedMediumColor:Int
private val fanSpeedMaxColor:Int
init {
isClickable = true
val typedArray = context.obtainStyledAttributes(attrs,R.styleable.DialView)
fanSpeedLowColor=typedArray.getColor(R.styleable.DialView_fanColor1,0)
fanSpeedMediumColor = typedArray.getColor(R.styleable.DialView_fanColor2,0)
fanSpeedMaxColor = typedArray.getColor(R.styleable.DialView_fanColor3,0)
typedArray.recycle()
updateContentDescription()
// For minsdk >= 21, you can just add a click action. In this app since minSdk is 19,
// you must add a delegate to handle accessibility.
ViewCompat.setAccessibilityDelegate(this, object : AccessibilityDelegateCompat() {
override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfoCompat) {
super.onInitializeAccessibilityNodeInfo(host, info)
val customClick = AccessibilityNodeInfoCompat.AccessibilityActionCompat(
AccessibilityNodeInfo.ACTION_CLICK,
// If the fan speed is OFF, LOW, or MEDIUM, the hint is to change the speed.
// If it is HIGH use reset.
context.getString(if (fanSpeed != FanSpeed.HIGH) R.string.change else R.string.reset)
)
info.addAction(customClick)
}
})
}
override fun performClick(): Boolean {
// Give default click listeners priority and perform accessibility/autofill events.
// Also calls onClickListener() to handle further subclass customizations.
if (super.performClick()) return true
// Rotates between each of the different selection
// states on each click.
fanSpeed = fanSpeed.next()
updateContentDescription()
// Redraw the view.
invalidate()
return true
}
/**
* This is called during layout when the size of this view has changed. If
* the view was just added to the view hierarchy, it is called with the old
* values of 0. The code determines the drawing bounds for the custom view.
*
* @param width Current width of this view.
* @param height Current height of this view.
* @param oldWidth Old width of this view.
* @param oldHeight Old height of this view.
*/
override fun onSizeChanged(width: Int, height: Int, oldWidth: Int, oldHeight: Int) {
// Calculate the radius from the smaller of the width and height.
radius = (min(width, height) / 2.0 * 0.8).toFloat()
}
/**
* Renders view content: an outer circle to serve as the "dial",
* and a smaller black circle to server as the indicator.
* The position of the indicator is based on fanSpeed.
*
* @param canvas The canvas on which the background will be drawn.
*/
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// Set dial background color based on the selection.
paint.color = when (fanSpeed) {
FanSpeed.OFF -> Color.GRAY
FanSpeed.LOW -> fanSpeedLowColor
FanSpeed.MEDIUM -> fanSpeedMediumColor
FanSpeed.HIGH -> fanSpeedMaxColor
}
// Draw the dial. 背景
canvas.drawCircle((width / 2).toFloat(), (height / 2).toFloat(), radius, paint)
// Draw the indicator circle.
val markerRadius = radius + RADIUS_OFFSET_INDICATOR
pointPosition.computeXYForSpeed(fanSpeed, markerRadius)
paint.color = Color.BLACK
canvas.drawCircle(pointPosition.x, pointPosition.y, radius/12, paint)
// Draw the text labels.
val labelRadius = radius + RADIUS_OFFSET_LABEL
for (i in FanSpeed.values()) {
pointPosition.computeXYForSpeed(i, labelRadius)
val label = resources.getString(i.label)
canvas.drawText(label, pointPosition.x, pointPosition.y, paint)
}
}
/**
* Computes the X/Y-coordinates for a label or indicator,
* given the FanSpeed and radius where the label should be drawn.
*
* @param pos Position (FanSpeed)
* @param radius Radius where label/indicator is to be drawn.
* @return 2-element array. Element 0 is X-coordinate, element 1 is Y-coordinate.
*/
private fun PointF.computeXYForSpeed(pos: FanSpeed, radius: Float) {
// Angles are in radians.
val startAngle = Math.PI * (9 / 8.0)
val angle = startAngle + pos.ordinal * (Math.PI / 4)
x = (radius * cos(angle)).toFloat() + width / 2
y = (radius * sin(angle)).toFloat() + height / 2
}
/**
* Updates the view's content description with the appropirate string for the
* current fan speed.
*/
private fun updateContentDescription() {
contentDescription = resources.getString(fanSpeed.label)
}
}
- 界面引入
<com.example.android.customfancontroller.DialView
android:id="@+id/dialView"
android:layout_width="@dimen/fan_dimen"
android:layout_height="@dimen/fan_dimen"
app:layout_constraintTop_toBottomOf="@+id/customViewLabel"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:layout_marginLeft="@dimen/default_margin"
android:layout_marginRight="@dimen/default_margin"
android:layout_marginTop="@dimen/default_margin"
app:fanColor1="#FFEB3B"
app:fanColor2="#CDDC39"
app:fanColor3="#009688"/>
- atts
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="DialView">
<attr name="fanColor1" format="color" />
<attr name="fanColor2" format="color" />
<attr name="fanColor3" format="color" />
</declare-styleable>
</resources>