上拉查看详情和下拉隐藏详情

项目商品详情页的需求,实现上拉显示和下拉隐藏详情的功能,最终效果图如图:

这里写图片描述

实现思路:上下通过判断两个ScrollView的滑动位置及触摸事件发生的位置,对他们进行隐藏和显示。
上拉部分,由于内部包裹了大量带有点击事件的组件,故需要在上拉条上进行拖动,否则会冲突无法判断。这部分是通过对其包裹内容动态的调用layout方法来实现拖拽效果;
下拉部分,通过对下拉头动态设置marginTop的值,来改变其高度,并达到隐藏和显示的效果。

核心代码


    //上拉组件
    @Bind(R.id.xscrollview)
    XScrollView mXscrollview;
    @Bind(R.id.scrollContainer)
    LinearLayout scrollContainer;

    //下拉组件
    @Bind(R.id.scrollContainer2)
    RelativeLayout scrollContainer2;
    @Bind(R.id.svBottomDetails)
    ScrollView svBottomDetails;
    @Bind({R.id.llDownScroll})
    LinearLayout llDownScroll;

    //上拉部分
    private Rect rect = null;//记录scrollView包裹组件的位置
    private int fullScroll;//scrollView滚动到底部时ScrollView的scrollY值
    private float mDeltaY;//(上拉模块)拖动的距离
    private int downScrollY;//开始拖动的ScrollView的scrollY值
    private float startY;//开始拖动的MotionEvent的y值

    //下拉部分
    private float startY2;
    private float downScrollY2;
    private float mDeltaY2;
    private boolean touched;//是否触摸,在ACTION_MOVE中做标记,记录按下时需要的值
    private RelativeLayout.LayoutParams mLayoutParams;


    /*******************监听详情的隐藏和显示*******************/

    private OnGoodsDetailsListener mOnGoodsDetailsListener;

    public void setOnGoodsDetailsListener(OnGoodsDetailsListener onGoodsDetailsListener) {
        mOnGoodsDetailsListener = onGoodsDetailsListener;
    }

    public interface OnGoodsDetailsListener{
        void onShow(boolean showDetails);
    }

    //下拉隐藏详情
    private void initPullDown() {
        mLayoutParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);

        svBottomDetails.setOnTouchListener(new View.OnTouchListener() {

            @Override
            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {

                    case MotionEvent.ACTION_DOWN:
                        //可能的冲突,改为在ACTION_MOVE中获取
                        break;

                    case MotionEvent.ACTION_UP:

                        //向下拖动距离大于100
                        if (mDeltaY2 > 120 && downScrollY2 == 0) {
                            Log.d(TAG, "onTouch: 隐藏详情了");

                            //显示上部
                            mXscrollview.setVisibility(View.VISIBLE);
                            mXscrollview.smoothScrollTo(0, fullScroll);//滚动到底部
                            //隐藏详情
                            svBottomDetails.setVisibility(View.GONE);

                            //还原标题-->商品 详情 评价
                            //修改返回按键和小箭头的事件->点击结束act
                            //将状态传到activity,改变标题
                            mOnGoodsDetailsListener.onShow(false);
                        }

//                      //恢复原有marginTop高度,隐藏头
                        mLayoutParams.setMargins(0, (int) getResources().getDimension(R.dimen.pulldown_head_margin), 0, 0);
                        llDownScroll.setLayoutParams(mLayoutParams);

                        //重置
                        mDeltaY2 = 0;
                        downScrollY2 = 0;
                        startY2 = 0;
                        touched = false;

                        break;
                    case MotionEvent.ACTION_MOVE:
                        //从顶部开始的滑动,且向下滑

                        //在此记录按下位置,取代ACTION_DOWN中
                        if (!touched) {
                            startY2 = event.getY();
                            downScrollY2 = svBottomDetails.getScrollY();
                            Log.d(TAG, "onTouch: startY2 = " + startY2 + " , downScrollY2 = " + downScrollY2);
                        }
                        touched = true;
                        mDeltaY2 = 0.5f * (event.getY() - startY2);
                        Log.d(TAG, "onTouch: downScrollY2 = " + downScrollY2);
                        Log.d(TAG, "onTouch: startY2 = " + startY2);
                        Log.d(TAG, "onTouch: mDeltaY2 = " + mDeltaY2);

                        if (downScrollY2 == 0 && mDeltaY2 > 0) {
                            //计算marginTop高度,动态显示头高度
                            int top = (int) (-120 + mDeltaY2);
                            Log.d(TAG, "onTouch: marginTop = " + top);
                            mLayoutParams.setMargins(0, top, 0, 0);
                            llDownScroll.setLayoutParams(mLayoutParams);
                        }

                        break;
                    default:
                        break;
                }
                return false;
            }
        });
    }


    /**
     * 上拉查看详情
     */
    private void initPullUp() {

        //这一步操作为,获取ScrollView的完全高度,在上拉时,判断是否从最底部开始
        mXscrollview.setOnScrollToBottomLintener(new XScrollView.OnScrollToBottomListener() {
            @Override
            public void onScrollBottomListener(boolean isBottom) {
                if (isBottom) {
                    fullScroll = mXscrollview.getScrollY();
                    Log.d(TAG, "onScrollBottomListener: scrollY = " + fullScroll);
                }
            }
        });

        mXscrollview.setOnTouchListener(new View.OnTouchListener() {

            @Override
            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {

                    case MotionEvent.ACTION_DOWN:
                        //记录按下时的Y值
                        startY = event.getY();
                        downScrollY = mXscrollview.getScrollY();
                        Log.d(TAG, "onTouch: startY = " + startY + " , downScrollY = " + downScrollY);
                        if (rect == null) {
                            rect = new Rect(scrollContainer.getLeft(), scrollContainer.getTop(), scrollContainer.getRight(), scrollContainer.getBottom());
                        }

                        break;

                    case MotionEvent.ACTION_UP:

                        //拖动距离大于120
                        if (Math.abs(mDeltaY) > 120) {
                            Log.d(TAG, "onTouch: 显示详情了");
                            //显示详情
                            svBottomDetails.setVisibility(View.VISIBLE);
                            svBottomDetails.smoothScrollTo(0, 0);
                            //隐藏上部
                            mXscrollview.setVisibility(View.GONE);

                            //改变标题-->图文详情
                            //修改返回按键和小箭头的事件->点击还原
                            //将状态传到activity,改变标题
                            mOnGoodsDetailsListener.onShow(true);
                        }

                        // 恢复原有高度
                        if (rect != null) {
                            scrollContainer.layout(rect.left, rect.top, rect.right, rect.bottom);
                            Log.d(TAG, "onTouch: 松手了");
                        }
                        //重置
                        mDeltaY = 0;
                        downScrollY = 0;
                        startY = 0;
                        break;
                    case MotionEvent.ACTION_MOVE:
                        //downScrollY != 0:不是从顶部开始的滑动;fullScroll不为0且不为负值(bug?)
                        if (downScrollY != 0 && fullScroll > 0 && downScrollY >= fullScroll - 20) {
                            //deltaY<0,向上滑动
                            mDeltaY = 0.5f * (event.getY() - startY);
                            Log.d(TAG, "onTouch: downScrollY = " + downScrollY);
                            Log.d(TAG, "onTouch: fullScroll = " + fullScroll);
                            Log.d(TAG, "onTouch: mDeltaY = " + mDeltaY);
                            if (rect != null) {
                                scrollContainer.layout(rect.left, (int) (rect.top + mDeltaY), rect.right, (int) (rect.bottom + mDeltaY));
                            }
                        }

                        break;
                    default:
                        break;
                }
                return false;
            }
        });

    }

