Skip to content

Instantly share code, notes, and snippets.

@Ifeo-A
Last active April 4, 2020 21:04
Show Gist options
  • Save Ifeo-A/142b826d05f4afe00b971aac84c6968b to your computer and use it in GitHub Desktop.
Save Ifeo-A/142b826d05f4afe00b971aac84c6968b to your computer and use it in GitHub Desktop.
SemiCircleView (Custom View) implementation
<!-- /values/ -->
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="SemiCircleView">
<attr name="semiCircleIcon" format="reference" />
<attr name="semiCircleIconScaleMultiplier" format="float" />
<attr name="semiCircleElevation" format="float" />
<attr name="semiCircleShadowColor" format="color" />
<attr name="semiCircleIconPointDirection" format="enum">
<enum name="left" value="0" />
<enum name="right" value="1" />
</attr>
<attr name="semiCircleIconMarginStart" format="float" />
<attr name="semiCircleIconMarginEnd" format="float" />
</declare-styleable>
</resources>
<!-- /res/drwable/ -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="60dp"
android:height="60dp"
android:viewportWidth="492"
android:viewportHeight="492">
<path
android:fillColor="#FF000000"
android:pathData="M464.344,207.418l0.768,0.168H135.888l103.496,-103.724c5.068,-5.064 7.848,-11.924 7.848,-19.124c0,-7.2 -2.78,-14.012 -7.848,-19.088L223.28,49.538c-5.064,-5.064 -11.812,-7.864 -19.008,-7.864c-7.2,0 -13.952,2.78 -19.016,7.844L7.844,226.914C2.76,231.998 -0.02,238.77 0,245.974c-0.02,7.244 2.76,14.02 7.844,19.096l177.412,177.412c5.064,5.06 11.812,7.844 19.016,7.844c7.196,0 13.944,-2.788 19.008,-7.844l16.104,-16.112c5.068,-5.056 7.848,-11.808 7.848,-19.008c0,-7.196 -2.78,-13.592 -7.848,-18.652L134.72,284.406h329.992c14.828,0 27.288,-12.78 27.288,-27.6v-22.788C492,219.198 479.172,207.418 464.344,207.418z"/>
</vector>
<!-- /layout/ -->
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/constraintLayoutSemiCircleViewDemo"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#444444"
android:clipToPadding="false"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.example.view.customView.SemiCircleView
android:id="@+id/btnPrevious"
android:layout_width="80dp"
android:layout_height="120dp"
android:paddingStart="-12dp"
android:paddingEnd="12dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:semiCircleIcon="@drawable/ic_arrow"
app:semiCircleIconPointDirection="left"
app:semiCircleIconScaleMultiplier="0.5"
tools:visibility="visible" />
<com.example.view.customView.SemiCircleView
android:id="@+id/btnNext"
android:layout_width="80dp"
android:layout_height="120dp"
android:paddingStart="12dp"
android:paddingEnd="-12dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:semiCircleIcon="@drawable/ic_arrow"
app:semiCircleIconPointDirection="right"
app:semiCircleIconScaleMultiplier="0.5"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.view.View
import androidx.annotation.ColorInt
import androidx.annotation.StyleableRes
import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.toBitmap
import androidx.core.graphics.scale
class SemiCircleView(context: Context, attributeSet: AttributeSet) : View(context, attributeSet) {
/**
* The image to use in the view
*/
private var semiCircleIcon: Bitmap?
/* Value to scale the icon by */
private var semiCircleIconScaleMultiplier: Float
private var semiCircleElevation: Float
@ColorInt private var semiCircleShadowColor: Int
private val semiCircleIconMarginStart: Float
private val semiCircleIconMarginEnd: Float
/* The direction the icon should face */
private var semiCirclePointDirection: Int
private object IconPointDirection {
const val LEFT = 0
const val RIGHT = 1
}
private val viewBoundsPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
strokeWidth = 10f
color = Color.BLUE
style = Paint.Style.STROKE
}
private val paintForBitmapMatrix = Paint(Paint.ANTI_ALIAS_FLAG)
private var arcPaint: Paint
private val viewRectangleBounds: RectF by lazy {
RectF(0f, 0f, width.toFloat(), height.toFloat())
}
init {
context.theme.obtainStyledAttributes(
attributeSet,
R.styleable.SemiCircleView,
0,
0
).apply {
try {
semiCirclePointDirection =
getInt(R.styleable.SemiCircleView_semiCircleIconPointDirection,
IconPointDirection.LEFT)
semiCircleElevation = getFloat(R.styleable.SemiCircleView_semiCircleElevation, 16f)
semiCircleIconScaleMultiplier =
getFloat(R.styleable.SemiCircleView_semiCircleIconScaleMultiplier, 1f)
semiCircleShadowColor = getColor(R.styleable.SemiCircleView_semiCircleShadowColor,
Color.parseColor("#7e7e7e"))
semiCircleIconMarginStart =
getFloat(R.styleable.SemiCircleView_semiCircleIconMarginStart, 0f)
semiCircleIconMarginEnd =
getFloat(R.styleable.SemiCircleView_semiCircleIconMarginEnd, 0f)
arcPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
setShadowLayer(semiCircleElevation, 0f, 0f, semiCircleShadowColor)
setLayerType(LAYER_TYPE_SOFTWARE, this)
color = Color.WHITE
style = Paint.Style.FILL
}
semiCircleIcon = getDrawable(
providedOrDefault(R.styleable.SemiCircleView_semiCircleIcon))?.toBitmap()
} finally {
recycle()
}
}
}
private fun providedOrDefault(@StyleableRes res: Int?): Int {
return res ?: android.R.drawable.ic_menu_gallery
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
canvas ?: return
when (semiCirclePointDirection) {
IconPointDirection.LEFT -> {
drawSemiCircleLeftEdge(canvas)
drawIconLeft(canvas)
}
IconPointDirection.RIGHT -> {
drawSemiCircleRightEdge(canvas)
drawIconRight(canvas)
}
}
}
private fun drawIconLeft(canvas: Canvas) {
val matrix = Matrix()
if (semiCircleIcon != null) {
val icon = semiCircleIcon!!.scale(
(semiCircleIcon!!.width * semiCircleIconScaleMultiplier).toInt(),
(semiCircleIcon!!.height * semiCircleIconScaleMultiplier).toInt()
)
val halfHeightOfScaledIcon = ((icon.height) / 2)
matrix.setRotate(0f, icon.width / 2.toFloat(), icon.height / 2.toFloat())
matrix.postTranslate(viewRectangleBounds.left + semiCircleIconMarginStart,
(viewRectangleBounds.height() / 2) - halfHeightOfScaledIcon)
canvas.drawBitmap(
icon,
matrix,
paintForBitmapMatrix
)
} else {
val icon = ContextCompat.getDrawable(context,
android.R.drawable.ic_menu_gallery)?.toBitmap()!!
val scaledIcon = icon.scale(
(icon.width * semiCircleIconScaleMultiplier).toInt(),
(icon.height * semiCircleIconScaleMultiplier).toInt()
)
val halfHeightOfScaledIcon = ((scaledIcon.height) / 2)
matrix.setRotate(0f, icon.width / 2.toFloat(), icon.height / 2.toFloat())
matrix.postTranslate(viewRectangleBounds.left + semiCircleIconMarginStart,
(viewRectangleBounds.height() / 2) - halfHeightOfScaledIcon)
canvas.drawBitmap(
icon,
matrix,
paintForBitmapMatrix
)
}
}
private fun drawIconRight(canvas: Canvas) {
val matrix = Matrix()
if (semiCircleIcon != null) {
val icon = semiCircleIcon!!.scale((semiCircleIcon!!.width * semiCircleIconScaleMultiplier).toInt(),
(semiCircleIcon!!.height * semiCircleIconScaleMultiplier).toInt())
val halfHeightOfScaledIcon = ((icon.height) / 2)
matrix.setRotate(180f, icon.width / 2.toFloat(), icon.height / 2.toFloat())
matrix.postTranslate(viewRectangleBounds.right - (icon.width + semiCircleIconMarginEnd),
(viewRectangleBounds.height() / 2) - halfHeightOfScaledIcon)
canvas.drawBitmap(
icon,
matrix,
paintForBitmapMatrix
)
} else {
val icon = ContextCompat.getDrawable(context,
android.R.drawable.ic_menu_gallery)?.toBitmap()!!
val scaledIcon = icon.scale((icon.width * semiCircleIconScaleMultiplier).toInt(), (icon.height * semiCircleIconScaleMultiplier).toInt())
val halfHeightOfScaledIcon = ((scaledIcon.height) / 2)
matrix.setRotate(180f, icon.width / 2.toFloat(), icon.height / 2.toFloat())
matrix.postTranslate(viewRectangleBounds.left + semiCircleIconMarginEnd,
(viewRectangleBounds.height() / 2) - halfHeightOfScaledIcon)
canvas.drawBitmap(
icon,
matrix,
paintForBitmapMatrix
)
}
}
private fun drawSemiCircleLeftEdge(canvas: Canvas) {
val path = Path()
// Start point
path.moveTo(
viewRectangleBounds.left + paddingStart,
viewRectangleBounds.left + (paddingTop + semiCircleElevation)
)
path.cubicTo(
viewRectangleBounds.width() - paddingEnd, // x1
0f + paddingTop, // y1
viewRectangleBounds.width() - paddingEnd, // x2
viewRectangleBounds.height() - paddingBottom, // y2
0f + paddingStart, // x3
viewRectangleBounds.bottom - (paddingBottom + semiCircleElevation) // y3
)
path.close()
canvas.drawPath(path, arcPaint)
}
private fun drawSemiCircleRightEdge(canvas: Canvas) {
val path = Path()
path.moveTo(
viewRectangleBounds.width() - paddingEnd,
viewRectangleBounds.top + (paddingTop + semiCircleElevation)
)
path.cubicTo(
viewRectangleBounds.left + paddingStart, // x1
0f, // y1
viewRectangleBounds.left + paddingStart, // x2
viewRectangleBounds.height(), // y2
viewRectangleBounds.width() - paddingEnd, // x3
viewRectangleBounds.bottom - (paddingBottom + semiCircleElevation) // y3
)
path.close()
canvas.drawPath(path, arcPaint)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment