302 lines
9.5 KiB
Kotlin
302 lines
9.5 KiB
Kotlin
package com.gh.common.notifier
|
|
|
|
import android.animation.*
|
|
import android.content.Context
|
|
import android.graphics.Path
|
|
import android.text.TextUtils
|
|
import android.util.AttributeSet
|
|
import android.util.Log
|
|
import android.util.TypedValue
|
|
import android.view.Gravity
|
|
import android.view.MotionEvent
|
|
import android.view.View
|
|
import android.view.ViewGroup
|
|
import android.widget.FrameLayout
|
|
import androidx.core.view.ViewCompat
|
|
import com.gh.common.util.DisplayUtils
|
|
import com.gh.common.util.ImageUtils
|
|
import com.gh.common.util.doOnEnd
|
|
import com.gh.common.util.doOnStart
|
|
import com.gh.gamecenter.R
|
|
import kotlinx.android.synthetic.main.view_notifier.view.*
|
|
|
|
class NotifierView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0)
|
|
: FrameLayout(context, attrs, defStyle) {
|
|
|
|
companion object {
|
|
const val SCALE_MINI = 0.2F
|
|
const val SCALE_DEFAULT = 1F
|
|
|
|
const val DEFAULT_DURATION = 500L
|
|
}
|
|
|
|
var onShowListener: OnShowNotificationListener? = null
|
|
var onHideListener: OnHideNotificationListener? = null
|
|
|
|
lateinit var expandAnimator: ValueAnimator
|
|
lateinit var shrinkAnimator: ValueAnimator
|
|
|
|
lateinit var translateUpAnimator: ObjectAnimator
|
|
lateinit var translateDownAnimator: ObjectAnimator
|
|
|
|
lateinit var translateToLeftAnimator: ObjectAnimator
|
|
lateinit var translateToRightAnimator: ObjectAnimator
|
|
|
|
lateinit var zoomInAnimator: ObjectAnimator
|
|
lateinit var zoomOutAnimator: ObjectAnimator
|
|
|
|
var showAnimatorSet: AnimatorSet
|
|
var hideAnimatorSet: AnimatorSet
|
|
|
|
var rightToLeftPath: Path
|
|
var leftToRightPath: Path
|
|
|
|
var veryRight: Float = 0F
|
|
var veryBottom: Float = 0F
|
|
var centerX: Float = 0F
|
|
|
|
var navigationHeight = 0
|
|
|
|
var textWidth: Int = 0
|
|
var cardViewWidth: Int = 0
|
|
var verticalAnimationOffset: Int = 0
|
|
|
|
var duration = DEFAULT_DURATION
|
|
|
|
var showVerticalTranslateAnimation: Boolean = true
|
|
|
|
init {
|
|
inflate(context, R.layout.view_notifier, this)
|
|
|
|
ViewCompat.setTranslationZ(this, Integer.MAX_VALUE.toFloat())
|
|
|
|
cardView.scaleX = SCALE_MINI
|
|
cardView.scaleY = SCALE_MINI
|
|
|
|
verticalAnimationOffset = dp2px(100F)
|
|
|
|
navigationHeight = DisplayUtils.retrieveNavigationHeight(context)
|
|
|
|
rightToLeftPath = Path()
|
|
leftToRightPath = Path()
|
|
|
|
showAnimatorSet = AnimatorSet()
|
|
hideAnimatorSet = AnimatorSet()
|
|
}
|
|
|
|
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
|
|
super.onLayout(changed, left, top, right, bottom)
|
|
|
|
centerX = (left + right - cardViewWidth) / 2F
|
|
|
|
// TODO Provide method to change these absolute offset.
|
|
val r = right - dp2px(72F).toFloat()
|
|
val b = bottom - dp2px(145F).toFloat() - navigationHeight
|
|
|
|
// Only set
|
|
if (veryRight != r || veryBottom != b) {
|
|
veryRight = r
|
|
veryBottom = b
|
|
|
|
rightToLeftPath.moveTo(r, b)
|
|
rightToLeftPath.lineTo(centerX, b)
|
|
|
|
leftToRightPath.moveTo(centerX, b)
|
|
leftToRightPath.lineTo(r, b)
|
|
|
|
initAnimator()
|
|
}
|
|
}
|
|
|
|
private fun initAnimator() {
|
|
expandAnimator = ValueAnimator.ofFloat(0F, 1F)
|
|
expandAnimator.duration = DEFAULT_DURATION
|
|
expandAnimator.addUpdateListener { a ->
|
|
val progress = a?.animatedValue as Float
|
|
tvText.width = (textWidth * progress).toInt()
|
|
}
|
|
expandAnimator.doOnEnd {
|
|
enableSwipeToDismiss()
|
|
shrinkAfterDelay()
|
|
}
|
|
|
|
shrinkAnimator = ValueAnimator.ofFloat(1F, 0F)
|
|
shrinkAnimator.duration = DEFAULT_DURATION
|
|
shrinkAnimator.addUpdateListener { a ->
|
|
val progress = a?.animatedValue as Float
|
|
tvText.width = (textWidth * progress).toInt()
|
|
}
|
|
shrinkAnimator.doOnEnd {
|
|
val lp = FrameLayout.LayoutParams(cardView.layoutParams)
|
|
lp.gravity = Gravity.NO_GRAVITY
|
|
cardView.layoutParams = lp
|
|
|
|
disableSwipeToDismiss()
|
|
}
|
|
|
|
translateToLeftAnimator = ObjectAnimator.ofFloat(cardView, "translationX", veryRight, centerX)
|
|
translateToLeftAnimator.duration = DEFAULT_DURATION
|
|
translateToLeftAnimator.doOnEnd {
|
|
onShowListener?.onShow()
|
|
|
|
val lp = FrameLayout.LayoutParams(cardView.layoutParams)
|
|
lp.gravity = Gravity.CENTER_HORIZONTAL
|
|
cardView.layoutParams = lp
|
|
cardView.translationX = 0f
|
|
|
|
expandAnimator.start()
|
|
}
|
|
|
|
translateToRightAnimator = ObjectAnimator.ofFloat(cardView, "translationX", centerX, veryRight)
|
|
translateToRightAnimator.duration = DEFAULT_DURATION
|
|
|
|
translateUpAnimator = ObjectAnimator.ofFloat(cardView, "translationY", veryBottom + verticalAnimationOffset, veryBottom)
|
|
translateUpAnimator.duration = DEFAULT_DURATION
|
|
translateUpAnimator.doOnStart { cardView.translationX = veryRight }
|
|
|
|
translateDownAnimator = ObjectAnimator.ofFloat(cardView, "translationY", veryBottom, veryBottom + verticalAnimationOffset)
|
|
translateDownAnimator.duration = DEFAULT_DURATION
|
|
|
|
zoomInAnimator = ObjectAnimator.ofPropertyValuesHolder(cardView, PropertyValuesHolder.ofFloat("scaleX", SCALE_DEFAULT),
|
|
PropertyValuesHolder.ofFloat("scaleY", SCALE_DEFAULT))
|
|
zoomInAnimator.duration = DEFAULT_DURATION
|
|
zoomInAnimator.doOnStart { cardView.translationX = veryRight }
|
|
zoomInAnimator.doOnStart { cardView.translationY = veryBottom }
|
|
|
|
zoomOutAnimator = ObjectAnimator.ofPropertyValuesHolder(cardView, PropertyValuesHolder.ofFloat("scaleX", SCALE_MINI),
|
|
PropertyValuesHolder.ofFloat("scaleY", SCALE_MINI))
|
|
zoomOutAnimator.duration = DEFAULT_DURATION
|
|
zoomOutAnimator.doOnEnd { removeFromParent() }
|
|
|
|
if (showVerticalTranslateAnimation) {
|
|
showAnimatorSet.play(translateUpAnimator).with(zoomInAnimator).before(translateToLeftAnimator)
|
|
} else {
|
|
showAnimatorSet.play(zoomInAnimator).before(translateToLeftAnimator)
|
|
}
|
|
showAnimatorSet.start()
|
|
}
|
|
|
|
private fun enableSwipeToDismiss() {
|
|
cardView?.setOnTouchListener(SwipeDismissTouchListener(cardView, object : SwipeDismissTouchListener.DismissCallbacks {
|
|
override fun canDismiss(): Boolean {
|
|
return true
|
|
}
|
|
|
|
override fun onDismiss(view: View) {
|
|
removeFromParent()
|
|
}
|
|
|
|
override fun onTouch(view: View, touch: Boolean) {
|
|
// Ignore.
|
|
}
|
|
}))
|
|
}
|
|
|
|
private fun disableSwipeToDismiss() {
|
|
cardView?.setOnTouchListener(null)
|
|
}
|
|
|
|
override fun onDetachedFromWindow() {
|
|
super.onDetachedFromWindow()
|
|
|
|
showAnimatorSet.cancel()
|
|
hideAnimatorSet.cancel()
|
|
|
|
try {
|
|
removeAllListeners(expandAnimator,
|
|
shrinkAnimator,
|
|
translateUpAnimator,
|
|
translateDownAnimator,
|
|
translateToLeftAnimator,
|
|
translateToRightAnimator)
|
|
} catch (e: Exception) {
|
|
e.printStackTrace()
|
|
}
|
|
}
|
|
|
|
private fun removeAllListeners(vararg ts: Animator) {
|
|
for (a in ts) {
|
|
a.removeAllListeners()
|
|
if (a is ValueAnimator) {
|
|
a.removeAllUpdateListeners()
|
|
}
|
|
}
|
|
}
|
|
|
|
override fun onTouchEvent(event: MotionEvent): Boolean {
|
|
performClick()
|
|
return super.onTouchEvent(event)
|
|
}
|
|
|
|
private fun shrinkAfterDelay() {
|
|
postDelayed({ shrink() }, duration)
|
|
}
|
|
|
|
private fun shrink() {
|
|
shrinkAnimator.doOnEnd { hide() }
|
|
shrinkAnimator.start()
|
|
}
|
|
|
|
private fun removeFromParent() {
|
|
clearAnimation()
|
|
visibility = View.GONE
|
|
|
|
postDelayed(object : Runnable {
|
|
override fun run() {
|
|
try {
|
|
if (parent == null) {
|
|
Log.e(javaClass.simpleName, "getParent() returning Null")
|
|
} else {
|
|
try {
|
|
(parent as ViewGroup).removeView(this@NotifierView)
|
|
|
|
onHideListener?.onHide()
|
|
} catch (ex: Exception) {
|
|
Log.e(javaClass.simpleName, "Cannot remove from parent layout")
|
|
}
|
|
}
|
|
} catch (ex: Exception) {
|
|
Log.e(javaClass.simpleName, Log.getStackTraceString(ex))
|
|
}
|
|
}
|
|
}, 100)
|
|
}
|
|
|
|
fun setText(text: String?) {
|
|
if (!TextUtils.isEmpty(text)) {
|
|
tvText.text = text
|
|
tvText.measure(0, 0)
|
|
textWidth = tvText.measuredWidth
|
|
tvText.width = 0
|
|
|
|
cardView.measure(0, 0)
|
|
cardViewWidth = cardView.measuredWidth
|
|
}
|
|
}
|
|
|
|
fun hide() {
|
|
if (showVerticalTranslateAnimation) {
|
|
hideAnimatorSet.play(translateDownAnimator).with(zoomOutAnimator).after(translateToRightAnimator)
|
|
} else {
|
|
hideAnimatorSet.play(zoomOutAnimator).after(translateToRightAnimator)
|
|
}
|
|
hideAnimatorSet.start()
|
|
}
|
|
|
|
fun setIcon(url: String) {
|
|
ImageUtils.display(ivIcon, url)
|
|
}
|
|
|
|
private fun dp2px(dp: Float): Int {
|
|
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, context.applicationContext.resources.displayMetrics).toInt()
|
|
}
|
|
|
|
interface OnShowNotificationListener {
|
|
fun onShow()
|
|
}
|
|
|
|
interface OnHideNotificationListener {
|
|
fun onHide()
|
|
}
|
|
|
|
} |