布局文件

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                xmlns:app="http://schemas.android.com/apk/res-auto"
                android:id="@+id/rlContainer"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:background="@color/nc_good_bg">

    <!--上部ScrollView包裹上部内容-->

    <ScrollView
        android:id="@+id/xscrollview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_alignParentStart="true"
        android:layout_below="@+id/tvLoading"
        android:visibility="visible">

        <LinearLayout
            android:id="@+id/scrollContainer"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">


                <!--省略部分-->

                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="10dp"
                    android:background="@color/app_white"
                    android:drawableLeft="@drawable/arrow_top"
                    android:gravity="center"
                    android:orientation="horizontal">

                    <ImageView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:background="@drawable/arrow_top"/>

                    <TextView
                        android:id="@+id/tvUpScroll"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:background="@color/app_white"
                        android:gravity="center"
                        android:paddingBottom="20dp"
                        android:paddingLeft="4dp"
                        android:paddingTop="20dp"
                        android:text="上拉查看图文详情"/>
                </LinearLayout>


        </LinearLayout>


    </ScrollView>


    <ScrollView
        android:id="@+id/svBottomDetails"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:visibility="gone">

        <RelativeLayout
            android:id="@+id/scrollContainer2"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:visibility="visible">

            <!--给marginTop一个高度的负值,隐藏下拉的头-->
            <LinearLayout
                android:id="@+id/llDownScroll"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="@dimen/pulldown_head_margin"
                android:background="@color/app_white"
                android:gravity="center"
                android:orientation="horizontal"
                android:visibility="visible">

                <ImageView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:background="@drawable/arrow_down"/>

                <TextView
                    android:id="@+id/tvDownScroll"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:background="@color/app_white"
                    android:gravity="center"
                    android:paddingBottom="20dp"
                    android:paddingLeft="4dp"
                    android:paddingTop="20dp"
                    android:text="下拉收起图文详情"/>
            </LinearLayout>

            <LinearLayout
                android:id="@+id/llTabs"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_below="@+id/llDownScroll"
                android:background="@color/app_white"
                android:orientation="horizontal"
                android:padding="@dimen/dp8"
                android:visibility="visible">

                <!--省略部分-->

            </LinearLayout>

            <WebView
                android:id="@+id/wvImg"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_below="@+id/llTabs"/>


        </RelativeLayout>

    </ScrollView>

