Skip to content

Instantly share code, notes, and snippets.

@hrules6872
Last active December 10, 2019 20:48
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hrules6872/82ea8f8a7ad11419cb5ac8589cc95d73 to your computer and use it in GitHub Desktop.
Save hrules6872/82ea8f8a7ad11419cb5ac8589cc95d73 to your computer and use it in GitHub Desktop.
Dash/LineView custom view for Android
class LineView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
companion object {
const val ORIENTATION_VERTICAL = 0
const val ORIENTATION_HORIZONTAL = 1
private const val DEFAULT_ORIENTATION = ORIENTATION_HORIZONTAL
private val INTERVAL = floatArrayOf(10f, 10f)
private const val PHASE = 0f
private const val STYLE_SQUARE = 0
private const val STYLE_ROUND = 1
private const val STYLE_DASHED = 2
private const val DEFAULT_STYLE = STYLE_SQUARE
private const val DEFAULT_FLOATING_STATE = false
}
private val DEFAULT_LINE_WIDTH by lazy { context.dimen(R.dimen.outline_height) }
private val DEFAULT_LINE_COLOR by lazy { context.colorIntFromAttr(R.attr.colorOutline) }
private val DEFAULT_SHADOW_COLOR by lazy {
Color.argb(51, 0, 0, 0)
} // +info: https://gist.github.com/serglo/f9f0be9a66fd6755a0bda85f9c64e85f
private val DEFAULT_FAKE_ELEVATION by lazy { context.dimen(R.dimen.elevation_none) }
private val paint = Paint().apply {
color = lineColor
strokeWidth = lineWidth
setPaintStyle(DEFAULT_STYLE)
}
private val path = Path()
var lineWidth = DEFAULT_LINE_WIDTH
set(value) {
field = value
paint.strokeWidth = value
invalidate()
}
var lineColor = DEFAULT_LINE_COLOR
set(value) {
field = value
paint.color = value
invalidate()
}
var fakeElevation = DEFAULT_FAKE_ELEVATION
set(value) {
field = value
calculateShadow()
requestLayout()
invalidate()
}
var isFloating = DEFAULT_FLOATING_STATE
set(value) {
field = value
requestLayout()
invalidate()
}
var orientation = DEFAULT_ORIENTATION
set(value) {
(value == ORIENTATION_HORIZONTAL || value == ORIENTATION_VERTICAL).ifTrue {
field = value
requestLayout()
invalidate()
}
}
init {
attrs?.use(context, R.styleable.LineView) {
lineWidth = getDimension(R.styleable.LineView_lw_lineWidth, DEFAULT_LINE_WIDTH)
lineColor = getColor(R.styleable.LineView_lw_lineColor, DEFAULT_LINE_COLOR)
fakeElevation = getDimension(R.styleable.LineView_lw_fakeElevation, DEFAULT_FAKE_ELEVATION)
isFloating = getBoolean(R.styleable.LineView_lw_floating, DEFAULT_FLOATING_STATE)
orientation = getInt(R.styleable.LineView_lw_orientation, DEFAULT_ORIENTATION)
paint.setPaintStyle(getInt(R.styleable.LineView_lw_style, DEFAULT_STYLE))
}
}
private fun calculateSize(desiredSize: Int, measureSpec: Int): Int {
val mode = MeasureSpec.getMode(measureSpec)
val specSize = MeasureSpec.getSize(measureSpec)
return when (mode) {
MeasureSpec.EXACTLY -> specSize
MeasureSpec.AT_MOST -> when {
desiredSize < specSize -> desiredSize
else -> specSize
}
else -> desiredSize
}
}
// +info: https://gist.github.com/serglo/f9f0be9a66fd6755a0bda85f9c64e85f
private fun calculateShadow() = when (fakeElevation) {
context.dimen(R.dimen.elevation_xsmall) -> paint.setFakeElevation(2f, 2f)
context.dimen(R.dimen.elevation_small) -> paint.setFakeElevation(3f, 4f)
context.dimen(R.dimen.elevation_medium) -> paint.setFakeElevation(4f, 5f)
context.dimen(R.dimen.elevation_large) -> paint.setFakeElevation(6f, 10f)
context.dimen(R.dimen.elevation_xlarge) -> paint.setFakeElevation(8f, 10f)
context.dimen(R.dimen.elevation_xxlarge) -> paint.setFakeElevation(16f, 24f)
context.dimen(R.dimen.elevation_xxxlarge) -> paint.setFakeElevation(24f, 38f)
else -> paint.clearShadowLayer() // context.dimen(R.dimen.elevation_none)
}
private fun Paint.setFakeElevation(yOffset: Float, blur: Float) {
setShadowLayer(blur, 0f, yOffset, DEFAULT_SHADOW_COLOR)
}
private fun Paint.setPaintStyle(style: Int) = when (style) {
STYLE_ROUND -> {
strokeJoin = Paint.Join.ROUND
strokeCap = Paint.Cap.ROUND
pathEffect = CornerPathEffect(lineWidth)
isDither = true
isAntiAlias = true
}
STYLE_DASHED -> pathEffect = DashPathEffect(INTERVAL, PHASE)
else -> this.style = Paint.Style.STROKE
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val span = when (paint.pathEffect) {
is CornerPathEffect -> lineWidth
else -> 0f
} + if (isFloating) fakeElevation else 0f
when (orientation) {
ORIENTATION_VERTICAL -> {
setMeasuredDimension(calculateSize(lineWidth.toInt() + paddingLeft + paddingRight, widthMeasureSpec), heightMeasureSpec)
path.apply {
moveTo(measuredWidth / 2f, span)
lineTo(measuredWidth / 2f, measuredHeight.toFloat() - span)
}
}
ORIENTATION_HORIZONTAL -> {
setMeasuredDimension(
widthMeasureSpec,
calculateSize(lineWidth.toInt() + paddingTop + paddingBottom + (fakeElevation.toInt() * 2), heightMeasureSpec)
)
path.apply {
moveTo(span, measuredHeight / 2f)
lineTo(measuredWidth.toFloat() - span, measuredHeight / 2f)
}
}
}
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.drawPath(path, paint)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment