Files
assistant-android/app/src/main/java/com/gh/common/notifier/NotifierView.kt
2019-12-25 11:56:06 +08:00

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()
}
}