自定义TextView实现多个文案切换炫酷动画

当显示2个或2个以上文案时,每隔2秒切换气泡文案
4C3FBC04FF667DAAEF17AA6B8F8F7A46

核心实现代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115

import android.animation.ValueAnimator
import android.content.Context
import android.util.AttributeSet
import android.util.Log
import androidx.appcompat.widget.AppCompatTextView

class TextViewSwitcher @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : AppCompatTextView(context, attrs, defStyleAttr) {

private var strs: List<String>? = null
private var startPos: Int = 0
private val timeStep = 2000L // 2S
private val TAG = "TextViewSwitcher"

private val showNext = Runnable {
showNextStr()
}

override fun onAttachedToWindow() {
Log.d(TAG, "onAttachedToWindow: ")
super.onAttachedToWindow()
val size = strs?.size ?: 0
if (size > 1) {
handler.postDelayed(showNext, timeStep)
}
}

override fun onDetachedFromWindow() {
Log.d(TAG, "onDetachedFromWindow: ")
handler.removeCallbacks(showNext)
super.onDetachedFromWindow()
}

fun setTextList(strs: List<String>?, startPos: Int = 0) {
Log.d(TAG, "setTextList: ")
if (strs.isNullOrEmpty()) return
this.strs = strs
this.startPos = startPos
if (strs.size == 1) {
text = strs[0]
} else {
this.startPos = startPos % strs.size
text = strs[this.startPos]
}
}

private fun showNextStr() {
var startPos = this.startPos + 1
val size = strs?.size ?: 0
if (size <= 1) return
if (startPos >= size) startPos %= size

this.startPos = startPos
changeTextWithAnimator(this, strs?.get(startPos))

handler.postDelayed(showNext, timeStep)
}

private fun changeTextWithAnimator(
textView: AppCompatTextView?,
nextContent: String?
) {
if (textView != null) {
val animator = ValueAnimator.ofFloat(0f, 2f)
animator.duration = 400
var changed = false
textView.pivotX = 0f
val height = textView.measuredHeight
if (height > 0) {
textView.pivotY = height.toFloat()
}

val startWidth = textView.measuredWidth
var endWidth = 0
val params = textView.layoutParams
animator.addUpdateListener { animation ->
val value = animation.animatedValue as Float
when {
value < 1f -> {
textView.rotation = 360 - value * 60
textView.alpha = 1 - value
}
value > 1f -> {
textView.alpha = value - 1
textView.rotation = 360 - (2 - value) * 60
if (!changed) {
changed = true
textView.text = nextContent

val measureSpec = MeasureSpec.makeMeasureSpec(
0,
MeasureSpec.UNSPECIFIED
)
textView.measure(measureSpec, measureSpec)
endWidth = textView.measuredWidth

Log.d(TAG, "changeTextWithAnimator: endWidth=$endWidth")
} else {
if (endWidth > 0) {
params.width =
(startWidth + (endWidth - startWidth) * (value - 1)).toInt()
textView.layoutParams = params
}
}
}
}
}
animator.start()
}
}
}

由于动画要在顶部浮层,这样动画才能不被父类容器的大小所限制和切割,所以,直接在PopWindow中显示。
具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.PopupWindow

object HomePromptView {
@JvmStatic
fun showTipPopView(view: View, typedStr: String?): PopupWindow {

val rootView = LayoutInflater.from(view.context).inflate(R.layout.home_prompt_layout, null)
val promptTv = rootView.findViewById<TextViewSwitcher>(R.id.tv_prompt)
promptTv.setTextList(typedStr?.split("|"))
rootView.isClickable = false

// PopWindow
val popTipWid = PopupWindow(
rootView,
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
popTipWid.isTouchable = false

// android.view.WindowManager$BadTokenException:
// Unable to add window -- token null is not valid;
// is your activity running?
try {
popTipWid.showAtLocation(view.rootView, 0, 0, 0)
layoutPromptLocation(promptTv, view)
// popTipWid.showAsDropDown(view, UiUtils.dip2px(39), -UiUtils.dip2px(48))
view.addOnLayoutChangeListener { view, i, i2, i3, i4, i5, i6, i7, i8 ->
layoutPromptLocation(promptTv, view)
}
} catch (e: Exception) {
e.printStackTrace()
}
return popTipWid
}


private fun layoutPromptLocation(
promptTv: TextViewSwitcher,
view: View
) {
try {
val params = promptTv.layoutParams as ViewGroup.MarginLayoutParams
val location = IntArray(2)
view.getLocationInWindow(location)
params.topMargin =
location[1] - UiUtils.getStatusBarHeight(view.context) + UiUtils.dip2px(4)
params.leftMargin = location[0] + UiUtils.dip2px(25)
promptTv.layoutParams = params
} catch (e: Exception) {
e.printStackTrace()
}
}

@JvmStatic
fun dismissTipPopView(popTipWid: PopupWindow?) {
popTipWid?.dismiss()
}

}

添加布局代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/transparent">

<******.TextViewSwitcher
android:id="@+id/tv_prompt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/common_ui_shape_red_heavy_prompt"
android:maxLines="1"
android:paddingStart="5dp"
android:paddingTop="1.5dp"
android:paddingEnd="5dp"
android:paddingBottom="1.5dp"
android:textColor="@color/white"
android:textSize="10sp"
tools:text="硬核安利">

</******.TextViewSwitcher>

</FrameLayout>

drawable:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners
android:bottomRightRadius="9dp"
android:topLeftRadius="9dp"
android:topRightRadius="9dp" />
<solid android:color="#FF5E79" />
<stroke
android:width="0.5dp"
android:color="@color/white" />
</shape>

作者

Dench

发布于

2022-03-09

更新于

2022-03-09

许可协议

CC BY-NC-SA 4.0

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×