如何将 TextView 宽度从 Visible 设置为 Gone 状态?
How to animate TextView width from Visible to Gone state?
我想在更改 TextView
的可见性时设置 TextView
宽度的动画。我不想实现通用的“淡入淡出 in/out”效果,但我想从侧面折叠 TextView
到 0 宽度。
这是我的功能:
fun fadeInTextViewSize(){
val parentWidth = (buttonText.parent as View).measuredWidth
val widthAnimator = ValueAnimator.ofInt(buttonText.width, parentWidth)
widthAnimator.duration = 500
widthAnimator.interpolator = DecelerateInterpolator()
widthAnimator.addUpdateListener { animation ->
buttonText.layoutParams.width = animation.animatedValue as Int
buttonText.requestLayout()
}
widthAnimator.start()
}
fun fadeOutTextViewSize(){
val widthAnimator = ValueAnimator.ofInt(buttonText.width, 0)
widthAnimator.duration = 500
widthAnimator.interpolator = DecelerateInterpolator()
widthAnimator.addUpdateListener { animation ->
buttonText.layoutParams.width = animation.animatedValue as Int
buttonText.requestLayout()
}
widthAnimator.start()
}
问题是使用此功能时,我的 TextView
身高出于某种原因设置为 MATCH_PARENT
。
我假设您需要 TextView 看起来就像从两端均等地擦除一样。这是一种可以做到这一点的技术:
private lateinit var buttonText: TextView
private var viewWidth = 0
fun fadeOutTextViewSize() {
with(buttonText) {
// Enable scrolling for the view since we will need to scroll horizontally to center text.
setMovementMethod(ScrollingMovementMethod())
setHorizontallyScrolling(true)
// Lock in the starting width and height.
viewWidth = buttonText.width
layoutParams.width = buttonText.width
layoutParams.height = buttonText.height
visibility = View.VISIBLE
}
with(ValueAnimator.ofInt(viewWidth, 0)) {
duration = 1000
interpolator = DecelerateInterpolator()
addUpdateListener { animation ->
val newWidth = animation.animatedValue as Int
buttonText.layoutParams.width = newWidth
// Shift text left so it stays centered in the initial bounds.
val scrollX = (viewWidth - newWidth) / 2
buttonText.scrollTo(scrollX, 0)
buttonText.requestLayout()
}
doOnEnd {
// Make the view invisible to seal its disappeared status. This could also be
// "GONE" depending on the desired effect.
buttonText.visibility = View.INVISIBLE
}
start()
}
}
技术是将 TextView 的大小和高度锁定为其初始大小。从该初始大小开始,动画将视图的宽度从初始大小缩小为零。由于布局会将文本的开头定位到视图的开头,因此文本会向左滚动以在宽度缩小时保持其在屏幕上的位置。
红线只是用来标记 TextView 的中心,不需要。
使视图重新出现与此处的代码主要相反。
测试布局:
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/holo_blue_dark"
tools:context=".MainActivity">
<TextView
android:id="@+id/buttonText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@android:color/holo_blue_light"
android:text="Hello World!"
android:textSize="48sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:layout_width="1dp"
android:layout_height="0dp"
android:background="@android:color/holo_red_light"
app:layout_constraintBottom_toBottomOf="@id/buttonText"
app:layout_constraintEnd_toEndOf="@id/buttonText"
app:layout_constraintStart_toStartOf="@id/buttonText"
app:layout_constraintTop_toTopOf="@id/buttonText" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Click Here"
app:layout_constraintEnd_toEndOf="@+id/buttonText"
app:layout_constraintStart_toStartOf="@id/buttonText"
app:layout_constraintTop_toBottomOf="@id/buttonText" />
</androidx.constraintlayout.widget.ConstraintLayout>
还有另一种方法可以实现此目的,即使用自定义 TextView 剪辑其 canvas 以使视图缩小和展开。这种方法有以下优点:
- 它可能更有效,因为它不需要 TextView 的额外布局。
- 如果 TextView 在 ConstraintLayout 内,并且有其他视图被限制在 and/or 的开始 and/or 结束TextView,这些视图不会移动,因为 TextView 的边界保持不变。
private lateinit var buttonText: ClippedTextView
fun fadeOutTextViewSize() {
with(ValueAnimator.ofInt(buttonText.width, 0)) {
duration = 1000
interpolator = DecelerateInterpolator()
addUpdateListener { animation ->
val newWidth = animation.animatedValue as Int
buttonText.setClippedWidth(newWidth)
buttonText.invalidate()
}
start()
}
}
ClippedTextView.kt
class ClippedTextView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : androidx.appcompat.widget.AppCompatTextView(context, attrs) {
private var mClipWidth = 0
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
mClipWidth = right - left
}
override fun draw(canvas: Canvas) {
val sideClipWidth = (width - mClipWidth) / 2
canvas.withClip(sideClipWidth, 0, width - sideClipWidth, height) {
super.draw(this)
}
}
fun setClippedWidth(clipWidth: Int) {
mClipWidth = clipWidth
}
}
我们也可以把所有的逻辑都放到自定义的TextView中,如下:
ClippedTextView.kt
class ClippedTextView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : androidx.appcompat.widget.AppCompatTextView(context, attrs) {
private var mClipWidth = 0
private val mAnimator: ValueAnimator by lazy {
ValueAnimator().apply {
duration = 1000
interpolator = DecelerateInterpolator()
addUpdateListener { animation ->
setClippedWidth(animation.animatedValue as Int)
invalidate()
}
}
}
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
setClippedWidth(right - left)
}
override fun draw(canvas: Canvas) {
val sideClipWidth = (width - mClipWidth) / 2
canvas.withClip(sideClipWidth, 0, width - sideClipWidth, height) {
super.draw(this)
}
}
fun expandView() {
doWidthAnimation(mClipWidth, 0)
}
fun shrinkView() {
doWidthAnimation(mClipWidth, width)
}
private fun doWidthAnimation(startWidth: Int, endWidth: Int) {
animation?.cancel()
with(mAnimator) {
setIntValues(startWidth, endWidth)
start()
}
}
private fun setClippedWidth(clipWidth: Int) {
mClipWidth = clipWidth
}
}
我想在更改 TextView
的可见性时设置 TextView
宽度的动画。我不想实现通用的“淡入淡出 in/out”效果,但我想从侧面折叠 TextView
到 0 宽度。
这是我的功能:
fun fadeInTextViewSize(){
val parentWidth = (buttonText.parent as View).measuredWidth
val widthAnimator = ValueAnimator.ofInt(buttonText.width, parentWidth)
widthAnimator.duration = 500
widthAnimator.interpolator = DecelerateInterpolator()
widthAnimator.addUpdateListener { animation ->
buttonText.layoutParams.width = animation.animatedValue as Int
buttonText.requestLayout()
}
widthAnimator.start()
}
fun fadeOutTextViewSize(){
val widthAnimator = ValueAnimator.ofInt(buttonText.width, 0)
widthAnimator.duration = 500
widthAnimator.interpolator = DecelerateInterpolator()
widthAnimator.addUpdateListener { animation ->
buttonText.layoutParams.width = animation.animatedValue as Int
buttonText.requestLayout()
}
widthAnimator.start()
}
问题是使用此功能时,我的 TextView
身高出于某种原因设置为 MATCH_PARENT
。
我假设您需要 TextView 看起来就像从两端均等地擦除一样。这是一种可以做到这一点的技术:
private lateinit var buttonText: TextView
private var viewWidth = 0
fun fadeOutTextViewSize() {
with(buttonText) {
// Enable scrolling for the view since we will need to scroll horizontally to center text.
setMovementMethod(ScrollingMovementMethod())
setHorizontallyScrolling(true)
// Lock in the starting width and height.
viewWidth = buttonText.width
layoutParams.width = buttonText.width
layoutParams.height = buttonText.height
visibility = View.VISIBLE
}
with(ValueAnimator.ofInt(viewWidth, 0)) {
duration = 1000
interpolator = DecelerateInterpolator()
addUpdateListener { animation ->
val newWidth = animation.animatedValue as Int
buttonText.layoutParams.width = newWidth
// Shift text left so it stays centered in the initial bounds.
val scrollX = (viewWidth - newWidth) / 2
buttonText.scrollTo(scrollX, 0)
buttonText.requestLayout()
}
doOnEnd {
// Make the view invisible to seal its disappeared status. This could also be
// "GONE" depending on the desired effect.
buttonText.visibility = View.INVISIBLE
}
start()
}
}
技术是将 TextView 的大小和高度锁定为其初始大小。从该初始大小开始,动画将视图的宽度从初始大小缩小为零。由于布局会将文本的开头定位到视图的开头,因此文本会向左滚动以在宽度缩小时保持其在屏幕上的位置。
红线只是用来标记 TextView 的中心,不需要。
使视图重新出现与此处的代码主要相反。
测试布局:
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/holo_blue_dark"
tools:context=".MainActivity">
<TextView
android:id="@+id/buttonText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@android:color/holo_blue_light"
android:text="Hello World!"
android:textSize="48sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:layout_width="1dp"
android:layout_height="0dp"
android:background="@android:color/holo_red_light"
app:layout_constraintBottom_toBottomOf="@id/buttonText"
app:layout_constraintEnd_toEndOf="@id/buttonText"
app:layout_constraintStart_toStartOf="@id/buttonText"
app:layout_constraintTop_toTopOf="@id/buttonText" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Click Here"
app:layout_constraintEnd_toEndOf="@+id/buttonText"
app:layout_constraintStart_toStartOf="@id/buttonText"
app:layout_constraintTop_toBottomOf="@id/buttonText" />
</androidx.constraintlayout.widget.ConstraintLayout>
还有另一种方法可以实现此目的,即使用自定义 TextView 剪辑其 canvas 以使视图缩小和展开。这种方法有以下优点:
- 它可能更有效,因为它不需要 TextView 的额外布局。
- 如果 TextView 在 ConstraintLayout 内,并且有其他视图被限制在 and/or 的开始 and/or 结束TextView,这些视图不会移动,因为 TextView 的边界保持不变。
private lateinit var buttonText: ClippedTextView
fun fadeOutTextViewSize() {
with(ValueAnimator.ofInt(buttonText.width, 0)) {
duration = 1000
interpolator = DecelerateInterpolator()
addUpdateListener { animation ->
val newWidth = animation.animatedValue as Int
buttonText.setClippedWidth(newWidth)
buttonText.invalidate()
}
start()
}
}
ClippedTextView.kt
class ClippedTextView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : androidx.appcompat.widget.AppCompatTextView(context, attrs) {
private var mClipWidth = 0
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
mClipWidth = right - left
}
override fun draw(canvas: Canvas) {
val sideClipWidth = (width - mClipWidth) / 2
canvas.withClip(sideClipWidth, 0, width - sideClipWidth, height) {
super.draw(this)
}
}
fun setClippedWidth(clipWidth: Int) {
mClipWidth = clipWidth
}
}
我们也可以把所有的逻辑都放到自定义的TextView中,如下:
ClippedTextView.kt
class ClippedTextView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : androidx.appcompat.widget.AppCompatTextView(context, attrs) {
private var mClipWidth = 0
private val mAnimator: ValueAnimator by lazy {
ValueAnimator().apply {
duration = 1000
interpolator = DecelerateInterpolator()
addUpdateListener { animation ->
setClippedWidth(animation.animatedValue as Int)
invalidate()
}
}
}
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
setClippedWidth(right - left)
}
override fun draw(canvas: Canvas) {
val sideClipWidth = (width - mClipWidth) / 2
canvas.withClip(sideClipWidth, 0, width - sideClipWidth, height) {
super.draw(this)
}
}
fun expandView() {
doWidthAnimation(mClipWidth, 0)
}
fun shrinkView() {
doWidthAnimation(mClipWidth, width)
}
private fun doWidthAnimation(startWidth: Int, endWidth: Int) {
animation?.cancel()
with(mAnimator) {
setIntValues(startWidth, endWidth)
start()
}
}
private fun setClippedWidth(clipWidth: Int) {
mClipWidth = clipWidth
}
}