Created
April 23, 2019 02:50
-
-
Save erickogi/6c3f0bb52bbd04eb2f5699d1578a0924 to your computer and use it in GitHub Desktop.
Revisions
-
erickogi created this gist
Apr 23, 2019 .There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,82 @@ package com.kogicodes.sokoni.utils.Badge; import android.graphics.PointF; import android.graphics.drawable.Drawable; import android.view.View; public interface Badge { int getBadgeNumber(); Badge setBadgeNumber(int badgeNum); String getBadgeText(); Badge setBadgeText(String badgeText); boolean isExactMode(); Badge setExactMode(boolean isExact); boolean isShowShadow(); Badge setShowShadow(boolean showShadow); Badge stroke(int color, float width, boolean isDpValue); int getBadgeBackgroundColor(); Badge setBadgeBackgroundColor(int color); Badge setBadgeBackground(Drawable drawable, boolean clip); Drawable getBadgeBackground(); Badge setBadgeBackground(Drawable drawable); int getBadgeTextColor(); Badge setBadgeTextColor(int color); Badge setBadgeTextSize(float size, boolean isSpValue); float getBadgeTextSize(boolean isSpValue); Badge setBadgePadding(float padding, boolean isDpValue); float getBadgePadding(boolean isDpValue); boolean isDraggable(); int getBadgeGravity(); Badge setBadgeGravity(int gravity); Badge setGravityOffset(float offset, boolean isDpValue); Badge setGravityOffset(float offsetX, float offsetY, boolean isDpValue); float getGravityOffsetX(boolean isDpValue); float getGravityOffsetY(boolean isDpValue); Badge setOnDragStateChangedListener(OnDragStateChangedListener l); PointF getDragCenter(); Badge bindTarget(View view); View getTargetView(); void hide(boolean animate); interface OnDragStateChangedListener { int STATE_START = 1; int STATE_DRAGGING = 2; int STATE_DRAGGING_OUT_OF_RANGE = 3; int STATE_CANCELED = 4; int STATE_SUCCEED = 5; void onDragStateChanged(int dragState, Badge badge, View targetView); } } 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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,93 @@ package com.kogicodes.sokoni.utils.Badge import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.animation.ValueAnimator import android.graphics.Bitmap import android.graphics.Canvas import android.graphics.Paint import android.graphics.PointF import java.lang.ref.WeakReference import java.util.Random class BadgeAnimator(badgeBitmap: Bitmap, center: PointF, badge: BadgeView) : ValueAnimator() { private val mFragments: Array<Array<BitmapFragment?>> private val mWeakBadge: WeakReference<BadgeView> init { mWeakBadge = WeakReference(badge) setFloatValues(0f, 1f) duration = 500 mFragments = getFragments(badgeBitmap, center) addUpdateListener { val badgeView = mWeakBadge.get() if (badgeView == null || !badgeView.isShown) { cancel() } else { badgeView.invalidate() } } addListener(object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator) { val badgeView = mWeakBadge.get() badgeView?.reset() } }) } fun draw(canvas: Canvas) { for (i in mFragments.indices) { for (j in 0 until mFragments[i].size) { val bf = mFragments[i][j] val value = java.lang.Float.parseFloat(animatedValue.toString()) bf?.updata(value, canvas) } } } private fun getFragments(badgeBitmap: Bitmap, center: PointF): Array<Array<BitmapFragment?>> { val width = badgeBitmap.width val height = badgeBitmap.height val fragmentSize = Math.min(width, height) / 6f val startX = center.x - badgeBitmap.width / 2f val startY = center.y - badgeBitmap.height / 2f var fragments = Array<Array<BitmapFragment?>>((height / fragmentSize).toInt()) { arrayOfNulls((width / fragmentSize)?.toInt()) } for (i in fragments.indices) { for (j in 0 until fragments[i].size) { val bf = BitmapFragment() bf.color = badgeBitmap.getPixel((j * fragmentSize).toInt(), (i * fragmentSize).toInt()) bf.x = startX + j * fragmentSize bf.y = startY + i * fragmentSize bf.size = fragmentSize bf.maxSize = Math.max(width, height) fragments[i][j] = bf } } badgeBitmap.recycle() return fragments } private inner class BitmapFragment { internal var random: Random internal var x: Float = 0.toFloat() internal var y: Float = 0.toFloat() internal var size: Float = 0.toFloat() internal var color: Int = 0 internal var maxSize: Int = 0 internal var paint: Paint init { paint = Paint() paint.isAntiAlias = true paint.style = Paint.Style.FILL random = Random() } fun updata(value: Float, canvas: Canvas) { paint.color = color x = x + 0.1f * random.nextInt(maxSize).toFloat() * (random.nextFloat() - 0.5f) y = y + 0.1f * random.nextInt(maxSize).toFloat() * (random.nextFloat() - 0.5f) canvas.drawCircle(x, y, size - value * size, paint) } } } 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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,775 @@ package com.kogicodes.sokoni.utils.Badge import android.content.Context import android.graphics.Bitmap import android.graphics.Canvas import android.graphics.Color import android.graphics.Paint import android.graphics.Path import android.graphics.PointF import android.graphics.PorterDuff import android.graphics.PorterDuffXfermode import android.graphics.RectF import android.graphics.drawable.Drawable import android.os.Build import android.text.TextPaint import android.text.TextUtils import android.util.AttributeSet import android.view.Gravity import android.view.MotionEvent import android.view.View import android.view.ViewGroup import android.view.ViewParent import android.widget.FrameLayout import java.util.ArrayList class BadgeView private constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int = 0) : View(context, attrs, defStyleAttr), Badge { override var badgeBackgroundColor: Int = 0 protected set(value: Int) { super.badgeBackgroundColor = value } protected var mColorBackgroundBorder: Int = 0 override var badgeTextColor: Int = 0 protected set(value: Int) { super.badgeTextColor = value } override var badgeBackground: Drawable? = null protected set(value: Drawable?) { super.badgeBackground = value } protected var mBitmapClip: Bitmap? = null protected var mDrawableBackgroundClip: Boolean = false protected var mBackgroundBorderWidth: Float = 0.toFloat() protected var mBadgeTextSize: Float = 0.toFloat() protected var mBadgePadding: Float = 0.toFloat() override var badgeNumber: Int = 0 protected set(value: Int) { super.badgeNumber = value } override var badgeText: String? = null protected set(value: String?) { super.badgeText = value } override var isDraggable: Boolean = false protected set(value: Boolean) { super.isDraggable = value } protected var mDragging: Boolean = false override var isExactMode: Boolean = false protected set(value: Boolean) { super.isExactMode = value } override var isShowShadow: Boolean = false protected set(value: Boolean) { super.isShowShadow = value } override var badgeGravity: Int = 0 protected set(value: Int) { super.badgeGravity = value } protected var mGravityOffsetX: Float = 0.toFloat() protected var mGravityOffsetY: Float = 0.toFloat() protected var mDefalutRadius: Float = 0.toFloat() protected var mFinalDragDistance: Float = 0.toFloat() protected var mDragQuadrant: Int = 0 protected var mDragOutOfRange: Boolean = false protected var mBadgeTextRect: RectF protected var mBadgeBackgroundRect: RectF protected var mDragPath: Path protected var mBadgeTextFontMetrics: Paint.FontMetrics protected var mBadgeCenter: PointF protected var mDragCenter: PointF protected var mRowBadgeCenter: PointF protected var mControlPoint: PointF protected var mInnertangentPoints: MutableList<PointF> override var targetView: View protected set(value: View) { super.targetView = value } protected var mWidth: Int = 0 protected var mHeight: Int = 0 protected var mBadgeTextPaint: TextPaint protected var mBadgeBackgroundPaint: Paint protected var mBadgeBackgroundBorderPaint: Paint protected var mAnimator: BadgeAnimator? = null protected var mDragStateChangedListener: Badge.OnDragStateChangedListener? = null protected var mActivityRoot: ViewGroup? = null private val badgeCircleRadius: Float get() = if (badgeText!!.isEmpty()) { mBadgePadding } else if (badgeText!!.length == 1) { if (mBadgeTextRect.height() > mBadgeTextRect.width()) mBadgeTextRect.height() / 2f + mBadgePadding * 0.5f else mBadgeTextRect.width() / 2f + mBadgePadding * 0.5f } else { mBadgeBackgroundRect.height() / 2f } override val dragCenter: PointF get() = if (isDraggable && mDragging) mDragCenter else null constructor(context: Context) : this(context, null) {} init { init() } private fun init() { setLayerType(View.LAYER_TYPE_SOFTWARE, null) mBadgeTextRect = RectF() mBadgeBackgroundRect = RectF() mDragPath = Path() mBadgeCenter = PointF() mDragCenter = PointF() mRowBadgeCenter = PointF() mControlPoint = PointF() mInnertangentPoints = ArrayList() mBadgeTextPaint = TextPaint() mBadgeTextPaint.isAntiAlias = true mBadgeTextPaint.isSubpixelText = true mBadgeTextPaint.isFakeBoldText = true mBadgeTextPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN) mBadgeBackgroundPaint = Paint() mBadgeBackgroundPaint.isAntiAlias = true mBadgeBackgroundPaint.style = Paint.Style.FILL mBadgeBackgroundBorderPaint = Paint() mBadgeBackgroundBorderPaint.isAntiAlias = true mBadgeBackgroundBorderPaint.style = Paint.Style.STROKE badgeBackgroundColor = -0x17b1c0 badgeTextColor = -0x1 mBadgeTextSize = DisplayUtil.dp2px(context, 11f).toFloat() mBadgePadding = DisplayUtil.dp2px(context, 5f).toFloat() badgeNumber = 0 badgeGravity = Gravity.END or Gravity.TOP mGravityOffsetX = DisplayUtil.dp2px(context, 1f).toFloat() mGravityOffsetY = DisplayUtil.dp2px(context, 1f).toFloat() mFinalDragDistance = DisplayUtil.dp2px(context, 90f).toFloat() isShowShadow = true mDrawableBackgroundClip = false if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { translationZ = 1000f } } override fun bindTarget(targetView: View): Badge { if (targetView == null) { throw IllegalStateException("targetView can not be null") } if (parent != null) { (parent as ViewGroup).removeView(this) } val targetParent = targetView.parent if (targetParent != null && targetParent is ViewGroup) { this.targetView = targetView if (targetParent is BadgeContainer) { targetParent.addView(this) } else { val index = targetParent.indexOfChild(targetView) val targetParams = targetView.layoutParams targetParent.removeView(targetView) val badgeContainer = BadgeContainer(context) badgeContainer.id = targetView.id targetView.id = View.NO_ID targetParent.addView(badgeContainer, index, targetParams) badgeContainer.addView(targetView) badgeContainer.addView(this) } } else { throw IllegalStateException("targetView must have a parent") } return this } override fun onAttachedToWindow() { super.onAttachedToWindow() if (mActivityRoot == null) findViewRoot(targetView) } private fun findViewRoot(view: View) { mActivityRoot = view.rootView as ViewGroup if (mActivityRoot == null) { findActivityRoot(view) } } private fun findActivityRoot(view: View) { if (view.parent != null && view.parent is View) { findActivityRoot(view.parent as View) } else if (view is ViewGroup) { mActivityRoot = view } } override fun onTouchEvent(event: MotionEvent): Boolean { when (event.actionMasked) { MotionEvent.ACTION_DOWN, MotionEvent.ACTION_POINTER_DOWN -> { val x = event.x val y = event.y if (isDraggable && event.getPointerId(event.actionIndex) == 0 && x > mBadgeBackgroundRect.left && x < mBadgeBackgroundRect.right && y > mBadgeBackgroundRect.top && y < mBadgeBackgroundRect.bottom && badgeText != null) { initRowBadgeCenter() mDragging = true updataListener(Badge.OnDragStateChangedListener.STATE_START) mDefalutRadius = DisplayUtil.dp2px(context, 7f).toFloat() parent.requestDisallowInterceptTouchEvent(true) screenFromWindow(true) mDragCenter.x = event.rawX mDragCenter.y = event.rawY } } MotionEvent.ACTION_MOVE -> if (mDragging) { mDragCenter.x = event.rawX mDragCenter.y = event.rawY invalidate() } MotionEvent.ACTION_UP, MotionEvent.ACTION_POINTER_UP, MotionEvent.ACTION_CANCEL -> if (event.getPointerId(event.actionIndex) == 0 && mDragging) { mDragging = false onPointerUp() } } return mDragging || super.onTouchEvent(event) } private fun onPointerUp() { if (mDragOutOfRange) { animateHide(mDragCenter) updataListener(Badge.OnDragStateChangedListener.STATE_SUCCEED) } else { reset() updataListener(Badge.OnDragStateChangedListener.STATE_CANCELED) } } protected fun createBadgeBitmap(): Bitmap { val bitmap = Bitmap.createBitmap(mBadgeBackgroundRect.width().toInt() + DisplayUtil.dp2px(context, 3f), mBadgeBackgroundRect.height().toInt() + DisplayUtil.dp2px(context, 3f), Bitmap.Config.ARGB_8888) val canvas = Canvas(bitmap) drawBadge(canvas, PointF(canvas.width / 2f, canvas.height / 2f), badgeCircleRadius) return bitmap } protected fun screenFromWindow(screen: Boolean) { if (parent != null) { (parent as ViewGroup).removeView(this) } if (screen) { mActivityRoot!!.addView(this, FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)) } else { bindTarget(targetView) } } private fun showShadowImpl(showShadow: Boolean) { var x = DisplayUtil.dp2px(context, 1f) var y = DisplayUtil.dp2px(context, 1.5f) when (mDragQuadrant) { 1 -> { x = DisplayUtil.dp2px(context, 1f) y = DisplayUtil.dp2px(context, -1.5f) } 2 -> { x = DisplayUtil.dp2px(context, -1f) y = DisplayUtil.dp2px(context, -1.5f) } 3 -> { x = DisplayUtil.dp2px(context, -1f) y = DisplayUtil.dp2px(context, 1.5f) } 4 -> { x = DisplayUtil.dp2px(context, 1f) y = DisplayUtil.dp2px(context, 1.5f) } } mBadgeBackgroundPaint.setShadowLayer((if (showShadow) DisplayUtil.dp2px(context, 2f) else 0).toFloat(), x.toFloat(), y.toFloat(), 0x33000000) } override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { super.onSizeChanged(w, h, oldw, oldh) mWidth = w mHeight = h } override fun onDraw(canvas: Canvas) { if (mAnimator != null && mAnimator!!.isRunning) { mAnimator!!.draw(canvas) return } if (badgeText != null) { initPaints() val badgeRadius = badgeCircleRadius val startCircleRadius = mDefalutRadius * (1 - MathUtil.getPointDistance(mRowBadgeCenter, mDragCenter) / mFinalDragDistance) if (isDraggable && mDragging) { mDragQuadrant = MathUtil.getQuadrant(mDragCenter, mRowBadgeCenter) showShadowImpl(isShowShadow) if (mDragOutOfRange = startCircleRadius < DisplayUtil.dp2px(context, 1.5f)) { updataListener(Badge.OnDragStateChangedListener.STATE_DRAGGING_OUT_OF_RANGE) drawBadge(canvas, mDragCenter, badgeRadius) } else { updataListener(Badge.OnDragStateChangedListener.STATE_DRAGGING) drawDragging(canvas, startCircleRadius, badgeRadius) drawBadge(canvas, mDragCenter, badgeRadius) } } else { findBadgeCenter() drawBadge(canvas, mBadgeCenter, badgeRadius) } } } private fun initPaints() { showShadowImpl(isShowShadow) mBadgeBackgroundPaint.color = badgeBackgroundColor mBadgeBackgroundBorderPaint.color = mColorBackgroundBorder mBadgeBackgroundBorderPaint.strokeWidth = mBackgroundBorderWidth mBadgeTextPaint.color = badgeTextColor mBadgeTextPaint.textAlign = Paint.Align.CENTER } private fun drawDragging(canvas: Canvas, startRadius: Float, badgeRadius: Float) { val dy = mDragCenter.y - mRowBadgeCenter.y val dx = mDragCenter.x - mRowBadgeCenter.x mInnertangentPoints.clear() if (dx != 0f) { val k1 = (dy / dx).toDouble() val k2 = -1 / k1 MathUtil.getInnertangentPoints(mDragCenter, badgeRadius, k2, mInnertangentPoints) MathUtil.getInnertangentPoints(mRowBadgeCenter, startRadius, k2, mInnertangentPoints) } else { MathUtil.getInnertangentPoints(mDragCenter, badgeRadius, 0.0, mInnertangentPoints) MathUtil.getInnertangentPoints(mRowBadgeCenter, startRadius, 0.0, mInnertangentPoints) } mDragPath.reset() mDragPath.addCircle(mRowBadgeCenter.x, mRowBadgeCenter.y, startRadius, if (mDragQuadrant == 1 || mDragQuadrant == 2) Path.Direction.CCW else Path.Direction.CW) mControlPoint.x = (mRowBadgeCenter.x + mDragCenter.x) / 2.0f mControlPoint.y = (mRowBadgeCenter.y + mDragCenter.y) / 2.0f mDragPath.moveTo(mInnertangentPoints[2].x, mInnertangentPoints[2].y) mDragPath.quadTo(mControlPoint.x, mControlPoint.y, mInnertangentPoints[0].x, mInnertangentPoints[0].y) mDragPath.lineTo(mInnertangentPoints[1].x, mInnertangentPoints[1].y) mDragPath.quadTo(mControlPoint.x, mControlPoint.y, mInnertangentPoints[3].x, mInnertangentPoints[3].y) mDragPath.lineTo(mInnertangentPoints[2].x, mInnertangentPoints[2].y) mDragPath.close() canvas.drawPath(mDragPath, mBadgeBackgroundPaint) //draw dragging border if (mColorBackgroundBorder != 0 && mBackgroundBorderWidth > 0) { mDragPath.reset() mDragPath.moveTo(mInnertangentPoints[2].x, mInnertangentPoints[2].y) mDragPath.quadTo(mControlPoint.x, mControlPoint.y, mInnertangentPoints[0].x, mInnertangentPoints[0].y) mDragPath.moveTo(mInnertangentPoints[1].x, mInnertangentPoints[1].y) mDragPath.quadTo(mControlPoint.x, mControlPoint.y, mInnertangentPoints[3].x, mInnertangentPoints[3].y) val startY: Float val startX: Float if (mDragQuadrant == 1 || mDragQuadrant == 2) { startX = mInnertangentPoints[2].x - mRowBadgeCenter.x startY = mRowBadgeCenter.y - mInnertangentPoints[2].y } else { startX = mInnertangentPoints[3].x - mRowBadgeCenter.x startY = mRowBadgeCenter.y - mInnertangentPoints[3].y } val startAngle = 360 - MathUtil.radianToAngle(MathUtil.getTanRadian(Math.atan((startY / startX).toDouble()), if (mDragQuadrant - 1 == 0) 4 else mDragQuadrant - 1)).toFloat() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { mDragPath.addArc(mRowBadgeCenter.x - startRadius, mRowBadgeCenter.y - startRadius, mRowBadgeCenter.x + startRadius, mRowBadgeCenter.y + startRadius, startAngle, 180f) } else { mDragPath.addArc(RectF(mRowBadgeCenter.x - startRadius, mRowBadgeCenter.y - startRadius, mRowBadgeCenter.x + startRadius, mRowBadgeCenter.y + startRadius), startAngle, 180f) } canvas.drawPath(mDragPath, mBadgeBackgroundBorderPaint) } } private fun drawBadge(canvas: Canvas, center: PointF, radius: Float) { var radius = radius if (center.x == -1000f && center.y == -1000f) { return } if (badgeText!!.isEmpty() || badgeText!!.length == 1) { mBadgeBackgroundRect.left = center.x - radius.toInt() mBadgeBackgroundRect.top = center.y - radius.toInt() mBadgeBackgroundRect.right = center.x + radius.toInt() mBadgeBackgroundRect.bottom = center.y + radius.toInt() if (badgeBackground != null) { drawBadgeBackground(canvas) } else { canvas.drawCircle(center.x, center.y, radius, mBadgeBackgroundPaint) if (mColorBackgroundBorder != 0 && mBackgroundBorderWidth > 0) { canvas.drawCircle(center.x, center.y, radius, mBadgeBackgroundBorderPaint) } } } else { mBadgeBackgroundRect.left = center.x - (mBadgeTextRect.width() / 2f + mBadgePadding) mBadgeBackgroundRect.top = center.y - (mBadgeTextRect.height() / 2f + mBadgePadding * 0.5f) mBadgeBackgroundRect.right = center.x + (mBadgeTextRect.width() / 2f + mBadgePadding) mBadgeBackgroundRect.bottom = center.y + (mBadgeTextRect.height() / 2f + mBadgePadding * 0.5f) radius = mBadgeBackgroundRect.height() / 2f if (badgeBackground != null) { drawBadgeBackground(canvas) } else { canvas.drawRoundRect(mBadgeBackgroundRect, radius, radius, mBadgeBackgroundPaint) if (mColorBackgroundBorder != 0 && mBackgroundBorderWidth > 0) { canvas.drawRoundRect(mBadgeBackgroundRect, radius, radius, mBadgeBackgroundBorderPaint) } } } if (!badgeText!!.isEmpty()) { canvas.drawText(badgeText!!, center.x, (mBadgeBackgroundRect.bottom + mBadgeBackgroundRect.top - mBadgeTextFontMetrics.bottom - mBadgeTextFontMetrics.top) / 2f, mBadgeTextPaint) } } private fun drawBadgeBackground(canvas: Canvas) { mBadgeBackgroundPaint.setShadowLayer(0f, 0f, 0f, 0) val left = mBadgeBackgroundRect.left.toInt() val top = mBadgeBackgroundRect.top.toInt() var right = mBadgeBackgroundRect.right.toInt() var bottom = mBadgeBackgroundRect.bottom.toInt() if (mDrawableBackgroundClip) { right = left + mBitmapClip!!.width bottom = top + mBitmapClip!!.height canvas.saveLayer(left.toFloat(), top.toFloat(), right.toFloat(), bottom.toFloat(), null, Canvas.ALL_SAVE_FLAG) } badgeBackground!!.setBounds(left, top, right, bottom) badgeBackground!!.draw(canvas) if (mDrawableBackgroundClip) { mBadgeBackgroundPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_IN) canvas.drawBitmap(mBitmapClip!!, left.toFloat(), top.toFloat(), mBadgeBackgroundPaint) canvas.restore() mBadgeBackgroundPaint.xfermode = null if (badgeText!!.isEmpty() || badgeText!!.length == 1) { canvas.drawCircle(mBadgeBackgroundRect.centerX(), mBadgeBackgroundRect.centerY(), mBadgeBackgroundRect.width() / 2f, mBadgeBackgroundBorderPaint) } else { canvas.drawRoundRect(mBadgeBackgroundRect, mBadgeBackgroundRect.height() / 2, mBadgeBackgroundRect.height() / 2, mBadgeBackgroundBorderPaint) } } else { canvas.drawRect(mBadgeBackgroundRect, mBadgeBackgroundBorderPaint) } } private fun createClipLayer() { if (badgeText == null) { return } if (!mDrawableBackgroundClip) { return } if (mBitmapClip != null && !mBitmapClip!!.isRecycled) { mBitmapClip!!.recycle() } val radius = badgeCircleRadius if (badgeText!!.isEmpty() || badgeText!!.length == 1) { mBitmapClip = Bitmap.createBitmap(radius.toInt() * 2, radius.toInt() * 2, Bitmap.Config.ARGB_4444) val srcCanvas = Canvas(mBitmapClip!!) srcCanvas.drawCircle(srcCanvas.width / 2f, srcCanvas.height / 2f, srcCanvas.width / 2f, mBadgeBackgroundPaint) } else { mBitmapClip = Bitmap.createBitmap((mBadgeTextRect.width() + mBadgePadding * 2).toInt(), (mBadgeTextRect.height() + mBadgePadding).toInt(), Bitmap.Config.ARGB_4444) val srcCanvas = Canvas(mBitmapClip!!) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { srcCanvas.drawRoundRect(0f, 0f, srcCanvas.width.toFloat(), srcCanvas.height.toFloat(), srcCanvas.height / 2f, srcCanvas.height / 2f, mBadgeBackgroundPaint) } else { srcCanvas.drawRoundRect(RectF(0f, 0f, srcCanvas.width.toFloat(), srcCanvas.height.toFloat()), srcCanvas.height / 2f, srcCanvas.height / 2f, mBadgeBackgroundPaint) } } } private fun findBadgeCenter() { val rectWidth = if (mBadgeTextRect.height() > mBadgeTextRect.width()) mBadgeTextRect.height() else mBadgeTextRect.width() when (badgeGravity) { Gravity.START or Gravity.TOP -> { mBadgeCenter.x = mGravityOffsetX + mBadgePadding + rectWidth / 2f mBadgeCenter.y = mGravityOffsetY + mBadgePadding + mBadgeTextRect.height() / 2f } Gravity.START or Gravity.BOTTOM -> { mBadgeCenter.x = mGravityOffsetX + mBadgePadding + rectWidth / 2f mBadgeCenter.y = mHeight - (mGravityOffsetY + mBadgePadding + mBadgeTextRect.height() / 2f) } Gravity.END or Gravity.TOP -> { mBadgeCenter.x = mWidth - (mGravityOffsetX + mBadgePadding + rectWidth / 2f) mBadgeCenter.y = mGravityOffsetY + mBadgePadding + mBadgeTextRect.height() / 2f } Gravity.END or Gravity.BOTTOM -> { mBadgeCenter.x = mWidth - (mGravityOffsetX + mBadgePadding + rectWidth / 2f) mBadgeCenter.y = mHeight - (mGravityOffsetY + mBadgePadding + mBadgeTextRect.height() / 2f) } Gravity.CENTER -> { mBadgeCenter.x = mWidth / 2f mBadgeCenter.y = mHeight / 2f } Gravity.CENTER or Gravity.TOP -> { mBadgeCenter.x = mWidth / 2f mBadgeCenter.y = mGravityOffsetY + mBadgePadding + mBadgeTextRect.height() / 2f } Gravity.CENTER or Gravity.BOTTOM -> { mBadgeCenter.x = mWidth / 2f mBadgeCenter.y = mHeight - (mGravityOffsetY + mBadgePadding + mBadgeTextRect.height() / 2f) } Gravity.CENTER or Gravity.START -> { mBadgeCenter.x = mGravityOffsetX + mBadgePadding + rectWidth / 2f mBadgeCenter.y = mHeight / 2f } Gravity.CENTER or Gravity.END -> { mBadgeCenter.x = mWidth - (mGravityOffsetX + mBadgePadding + rectWidth / 2f) mBadgeCenter.y = mHeight / 2f } } initRowBadgeCenter() } private fun measureText() { mBadgeTextRect.left = 0f mBadgeTextRect.top = 0f if (TextUtils.isEmpty(badgeText)) { mBadgeTextRect.right = 0f mBadgeTextRect.bottom = 0f } else { mBadgeTextPaint.textSize = mBadgeTextSize mBadgeTextRect.right = mBadgeTextPaint.measureText(badgeText) mBadgeTextFontMetrics = mBadgeTextPaint.fontMetrics mBadgeTextRect.bottom = mBadgeTextFontMetrics.descent - mBadgeTextFontMetrics.ascent } createClipLayer() } private fun initRowBadgeCenter() { val screenPoint = IntArray(2) getLocationOnScreen(screenPoint) mRowBadgeCenter.x = mBadgeCenter.x + screenPoint[0] mRowBadgeCenter.y = mBadgeCenter.y + screenPoint[1] } protected fun animateHide(center: PointF) { if (badgeText == null) { return } if (mAnimator == null || !mAnimator!!.isRunning) { screenFromWindow(true) mAnimator = BadgeAnimator(createBadgeBitmap(), center, this) mAnimator!!.start() setBadgeNumber(0) } } fun reset() { mDragCenter.x = -1000f mDragCenter.y = -1000f mDragQuadrant = 4 screenFromWindow(false) parent.requestDisallowInterceptTouchEvent(false) invalidate() } override fun hide(animate: Boolean) { if (animate && mActivityRoot != null) { initRowBadgeCenter() animateHide(mRowBadgeCenter) } else { setBadgeNumber(0) } } /** * @param badgeNumber equal to zero badge will be hidden, less than zero show dot */ override fun setBadgeNumber(badgeNumber: Int): Badge { this.badgeNumber = badgeNumber if (this.badgeNumber < 0) { badgeText = "" } else if (this.badgeNumber > 99) { badgeText = if (isExactMode) this.badgeNumber.toString() else "99+" } else if (this.badgeNumber > 0 && this.badgeNumber <= 99) { badgeText = this.badgeNumber.toString() } else if (this.badgeNumber == 0) { badgeText = null } measureText() invalidate() return this } override fun setBadgeText(badgeText: String): Badge { this.badgeText = badgeText badgeNumber = 1 measureText() invalidate() return this } override fun setExactMode(isExact: Boolean): Badge { isExactMode = isExact if (badgeNumber > 99) { setBadgeNumber(badgeNumber) } return this } override fun setShowShadow(showShadow: Boolean): Badge { isShowShadow = showShadow invalidate() return this } override fun setBadgeBackgroundColor(color: Int): Badge { badgeBackgroundColor = color if (badgeBackgroundColor == Color.TRANSPARENT) { mBadgeTextPaint.xfermode = null } else { mBadgeTextPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN) } invalidate() return this } override fun stroke(color: Int, width: Float, isDpValue: Boolean): Badge { mColorBackgroundBorder = color mBackgroundBorderWidth = if (isDpValue) DisplayUtil.dp2px(context, width) else width invalidate() return this } override fun setBadgeBackground(drawable: Drawable): Badge { return setBadgeBackground(drawable, false) } override fun setBadgeBackground(drawable: Drawable, clip: Boolean): Badge { mDrawableBackgroundClip = clip badgeBackground = drawable createClipLayer() invalidate() return this } override fun setBadgeTextColor(color: Int): Badge { badgeTextColor = color invalidate() return this } override fun setBadgeTextSize(size: Float, isSpValue: Boolean): Badge { mBadgeTextSize = if (isSpValue) DisplayUtil.dp2px(context, size) else size measureText() invalidate() return this } override fun getBadgeTextSize(isSpValue: Boolean): Float { return if (isSpValue) DisplayUtil.px2dp(context, mBadgeTextSize) else mBadgeTextSize } override fun setBadgePadding(padding: Float, isDpValue: Boolean): Badge { mBadgePadding = if (isDpValue) DisplayUtil.dp2px(context, padding) else padding createClipLayer() invalidate() return this } override fun getBadgePadding(isDpValue: Boolean): Float { return if (isDpValue) DisplayUtil.px2dp(context, mBadgePadding) else mBadgePadding } /** * @param gravity only support Gravity.START | Gravity.TOP , Gravity.END | Gravity.TOP , * Gravity.START | Gravity.BOTTOM , Gravity.END | Gravity.BOTTOM , * Gravity.CENTER , Gravity.CENTER | Gravity.TOP , Gravity.CENTER | Gravity.BOTTOM , * Gravity.CENTER | Gravity.START , Gravity.CENTER | Gravity.END */ override fun setBadgeGravity(gravity: Int): Badge { if (gravity == Gravity.START or Gravity.TOP || gravity == Gravity.END or Gravity.TOP || gravity == Gravity.START or Gravity.BOTTOM || gravity == Gravity.END or Gravity.BOTTOM || gravity == Gravity.CENTER || gravity == Gravity.CENTER or Gravity.TOP || gravity == Gravity.CENTER or Gravity.BOTTOM || gravity == Gravity.CENTER or Gravity.START || gravity == Gravity.CENTER or Gravity.END) { badgeGravity = gravity invalidate() } else { throw IllegalStateException("only support Gravity.START | Gravity.TOP , Gravity.END | Gravity.TOP , " + "Gravity.START | Gravity.BOTTOM , Gravity.END | Gravity.BOTTOM , Gravity.CENTER" + " , Gravity.CENTER | Gravity.TOP , Gravity.CENTER | Gravity.BOTTOM ," + "Gravity.CENTER | Gravity.START , Gravity.CENTER | Gravity.END") } return this } override fun setGravityOffset(offset: Float, isDpValue: Boolean): Badge { return setGravityOffset(offset, offset, isDpValue) } override fun setGravityOffset(offsetX: Float, offsetY: Float, isDpValue: Boolean): Badge { mGravityOffsetX = if (isDpValue) DisplayUtil.dp2px(context, offsetX) else offsetX mGravityOffsetY = if (isDpValue) DisplayUtil.dp2px(context, offsetY) else offsetY invalidate() return this } override fun getGravityOffsetX(isDpValue: Boolean): Float { return if (isDpValue) DisplayUtil.px2dp(context, mGravityOffsetX) else mGravityOffsetX } override fun getGravityOffsetY(isDpValue: Boolean): Float { return if (isDpValue) DisplayUtil.px2dp(context, mGravityOffsetY) else mGravityOffsetY } private fun updataListener(state: Int) { if (mDragStateChangedListener != null) mDragStateChangedListener!!.onDragStateChanged(state, this, targetView) } override fun setOnDragStateChangedListener(l: Badge.OnDragStateChangedListener): Badge { isDraggable = l != null mDragStateChangedListener = l return this } private inner class BadgeContainer(context: Context) : ViewGroup(context) { override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { for (i in 0 until childCount) { val child = getChildAt(i) child.layout(0, 0, child.measuredWidth, child.measuredHeight) } } override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { var targetView: View? = null var badgeView: View? = null for (i in 0 until childCount) { val child = getChildAt(i) if (child !is BadgeView) { targetView = child } else { badgeView = child } } if (targetView == null) { super.onMeasure(widthMeasureSpec, heightMeasureSpec) } else { targetView.measure(widthMeasureSpec, heightMeasureSpec) badgeView?.measure(View.MeasureSpec.makeMeasureSpec(targetView.measuredWidth, View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(targetView.measuredHeight, View.MeasureSpec.EXACTLY)) setMeasuredDimension(targetView.measuredWidth, targetView.measuredHeight) } } } } 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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,15 @@ package com.kogicodes.sokoni.utils.Badge import android.content.Context object DisplayUtil { fun dp2px(context: Context, dp: Float): Int { val scale = context.resources.displayMetrics.density return (dp * scale + 0.5f).toInt() } fun px2dp(context: Context, pxValue: Float): Int { val scale = context.resources.displayMetrics.density return (pxValue / scale + 0.5f).toInt() } } 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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,56 @@ package com.kogicodes.sokoni.utils.Badge import android.graphics.PointF object MathUtil { val CIRCLE_RADIAN = 2 * Math.PI fun getTanRadian(atan: Double, quadrant: Int): Double { var atan = atan if (atan < 0) { atan += CIRCLE_RADIAN / 4 } atan += CIRCLE_RADIAN / 4 * (quadrant - 1) return atan } fun radianToAngle(radian: Double): Double { return 360 * (radian / CIRCLE_RADIAN) } fun getQuadrant(p: PointF, center: PointF): Int { if (p.x > center.x) { if (p.y > center.y) { return 4 } else if (p.y < center.y) { return 1 } } else if (p.x < center.x) { if (p.y > center.y) { return 3 } else if (p.y < center.y) { return 2 } } return -1 } fun getPointDistance(p1: PointF, p2: PointF): Float { return Math.sqrt(Math.pow((p1.x - p2.x).toDouble(), 2.0) + Math.pow((p1.y - p2.y).toDouble(), 2.0)).toFloat() } fun getInnertangentPoints(circleCenter: PointF, radius: Float, slopeLine: Double?, points: MutableList<PointF>) { val radian: Float val xOffset: Float val yOffset: Float if (slopeLine != null) { radian = Math.atan(slopeLine).toFloat() xOffset = (Math.cos(radian.toDouble()) * radius).toFloat() yOffset = (Math.sin(radian.toDouble()) * radius).toFloat() } else { xOffset = radius yOffset = 0f } points.add(PointF(circleCenter.x + xOffset, circleCenter.y + yOffset)) points.add(PointF(circleCenter.x - xOffset, circleCenter.y - yOffset)) } }