</RelativeLayout>

重写的ScrollView,可获取最大滚动高度

//滚动到底部时,clampedY变为true,其余情况为false,通过回调将状态传出去即可。
    @Override
    protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
        super.onOverScrolled(scrollX, scrollY, clampedX, clampedY);
        if (scrollY != 0 && null != mOnScrollToBottomListener) {
            mOnScrollToBottomListener.onScrollBottomListener(clampedY);
        }
    }

重写后的ViewPager,可禁止水平滑动

public class MyViewPager extends ViewPager {

    public MyViewPager(Context context) {
        super(context);
    }

    public MyViewPager(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    //默认为true
    private boolean isCanScroll = true;

    //通过设置为false,禁止ViewPager的水平滑动
    public void setCanScroll(boolean isCanScroll) {
        this.isCanScroll = isCanScroll;
    }

    @Override
    public void scrollTo(int x, int y) {
        if (isCanScroll) {
            super.scrollTo(x, y);
        }
    }
}

Activity中控制标题显示

/*******************控制标题显示*********************/
    @Override
    public void onShow(boolean showDetails) {
        this.showDetails = showDetails;
        if (showDetails){
            setCommonHeader("图文详情");
            tabGoods.setVisibility(View.GONE);
            //禁止ViewPager水平滑动
            vpFragment.setCanScroll(false);
        }else {
            setCommonHeader("");
            //显示TabLayout
            tabGoods.setVisibility(View.VISIBLE);
            //恢复ViewPager水平滑动
            vpFragment.setCanScroll(true);
        }
    }
  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
分页上拉加载和下拉刷新是移动端开发中常见的功能,通常需要使用到第三方库来实现,比如React Native中的FlatList、ListView、SectionList等组件,这些组件都提供了相关的API来实现分页、上拉加载和下拉刷新功能。 具体实现步骤如下: 1.在界面中添加FlatList、ListView、SectionList等组件,并设置相关属性如数据源、渲染函数等。 2.为组件添加onEndReachedThreshold和onEndReached事件,onEndReachedThreshold表示距离底部多少距离触发onEndReached事件,onEndReached事件表示到达底部时触发的回调函数,通常在回调函数中发起下一页数据的请求。 3.为组件添加refreshControl属性,并设置onRefresh事件,refreshControl表示下拉刷新的控件,onRefresh事件表示下拉刷新时触发的回调函数,通常在回调函数中重新请求第一页数据。 4.为组件添加ListFooterComponent属性,并设置上拉加载的控件,通常在请求下一页数据时显示上拉加载的控件,请求完毕后隐藏上拉加载的控件。 5.在请求数据时需要记录当前页码,并将请求的数据添加到数据源中,然后重新渲染组件即可。 示例代码如下(以React Native中的FlatList组件为例): ```javascript import React, { useState, useEffect } from 'react'; import { FlatList, View, Text, ActivityIndicator } from 'react-native'; const pageSize = 10; // 每页大小 const data = []; // 数据源 const renderItem = ({ item }) => { return ( <View> <Text>{item.title}</Text> </View> ); }; const renderFooter = () => { return ( <View style={{ alignItems: 'center', justifyContent: 'center', paddingVertical: 10 }}> <ActivityIndicator size="small" /> </View> ); }; const App = () => { const [page, setPage] = useState(1); // 当前页码 const [isLoading, setIsLoading] = useState(false); // 是否正在加载 useEffect(() => { fetchData(); }, [page]); const fetchData = async () => { setIsLoading(true); try { const response = await fetch(`https://api.example.com/list?page=${page}&pageSize=${pageSize}`); const result = await response.json(); data.push(...result.data); setIsLoading(false); } catch (error) { console.error(error); setIsLoading(false); } }; const handleRefresh = () => { setPage(1); }; const handleEndReached = () => { setPage(page + 1); }; return ( <FlatList data={data} renderItem={renderItem} ListFooterComponent={isLoading && renderFooter} onEndReachedThreshold={0.1} onEndReached={handleEndReached} refreshControl={<RefreshControl refreshing={isLoading} onRefresh={handleRefresh} />} /> ); }; export default App; ``` 以上代码中,App组件中的FlatList组件实现了分页上拉加载和下拉刷新功能,pageSize表示每页大小,data表示数据源,renderItem表示渲染函数,renderFooter表示上拉加载的控件,page表示当前页码,isLoading表示是否正在加载,fetchData表示请求数据的函数,handleRefresh表示下拉刷新时触发的函数,handleEndReached表示到达底部时触发的函数。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值