Created
February 17, 2021 15:12
-
-
Save hi-manshu/1effbcb736daa8cf1441f7f9b34d7ce5 to your computer and use it in GitHub Desktop.
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
import android.annotation.SuppressLint | |
import android.content.Context | |
import android.graphics.Canvas | |
import android.graphics.Color | |
import android.graphics.Paint | |
import android.graphics.Path | |
import android.graphics.PointF | |
import android.util.AttributeSet | |
import android.util.Log | |
import android.view.View | |
class BezierView : View { | |
private var mainPaint: Paint? = null | |
private var shadowPaint: Paint? = null | |
private var mainPath: Path? = null | |
private var shadowPath: Path? = null | |
private lateinit var outerArray: Array<PointF> | |
private lateinit var innerArray: Array<PointF> | |
private lateinit var progressArray: Array<PointF> | |
private var width = 0f | |
private var height = 0f | |
private var bezierOuterWidth = 0f | |
private var bezierOuterHeight = 0f | |
private var bezierInnerWidth = 0f | |
private var bezierInnerHeight = 0f | |
private val shadowHeight = dipf(context, 35) // this height will change bottomnavigation bg height | |
var color = 0 | |
set(value) { | |
field = value | |
mainPaint?.color = field | |
invalidate() | |
} | |
var shadowColor = 0 | |
set(value) { | |
field = value | |
shadowPaint?.setShadowLayer(dipf(context, 2), 0f, 0f, shadowColor) | |
invalidate() | |
} | |
var bezierX = 0f | |
set(value) { | |
if (value == field) | |
return | |
field = value | |
Log.e("BezierX", field.toString()); | |
invalidate() | |
} | |
var progress = 0f | |
set(value) { | |
if (value == field) | |
return | |
field = value | |
progressArray[1].x = bezierX - bezierInnerWidth / 2 | |
progressArray[2].x = bezierX - bezierInnerWidth / 4 | |
progressArray[3].x = bezierX - bezierInnerWidth / 4 | |
progressArray[4].x = bezierX | |
progressArray[5].x = bezierX + bezierInnerWidth / 4 | |
progressArray[6].x = bezierX + bezierInnerWidth / 4 | |
progressArray[7].x = bezierX + bezierInnerWidth / 2 | |
for (i in 2..6) { | |
if (progress <= 1f) {//convert to outer | |
progressArray[i].y = calculate(innerArray[i].y, outerArray[i].y) | |
} else { | |
progressArray[i].y = calculate(outerArray[i].y, innerArray[i].y) | |
} | |
} | |
if (field == 2f) | |
field = 0f | |
invalidate() | |
} | |
var waveHeight = 7 | |
set(value) { | |
if (value == field) | |
return | |
if (value < 7) { | |
field = 7 | |
invalidate() | |
} else { | |
field = value | |
invalidate() | |
} | |
} | |
@SuppressLint("NewApi") | |
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) { | |
initializeViews() | |
} | |
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { | |
initializeViews() | |
} | |
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { | |
initializeViews() | |
} | |
constructor(context: Context) : super(context) { | |
initializeViews() | |
} | |
private fun initializeViews() { | |
setWillNotDraw(false) | |
mainPath = Path() | |
shadowPath = Path() | |
outerArray = Array(11) { PointF() } | |
innerArray = Array(11) { PointF() } | |
progressArray = Array(11) { PointF() } | |
mainPaint = Paint(Paint.ANTI_ALIAS_FLAG) | |
mainPaint?.apply { | |
strokeWidth = 0f | |
isAntiAlias = true | |
style = Paint.Style.STROKE | |
color = this@BezierView.color | |
} | |
shadowPaint = Paint(Paint.ANTI_ALIAS_FLAG) | |
shadowPaint?.apply { | |
isAntiAlias = true | |
setShadowLayer(dipf(context, 4), 0f, 0f, shadowColor) | |
} | |
color = color | |
shadowColor = shadowColor | |
setLayerType(LAYER_TYPE_SOFTWARE, shadowPaint) | |
} | |
@SuppressLint("DrawAllocation") | |
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { | |
super.onMeasure(widthMeasureSpec, heightMeasureSpec) | |
width = MeasureSpec.getSize(widthMeasureSpec).toFloat() | |
height = MeasureSpec.getSize(heightMeasureSpec).toFloat() | |
bezierOuterWidth = dipf(context, 72) | |
bezierOuterHeight = dipf(context, 0) | |
bezierInnerWidth = dipf(context, 124) | |
bezierInnerHeight = dipf(context, 0) // this will change curve shape | |
val extra = shadowHeight | |
outerArray[0] = PointF(0f, bezierOuterHeight + extra) | |
outerArray[1] = PointF((bezierX - bezierOuterWidth / 2), bezierOuterHeight + extra) | |
outerArray[2] = PointF(bezierX - bezierOuterWidth / 4, bezierOuterHeight + extra) | |
outerArray[3] = PointF(bezierX - bezierOuterWidth / 4, extra) | |
outerArray[4] = PointF(bezierX, extra) | |
outerArray[5] = PointF(bezierX + bezierOuterWidth / 4, extra) | |
outerArray[6] = PointF(bezierX + bezierOuterWidth / 4, bezierOuterHeight + extra) | |
outerArray[7] = PointF(bezierX + bezierOuterWidth / 2, bezierOuterHeight + extra) | |
outerArray[8] = PointF(width, bezierOuterHeight + extra) | |
outerArray[9] = PointF(width, height) | |
outerArray[10] = PointF(0f, height) | |
} | |
override fun onDraw(canvas: Canvas) { | |
super.onDraw(canvas) | |
mainPath!!.reset() | |
shadowPath!!.reset() | |
if (progress == 0f) { | |
drawInner(canvas, true) | |
drawInner(canvas, false) | |
} else { | |
drawProgress(canvas, true) | |
drawProgress(canvas, false) | |
} | |
} | |
private fun drawInner(canvas: Canvas, isShadow: Boolean) { | |
val paint = if (isShadow) shadowPaint else mainPaint | |
val path = if (isShadow) shadowPath else mainPath | |
paint?.apply { | |
strokeWidth = 2f | |
isAntiAlias = true | |
style = Paint.Style.FILL | |
color = this@BezierView.color | |
} | |
calculateInner() | |
path!!.lineTo(innerArray[0].x, innerArray[0].y) | |
path.lineTo(innerArray[1].x, innerArray[1].y) | |
path.cubicTo(innerArray[2].x, innerArray[2].y, innerArray[3].x, innerArray[3].y, innerArray[4].x, innerArray[4].y) | |
path.cubicTo(innerArray[5].x, innerArray[5].y, innerArray[6].x, innerArray[6].y, innerArray[7].x, innerArray[7].y) | |
path.lineTo(innerArray[8].x, innerArray[8].y) | |
path.lineTo(innerArray[9].x, innerArray[9].y) | |
path.lineTo(innerArray[10].x, innerArray[10].y) | |
progressArray = innerArray.clone() | |
canvas.drawPath(path, paint!!) | |
} | |
private fun calculateInner() { | |
val extra = shadowHeight | |
innerArray[0] = PointF(0f, bezierInnerHeight + extra) | |
innerArray[1] = PointF((bezierX - bezierInnerWidth / 2), bezierInnerHeight + extra) | |
innerArray[2] = PointF(bezierX - bezierInnerWidth / 4, bezierInnerHeight + extra) | |
innerArray[3] = PointF(bezierX - bezierInnerWidth / 4, (height - extra) / waveHeight) | |
innerArray[4] = PointF(bezierX, (height - extra) / waveHeight) | |
innerArray[5] = PointF(bezierX + bezierInnerWidth / 4, (height - extra) / waveHeight) | |
innerArray[6] = PointF(bezierX + bezierInnerWidth / 4, bezierInnerHeight + extra) | |
innerArray[7] = PointF(bezierX + bezierInnerWidth / 2, bezierInnerHeight + extra) | |
innerArray[8] = PointF(width, bezierInnerHeight + extra) | |
innerArray[9] = PointF(width, height) | |
innerArray[10] = PointF(0f, height) | |
} | |
private fun drawProgress(canvas: Canvas, isShadow: Boolean) { | |
val paint = if (isShadow) shadowPaint else mainPaint | |
val path = if (isShadow) shadowPath else mainPath | |
path!!.lineTo(progressArray[0].x, progressArray[0].y) | |
path.lineTo(progressArray[1].x, progressArray[1].y) | |
path.cubicTo(progressArray[2].x, progressArray[2].y, progressArray[3].x, progressArray[3].y, progressArray[4].x, progressArray[4].y) | |
path.cubicTo(progressArray[5].x, progressArray[5].y, progressArray[6].x, progressArray[6].y, progressArray[7].x, progressArray[7].y) | |
path.lineTo(progressArray[8].x, progressArray[8].y) | |
path.lineTo(progressArray[9].x, progressArray[9].y) | |
path.lineTo(progressArray[10].x, progressArray[10].y) | |
canvas.drawPath(path, paint!!) | |
} | |
private fun calculate(start: Float, end: Float): Float { | |
var p = progress | |
if (p > 1f) | |
p = progress - 1f | |
if (p in 0.9f..1f) | |
calculateInner() | |
return (p * (end - start)) + start | |
} | |
} |
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
import android.animation.ValueAnimator | |
import android.content.Context | |
import android.util.AttributeSet | |
import android.util.Log | |
import androidx.appcompat.widget.AppCompatImageView | |
import androidx.interpolator.view.animation.FastOutSlowInInterpolator | |
import com.koel.app.R | |
import kotlin.math.ceil | |
internal class CellImageView : AppCompatImageView { | |
var isBitmap = false | |
set(value) { | |
field = value | |
draw() | |
} | |
var useColor = true | |
set(value) { | |
field = value | |
draw() | |
} | |
var resource = 0 | |
set(value) { | |
field = value | |
draw() | |
} | |
var color = 0 | |
set(value) { | |
field = value | |
draw() | |
} | |
var size = dip(context, 24) | |
set(value) { | |
field = value | |
requestLayout() | |
} | |
private var actionBackgroundAlpha = false | |
private var changeSize = true | |
private var fitImage = false | |
private var colorAnimator: ValueAnimator? = null | |
private var allowDraw = false | |
constructor(context: Context) : super(context) { | |
initializeView() | |
} | |
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { | |
setAttributeFromXml(context, attrs) | |
initializeView() | |
} | |
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { | |
setAttributeFromXml(context, attrs) | |
initializeView() | |
} | |
private fun setAttributeFromXml(context: Context, attrs: AttributeSet) { | |
val a = context.theme.obtainStyledAttributes(attrs, R.styleable.CellImageView, 0, 0) | |
try { | |
a.apply { | |
isBitmap = getBoolean(R.styleable.CellImageView_icon_imageview_isBitmap, isBitmap) | |
useColor = getBoolean(R.styleable.CellImageView_icon_imageview_useColor, useColor) | |
resource = getResourceId(R.styleable.CellImageView_icon_imageview_resource, resource) | |
color = getColor(R.styleable.CellImageView_icon_imageview_color, color) | |
size = getDimensionPixelSize(R.styleable.CellImageView_icon_imageview_size, size) | |
actionBackgroundAlpha = getBoolean(R.styleable.CellImageView_icon_imageview_actionBackgroundAlpha, actionBackgroundAlpha) | |
changeSize = getBoolean(R.styleable.CellImageView_icon_imageview_changeSize, changeSize) | |
fitImage = getBoolean(R.styleable.CellImageView_icon_imageview_fitImage, fitImage) | |
} | |
} finally { | |
a.recycle() | |
} | |
} | |
private fun initializeView() { | |
allowDraw = true | |
draw() | |
} | |
private fun draw() { | |
if (!allowDraw) | |
return | |
if (resource == 0) | |
return | |
if (isBitmap) { | |
try { | |
val drawable = if (color == 0) context.getDrawableCompat(resource) else DrawableHelper.changeColorDrawableRes(context, resource, color) | |
setImageDrawable(drawable) | |
} catch (e: Exception) { | |
e.printStackTrace() | |
} | |
return | |
} | |
if (useColor && color == 0) | |
return | |
val c = if (useColor) color else -2 | |
try { | |
setImageDrawable(DrawableHelper.changeColorDrawableVector(context, resource, c)) | |
} catch (e: Exception) { | |
e.printStackTrace() | |
} | |
} | |
fun changeColorByAnim(newColor: Int, d: Long = 250L) { | |
if (color == 0) { | |
color = newColor | |
return | |
} | |
val lastColor = color | |
colorAnimator?.cancel() | |
colorAnimator = ValueAnimator.ofFloat(0f, 1f) | |
colorAnimator?.apply { | |
duration = d | |
interpolator = FastOutSlowInInterpolator() | |
addUpdateListener { animation -> | |
val f = animation.animatedFraction | |
color = ColorHelper.mixTwoColors(newColor, lastColor, f) | |
} | |
start() | |
} | |
} | |
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { | |
Log.e("heightMeasureSpec", heightMeasureSpec.toString() + "") | |
if (fitImage) { | |
val d = drawable | |
if (d != null) { | |
val width = MeasureSpec.getSize(widthMeasureSpec) | |
val height = ceil((width.toFloat() * d.intrinsicHeight.toFloat() / d.intrinsicWidth).toDouble()).toInt() | |
setMeasuredDimension(width, height) | |
} else { | |
super.onMeasure(widthMeasureSpec, heightMeasureSpec) | |
} | |
return | |
} | |
if (isBitmap || !changeSize) { | |
super.onMeasure(widthMeasureSpec, heightMeasureSpec) | |
return | |
} | |
val newSize = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY) | |
super.onMeasure(newSize, newSize) | |
} | |
} |
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
private fun getDP(context: Context) = context.resources.displayMetrics.density | |
internal fun dipf(context: Context, f: Float) = f * getDP(context) | |
internal fun dipf(context: Context, i: Int) = i * getDP(context) | |
internal fun dip(context: Context, i: Int) = (i * getDP(context)).toInt() | |
internal fun toDP(context: Context,value: Int): Int = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value.toFloat(),context.resources.displayMetrics).toInt() | |
internal fun toPx(context: Context,value: Float): Float = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, value,context.resources.displayMetrics) | |
internal object DrawableHelper { | |
fun changeColorDrawableVector(c: Context?, resDrawable: Int, color: Int): Drawable? { | |
if (c == null) | |
return null | |
val d = VectorDrawableCompat.create(c.resources, resDrawable, null) ?: return null | |
d.mutate() | |
if (color != -2) { | |
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { | |
d.colorFilter = BlendModeColorFilter(color, BlendMode.SRC_IN) | |
} else { | |
d.setColorFilter(color, PorterDuff.Mode.SRC_IN) | |
} | |
} | |
return d | |
} | |
fun changeColorDrawableRes(c: Context?, resDrawable: Int, color: Int): Drawable? { | |
if (c == null) | |
return null | |
val d = ContextCompat.getDrawable(c, resDrawable) ?: return null | |
d.mutate() | |
if (color != -2) { | |
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { | |
d.colorFilter = BlendModeColorFilter(color, BlendMode.SRC_IN) | |
} else { | |
d.setColorFilter(color, PorterDuff.Mode.SRC_IN) | |
} | |
} | |
return d | |
} | |
} | |
internal object ColorHelper { | |
fun mixTwoColors(color1: Int, color2: Int, amount: Float): Int { | |
val alphaChannel = 24 | |
val redChannel = 16 | |
val greenChannel = 8 | |
val inverseAmount = 1.0f - amount | |
val a = | |
((color1 shr alphaChannel and 0xff).toFloat() * amount + (color2 shr alphaChannel and 0xff).toFloat() * inverseAmount).toInt() and 0xff | |
val r = | |
((color1 shr redChannel and 0xff).toFloat() * amount + (color2 shr redChannel and 0xff).toFloat() * inverseAmount).toInt() and 0xff | |
val g = | |
((color1 shr greenChannel and 0xff).toFloat() * amount + (color2 shr greenChannel and 0xff).toFloat() * inverseAmount).toInt() and 0xff | |
val b = | |
((color1 and 0xff).toFloat() * amount + (color2 and 0xff).toFloat() * inverseAmount).toInt() and 0xff | |
return a shl alphaChannel or (r shl redChannel) or (g shl greenChannel) or b | |
} | |
} | |
internal fun Context.getDrawableCompat(res: Int) = ContextCompat.getDrawable(this, res) | |
internal inline fun <T : View?> T.runAfterDelay(delay: Long, crossinline f: T.() -> Unit) { | |
this?.postDelayed({ | |
try { | |
f() | |
} catch (e: Exception) { | |
} | |
}, delay) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment