滚动视图内的粘性组件

时间:2016-07-26 05:20:39

标签: react-native

我试图像这个应用程序一样构建一个粘性组件

http://www.screencapture.ru/file/E88F08Fc

交易,产品,事件选项卡/ segmentControl实际上是从底部开始,当您点击标题的底部时滚动它停止并在内容保持滚动时开始粘贴

这是我的代码

        <View style={styles.container}>
            <ScrollView 
                style={styles.container}
                scrollEventThrottle={16}
                onScroll={
                    Animated.event(
                        [{nativeEvent:{contentOffset: {y: this.state.scrollY}}}]
                    )
                }
            >
                {this._renderScrollViewContent()}
            </ScrollView>
            <View style={styles.stickyStuff}></View>
            <Animated.View
                style={[styles.header, {height: headerHeight}]}
            >
                <Animated.Image
                    source={require('../../assets/images/pvj.jpg')}
                    style={[styles.backgroundImage, {opacity: imageOpacity}]}
                />
                <Animated.View style={[styles.bar, {transform: [{translateY: titleTranslateY}, {translateX: titleTranslateX}]}]}>
                    <Text style={styles.title}>PARIS VAN JAVA</Text>
                </Animated.View>

            </Animated.View>
        </View>

6 个答案:

答案 0 :(得分:35)

使用ScrollView组件非常简单。已经有一些名为&#34; stickyHeaderIndices&#34;它使得孩子的索引变得粘滞。在下面的代码中,当你滚动时,renderComponent2里面的内容会保持粘滞状态。

<ScrollView
      stickyHeaderIndices={[1]}
      showsVerticalScrollIndicator={false}
 >
  {this.renderComponent1()}
  {this.renderComponent2()}
  {this.renderComponent3()}
 </ScrollView>

参考:https://facebook.github.io/react-native/docs/scrollview.html#stickyheaderindices

答案 1 :(得分:9)

嘿伙计们感谢你回答我的第一个问题(是的!),我发现它现在是怎么做的...所以基本上我做的是我欺骗F8App代码,如果它不是F8Scrolling原生模块可以使用

if (!NativeModules.F8Scrolling) {
  var distance = EMPTY_CELL_HEIGHT - this.state.stickyHeaderHeight;
  var translateY = 0; this.state.anim.interpolate({
    inputRange: [0, distance],
    outputRange: [distance, 0],
    extrapolateRight: 'clamp',
  });
  transform = [{translateY}];
}

给出了动画我的粘性视图的想法,所以这里是我最终

const stickySegmentControlX = this.state.scrollY.interpolate({
    inputRange: [0, STICKY_SCROLL_DISTANCE],
    outputRange: [INIT_STICKY_HEADER, HEADER_MIN_HEIGHT],
    extrapolate: 'clamp'
})

...

    return(
    ....
     <Animated.View ref="stickyHeader" style={[styles.stickyStuff, {top: stickySegmentControlX}]}>
       <Text>Go Stick</Text>
     </Animated.View>
    ....
   );

所以基本上我所做的是动画{position:'absolute'}视图的顶部位置,而输出范围的值在底部位置到我的标题高度之间(所以它会停止在我的标题下方,输入范围介于0和顶部位置与标题高度之间的差异...最终动画在滚动滚动视图时会感觉自然......

你去了一个粘性标题自定义视图......这是结果

enter image description here

如果你觉得我的答案令人困惑,那你最好去janic duplessis中文帖子:
React Native ScrollView animated header

答案 2 :(得分:3)

这很简单,您只需要知道在滚动视图组件内滚动时希望它粘住的索引组件。下面是一个例子:

 <ScrollView
      style={styles.screen}
      stickyHeaderIndices={[0]}
 >
     <View><Text>Hello1</Text></View>
     <View><Text>Hello2</Text></View>
     <View><Text>Hello3</Text></View>
 </ScrollView>

所以当滚动Hello1文本会粘在ScrollView的顶部

祝你好运

答案 3 :(得分:0)

您可以将所有内容放在ScrollView中,将粘性标题放在那里,然后在主视图中使用第二个ScrollView来获取其余内容。像这样:

<View style={styles.container}>
    <ScrollView style={styles.mainScrollView}>
        <View style={styles.header}>
            // Header content here
        </View>

        <ScrollView style={styles.scrollableContent}>
            // Scrollable content here
        </ScrollView>
     </ScrollView>
</View>

为第二个height设置ScrollView,使其仅占用可用空间的一部分,这样标题将在顶部保持可见。

答案 4 :(得分:0)

我找到了这个视差滚动视图模块。它做了那种行为。

react-native-parallax-scroll-view

答案 5 :(得分:0)

这可以证明是有用的

import android.os.Build
import android.util.Log
import android.view.View
import android.view.ViewGroup
import android.widget.ScrollView
import androidx.annotation.RequiresApi
import kotlinx.coroutines.handleCoroutineException

@RequiresApi(Build.VERSION_CODES.M)
class StickyViewHistory(val stickyViewInflateListener: StickyViewInflateListener, val scrollView: ScrollView) {


    interface StickyViewInflateListener {
        fun inflate(view : View)
        fun getHeaderViews() : List<View>
        fun removeSticky()
        fun getStickyHolderView() : View
        fun getStickyLayout() : View
    }

    var currentVisibleHeader : ViewOverLapState? = null
    private var isStickyVisible = false

    init {
        scrollView.setOnScrollChangeListener { view: View, i: Int, newScrollY: Int, i2: Int, oldScrollY: Int ->
             val overlapStates = getOverlappStates()
             val numberOfHeadersAboveScroll = overlapStates.filter { it == ViewOverLapState.IS_ABOVE }.count()
             //If all of the headers are either visible or below the screen, we remove the sticky
             if(numberOfHeadersAboveScroll == 0){
                 if(isStickyVisible == true){
                   removeSticky()
                 }
             }
             else {
                 if(newScrollY < oldScrollY){
                     displayHeaderWhenScrollingDown(overlapStates)
                 }
                 else {
                     displayHeaderWhileScrollingUp(overlapStates)
                 }

             }

        }
    }

    /**
     * Assumes there is atleast one header view which has scrolled above screen
     */
    fun displayHeaderWhileScrollingUp(overlapStates : List<ViewOverLapState>){
        val mostRecentAboveHeader = getMostRecentAboveHeader(overlapStates)
        val highestVisibleHeader = getHighestVisibleHeader(overlapStates)

        //If there is a header on screen which is just about to overlap the current sticky layout we remove the Sticky altogether
        highestVisibleHeader?.let {
            if(isOverlappingCurrentStickyLayout(it.view)){
                removeSticky()
                return
            }
        }


        // If the most recent header that has scrolled above screen is the current sticky,
        // we dont do anything since its already being displayed as sticky
        if(currentVisibleHeader != null && currentVisibleHeader!!.view == mostRecentAboveHeader.view){
            //Do nothing
        }
        else {
            //We sticky the most recent header which has scrolled above screen
            setSticky(mostRecentAboveHeader)
        }

        isStickyVisible = true
    }


    fun displayHeaderWhenScrollingDown(overlapStates : List<ViewOverLapState>){
        currentVisibleHeader?.let { cvh ->
            val highestVisibleHeader = getHighestVisibleHeader(overlapStates)
            highestVisibleHeader?.let { hvh ->
                if(isOverlappingCurrentStickyLayout(hvh.view)){
                    removeSticky()
                    return
                }
            }
        }

        if(currentVisibleHeader == null){
            val mostRecentAboveHeader = getMostRecentAboveHeader(overlapStates)
            setSticky(mostRecentAboveHeader)
        }
    }


    /**
     * Checks if a view is overlapping with any part of the current sticky layout
     */
    fun isOverlappingCurrentStickyLayout(childView: View) : Boolean{
        val locationOfSticky = IntArray(2)
        stickyViewInflateListener.getStickyLayout().getLocationOnScreen(locationOfSticky)
        val childLocation = IntArray(2)
        childView.getLocationOnScreen(childLocation)
        val locationOfScrollView = IntArray(2)
        scrollView.getLocationOnScreen(locationOfScrollView)
        if(childLocation[1] <= locationOfSticky[1]+stickyViewInflateListener.getStickyLayout().height &&
                childLocation[1] >= locationOfScrollView[1]){
            return true
        }

        return false
    }


    /**
     * Get the headerview which most recently scrolled above screen
     */
    fun getMostRecentAboveHeader(overlapStates : List<ViewOverLapState>) : ViewOverLapState {
        val aboveViews = overlapStates.filter { it == ViewOverLapState.IS_ABOVE }
        var lowestAboveView : ViewOverLapState? = null
        var highestYCooridnatesoFar = Int.MIN_VALUE
        aboveViews.forEach {
            val locationOnScreen = IntArray(2)
            it.view.getLocationOnScreen(locationOnScreen)
            if(locationOnScreen[1] > highestYCooridnatesoFar){
                lowestAboveView = it
                highestYCooridnatesoFar = locationOnScreen[1]
            }
        }
        return lowestAboveView!!
    }

    /**
     * Highest here does not mean the highest [y] value but its position on the screen, so lower y means higher
     */
    fun getHighestVisibleHeader(overlapStates: List<ViewOverLapState>) : ViewOverLapState? {
        val insideViews = overlapStates.filter { it == ViewOverLapState.IS_INSIDE }
        var highestInsideView : ViewOverLapState? = null
        var lowestYCordinatesoFar = Int.MAX_VALUE
        insideViews.forEach {
            val locationOnScreen = IntArray(2)
            it.view.getLocationOnScreen(locationOnScreen)
            if(locationOnScreen[1] < lowestYCordinatesoFar){
                highestInsideView = it
                lowestYCordinatesoFar = locationOnScreen[1]
            }
        }
        return highestInsideView
    }


    fun getOverlappStates() : List<ViewOverLapState>{
        return stickyViewInflateListener.getHeaderViews()
            .map {
                getOverlapState(scrollView,it)
            }
    }

    /**
     * For all of the view headers, get their overlap state with the scroll view
     */
    fun getOverlapState(parentView : View, childView : View) : ViewOverLapState{
        val scrollViewLocation = IntArray(2)
        parentView.getLocationOnScreen(scrollViewLocation)

        val childLocation = IntArray(2)
        childView.getLocationOnScreen(childLocation)

        if(childLocation[1] < scrollViewLocation[1]){
            return ViewOverLapState.IS_ABOVE.apply { view = childView }
        }
        else if(childLocation[1] > (scrollViewLocation[1]+parentView.height)){
            return ViewOverLapState.IS_BELOW.apply { view =childView }
        }
        else {
            return ViewOverLapState.IS_INSIDE.apply { view = childView }
        }
    }



    fun setSticky(viewOverLapState: ViewOverLapState){
        stickyViewInflateListener.inflate(viewOverLapState.view)
        currentVisibleHeader = viewOverLapState
        isStickyVisible = true
    }

    fun removeSticky(){
        currentVisibleHeader = null
        stickyViewInflateListener.removeSticky()
        isStickyVisible = false
    }

    enum class ViewOverLapState(val text : String) {
        IS_INSIDE("inside"),IS_ABOVE("above"),IS_BELOW("below");
        lateinit var view : View
    }
}