I assume that you need the TextView to appear as if erased from both ends equally. Here is a technique that will do that:
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()
}
}

The technique is to lock in the size and height of the TextView to its initial size. From that initial size, the animation shrinks the view's width from the initial size to zero. Since the layout will position the start of the text to the start of the view, the text is scrolled left to maintain its position on the screen as the width shrinks.
The red line is there just to mark the center of the TextView and is not needed.
Causing the view to reappear is mostly the opposite of the code here.
The test layout:
<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>
There is another way to accomplish this which is to use a custom TextView that will clip its canvas to make the view shrink and expand. This method has the following advantages:
- It is probably more efficient in that it does not require additional layout of the TextView.
- If the TextView is within a ConstraintLayout and there are other views that are constrained to the start and/or end of the TextView, those views will not shift since the boundaries of the TextView remain unchanged.
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
}
}
We can also bring all the logic into the custom TextView as follows:
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
}
}