Last active
December 10, 2019 20:48
-
-
Save hector6872/82ea8f8a7ad11419cb5ac8589cc95d73 to your computer and use it in GitHub Desktop.
Dash/LineView custom view for Android
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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