Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

记一次滑动组件开发 #6

Open
ljunb opened this issue Mar 13, 2019 · 0 comments
Open

记一次滑动组件开发 #6

ljunb opened this issue Mar 13, 2019 · 0 comments

Comments

@ljunb
Copy link
Owner

ljunb commented Mar 13, 2019

背景

在最近的一版需求中,App 的目的地详情页面,接入了中文站旅拍的入口,支持查看该目的地下或是 POI 下的相关旅拍。在旅拍详情页面中,参考中文站的功能交互,头图在滑动切换时需要做高度的渐变动画。

在自定义的滑动组件中,为了有更优效果,iOS、Android 两端分别采用了 ScrollView、ViewPagerAndroid 来实现。实际开发发现,把 Animated.View 作用于 ScrollView 外层,可达到目的。

而 ViewPagerAndroid 则无法使用该方式,由此引出 Animated.createAnimatedComponent 来封装 ViewPagerAndroid。

问题

组件渲染

iOS 端调试完毕后,开始着手 Android 端的实现。基于旅拍详情页的设计稿,该页面的最外层是需要使用
ScrollView 的,所以滑动组件属于 ScrollView 子组件。这里遇到第一个坑,无论是设置 ViewPagerAndroid 或其父组件的宽高,还是 flex 设置为1 ,组件都没有正常渲染出来。在官方 issue 列表中没有发现相关问题。重新检查代码,无意中看到最外层 ScrollView 组件设置了 removeClippedSubviews={true} ,看到这个当然是选择移除他了!果然,组件正常出来了。

实例引用

这个问题是在组件完成之后,想实现循环滑动效果时遇到的。主要是在 Android 端,在使用 Animated.createAnimatedComponent 封装支持动画的 AnimatedViewPager 后,在某些时机需要调用原始组件的实例方法 setPageWithoutAnimation ,但是报错:undefined is not a function ( evaluating 'this.viewPager.setPageWithoutAnimation(index)')

从错误信息来看,代码是可以正常获取到 AnimatedViewPager 的实例 this.viewPager,但是该实例并没有 setPageWithoutAnimation 方法,由此推测获取的实例并不是原始的 ViewPagerAndroid 实例。

以关键字 createAnimatedComponent 在 issue 列表搜了下,有相关的讨论 createAnimatedComponent should use forwardRef?。在底下的回复中,可以看到一个 getNode() 的调用。翻了下官方 createAnimatedComponent.js 的源码:该函数其实是一个高阶函数,原始组件的实例引用被保存在了一个内部变量中,可通过 getNode 调用。修改后代码:

// LiteBannerView.js
export default class LiteBannerView extends React.Component<LiteBannerViewProps, {}> {
    ...
    renderAndroidComponent = () => {
        return (
            <AnimatedViewPager
                ref={r => this.viewPager = r}
                style={{
                    height: this.props.animatedHeight,
                    width: Screen.width
                }}
                initialPage={this.props.loop ? 1 : 0}
                onPageScroll={this.handlePageScroll}
                onPageSelected={this.handlePageSelected}
            >
                {this.state.items.map(this.renderBannerItem)}
            </AnimatedViewPager>
        );
    };

    handlePageSelected = (evt) => {
        if (!this.props.loop) return;

        const { items = [] } = this.props;
        const { position } = evt.nativeEvent;
        // 使用 Animated.createAnimatedComponent 创建新组件后,
        // 使用 getNode 获取原始组件实例,否则无法调用绑定在原始组件上的方法
        const instance = this.viewPager.getNode();
        if (position === 0) {
            instance && instance.setPageWithoutAnimation(items.length);
        } else if (position === items.length + 1) {
            instance && instance.setPageWithoutAnimation(1);
        }
    };
}

遗留问题

以最终测试结果来看,组件的循环滑动功能是正常的,存在的问题是:第一张和最后一张两者切换时,无法做到高度的渐变。

该问题主要是因为组件的 animatedHeight 是由详情页面基于每张图片生成的动画插值,插值的计算是基于原始图片数组的,而滑动组件在设计过程中,原理是在首尾各添加一张图片,再根据滑动距离重新更新 index 。这样就导致首尾两张图片的动画插值无法匹配了。

鉴于组件设置了 loop 参数,考虑移除 animatedHeight,添加 dynamicHeights 的参数来接收所有图片的高度,然后在组件内部根据 loop 生成对应的高度动画插值。待有空验证。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant