Bezier Points
Last active
April 4, 2020 21:04
-
-
Save Ifeo-A/142b826d05f4afe00b971aac84c6968b to your computer and use it in GitHub Desktop.
SemiCircleView (Custom View) implementation
This file contains 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
<!-- /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> |
This file contains 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
<!-- /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> |
This file contains 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
<!-- /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> |
This file contains 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
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