You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android视图可见性切换动画拼接问题排查

解决Android视图可见性切换时的动画拼接问题

嘿,看了你的代码,我发现问题出在嵌套的动画监听逻辑太复杂,多个淡出动画的结束时机不一致,导致淡入动画可能被触发多次,或者时序混乱。咱们来简化这个逻辑,用更清晰的方式实现视图切换的淡入淡出效果。

核心问题分析

你现在的fadeOutViews方法里,每个视图的动画监听都嵌套在一起,当有多个视图需要淡出时,很难保证所有淡出动画都完成后再执行淡入,容易出现动画拼接不上的情况。而且重复的代码也增加了维护难度。

优化方案:用ViewPropertyAnimator的withEndAction统一管理动画时序

我们可以封装通用的淡入、淡出方法,利用withEndAction在所有淡出动画完成后,再统一执行淡入动画,这样逻辑更清晰,也不会出现时序问题。

修改后的完整代码

public class SearchBookTest extends AppCompatActivity {
    private final static int INTRO_VIEW = 0;
    private final static int OFFLINE_VIEW = 1;
    private boolean[] currentView = {true, false};
    private FloatingSearchView mSearchView;
    private TextView mIntroTextViewTop;
    private TextView mIntroTextViewBottom;
    private TextView mNoConnectionTextViewTop;
    private TextView mNoConnectionTextViewBottom;
    private AppCompatButton mNoConnectionButton;
    private int mShortAnimationDuration;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_search_test);
        mSearchView = findViewById(R.id.floating_search_view);
        mIntroTextViewTop = findViewById(R.id.search_book_intro_top);
        mIntroTextViewBottom = findViewById(R.id.search_book_intro_bottom);
        mNoConnectionTextViewTop = findViewById(R.id.search_book_no_connection_top);
        mNoConnectionTextViewBottom = findViewById(R.id.search_book_no_connection_bottom);
        mNoConnectionButton = findViewById(R.id.search_book_no_connection_try_again);
        mShortAnimationDuration = getResources().getInteger(android.R.integer.config_shortAnimTime);
        
        // 初始化视图可见性,确保初始状态正确
        setViewsVisibility(View.GONE, mNoConnectionTextViewTop, mNoConnectionTextViewBottom, mNoConnectionButton);
        setViewsVisibility(View.VISIBLE, mIntroTextViewTop, mIntroTextViewBottom);
        
        setupQueryChangeListener();
    }

    private void setupQueryChangeListener() {
        mSearchView.setOnQueryChangeListener(new FloatingSearchView.OnQueryChangeListener() {
            @Override
            public void onSearchTextChanged(String oldQuery, String newQuery) {
                switch (newQuery.length()) {
                    case 1:
                        showIntroView();
                        break;
                    case 2:
                        showOfflineView();
                        break;
                    default:
                        // 可以根据需求处理其他长度的情况,比如隐藏所有视图
                        break;
                }
            }
        });
    }

    private void showOfflineView() {
        if (currentView[INTRO_VIEW]) {
            // 需要隐藏Intro视图,显示Offline视图
            switchViews(
                    new View[]{mIntroTextViewTop, mIntroTextViewBottom},
                    new View[]{mNoConnectionTextViewTop, mNoConnectionTextViewBottom, mNoConnectionButton}
            );
            currentView[INTRO_VIEW] = false;
            currentView[OFFLINE_VIEW] = true;
        }
    }

    private void showIntroView() {
        if (currentView[OFFLINE_VIEW]) {
            // 需要隐藏Offline视图,显示Intro视图
            switchViews(
                    new View[]{mNoConnectionTextViewTop, mNoConnectionTextViewBottom, mNoConnectionButton},
                    new View[]{mIntroTextViewTop, mIntroTextViewBottom}
            );
            currentView[OFFLINE_VIEW] = false;
            currentView[INTRO_VIEW] = true;
        }
    }

    /**
     * 切换视图:先淡出要隐藏的视图,再淡入要显示的视图
     */
    private void switchViews(View[] viewsToHide, View[] viewsToShow) {
        // 先执行所有要隐藏视图的淡出动画
        fadeOutViews(viewsToHide, () -> {
            // 所有淡出动画完成后,执行淡入动画
            fadeInViews(viewsToShow);
        });
    }

    /**
     * 淡出一组视图,完成后执行回调
     */
    private void fadeOutViews(View[] views, Runnable onFinish) {
        if (views == null || views.length == 0) {
            onFinish.run();
            return;
        }

        int animatingCount = views.length;
        for (View view : views) {
            if (view == null) {
                if (--animatingCount == 0) {
                    onFinish.run();
                }
                continue;
            }
            view.animate()
                    .alpha(0f)
                    .setDuration(mShortAnimationDuration)
                    .withEndAction(() -> {
                        view.setVisibility(View.GONE);
                        if (--animatingCount == 0) {
                            onFinish.run();
                        }
                    });
        }
    }

    /**
     * 淡入一组视图
     */
    private void fadeInViews(View[] views) {
        if (views == null || views.length == 0) return;

        for (View view : views) {
            if (view == null) continue;
            view.setAlpha(0f);
            view.setVisibility(View.VISIBLE);
            view.animate()
                    .alpha(1f)
                    .setDuration(mShortAnimationDuration);
        }
    }

    /**
     * 批量设置视图可见性
     */
    private void setViewsVisibility(int visibility, View... views) {
        for (View view : views) {
            if (view != null) {
                view.setVisibility(visibility);
            }
        }
    }
}

关键改进点

  • 统一管理动画时序:用switchViews方法封装切换逻辑,确保所有淡出动画完成后才执行淡入,避免时序混乱
  • 简化动画监听:使用withEndAction替代嵌套的AnimatorListenerAdapter,代码更简洁易读
  • 批量操作视图:封装setViewsVisibility方法,减少重复代码
  • 初始化可见性:在onCreate里明确设置初始视图的可见性,避免布局默认状态导致的异常

布局注意事项

确保你的布局文件中,初始不需要显示的视图(比如Offline相关视图)默认设置android:visibility="gone",或者在代码初始化时设置,避免初始显示异常。

比如在布局里给Offline视图添加:

android:visibility="gone"

这样修改后,视图切换的动画就会流畅衔接,不会出现拼接问题啦~

内容的提问来源于stack exchange,提问作者Mdp11

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 08:03:49