利用NestedScrolling实现悬浮式导航详情页

2023-10-08 05:30

本文主要是介绍利用NestedScrolling实现悬浮式导航详情页,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1.需要实现的效果图如下:


实现方式主要有两种:

1)、ScrollView内嵌软件介绍+ViewPager+ViewPager中是ScrollView,这种方式呢,纯原生,没有涉及到自定义控件,但是这样嵌套呢,涉及到测量以及事件的冲突处理。

2)、将做外层的ScrollView改为了自定义的一个控件,继承自LinearLayout,叫做StickyNavLayout,利用NestedScrolling特性:子view和父view共同消费滑动来实现。

详细代码如下:

import android.animation.ValueAnimator;
import android.content.Context;
import android.support.v4.view.NestedScrollingParent;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.ViewPager;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.util.Log;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.animation.Interpolator;
import android.widget.LinearLayout;
import android.widget.OverScroller;public class StickyNavLayout extends LinearLayout implements NestedScrollingParent
{private static final String TAG = "StickyNavLayout";/*首先子View需要找到一个支持NestedScrollingParent的父View,
告知父View我准备开始和你一起处理滑动事件了,
一般情况下都是在onTouchEvent的ACTION_DOWN中调用public boolean startNestedScroll(int axes),
然后父View就会被回调public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes)
和public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes)onStartNestedScroll可以理解是父View的一个验证机制,
父View可以在此方法中根据滑动方向等信息决定是否要和子View一起处理此次滑动,
只有在onStartNestedScroll返回true的时候才会接着调用onNestedScrollAccepted*/@Overridepublic boolean onStartNestedScroll(View child, View target, int nestedScrollAxes){Log.e(TAG, "onStartNestedScroll");return true;}
/*父View接受了子View的邀请,可以在此方法中做一些初始化的操作*/@Overridepublic void onNestedScrollAccepted(View child, View target, int nestedScrollAxes){Log.e(TAG, "onNestedScrollAccepted");}
/*
* 随着ACTION_UP或者ACTION_CANCEL的到来,子View需要调用public void stopNestedScroll()
* 来告知父View本次NestedScrollig结束,父View对应的会被回调public void onStopNestedScroll(View target),
* 可以在此方法中做一些对应停止的逻辑操作比如资源释放等
* */@Overridepublic void onStopNestedScroll(View target){Log.e(TAG, "onStopNestedScroll");}
/*
* 父View处理完后,接下来子View就要进自己的滑动操作了,滑动完成后子View还需要调用public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow)
* 将自己的滑动结果再次传递给父View,父View对应的会被回调public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed),但这步操作有一个前提,
* 就是父View没有将滑动值全部消耗掉,因为父View全部消耗掉,子View就不应该再进行滑动了,这一步也就没有必要了
*
* */@Overridepublic void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed){Log.e(TAG, "onNestedScroll");}
/*
* 每次子View在滑动前都需要将滑动细节传递给父View,
* 一般情况下是在ACTION_MOVE中调用public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow),
* 然后父View就会被回调public void onNestedPreScroll(View target, int dx, int dy, int[] consumed)
* dx dy代表本次滑动 x y方向的距离,consumed需要子View创建并传递给父View,
* 如果父View选择要消耗掉滑动的值就需要通过此数组传递给子View,consumed[0]:x轴消费的距离;consumed[1]:y轴消费的距离
* */@Overridepublic void onNestedPreScroll(View target, int dx, int dy, int[] consumed){Log.e(TAG, "onNestedPreScroll");Log.e(TAG, "onNestedPreScroll scrollY=" + getScrollY());Log.e(TAG, "onNestedPreScroll dy=" + dy);boolean hiddenTop = dy > 0 && getScrollY() < mTopViewHeight;boolean showTop = dy < 0 && getScrollY() >= 0 && !ViewCompat.canScrollVertically(target, -1);if (hiddenTop || showTop){scrollBy(0, dy);consumed[1] = dy;}}private int TOP_CHILD_FLING_THRESHOLD = 3;/** 如果产生了fling,就需要子View在stopNestedScroll前调用public boolean dispatchNestedPreFling(View target, float velocityX, float velocityY)* 和public boolean dispatchNestedFling(View target, float velocityX, float velocityY, boolean consumed),* 父View对应的会被回调public boolean onNestedPreFling(View target, float velocityX, float velocityY)* 和public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed)*参数consumed代表子View是否消耗掉了fling,fling不存在部分消耗,一旦被消耗就是指全部*返回值代表父View是否消耗掉了fling*  */@Overridepublic boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed){Log.e(TAG, "onNestedFling, velocityX=" + velocityX + ",velocityY=" + velocityY);//如果是recyclerView 根据判断第一个元素是哪个位置可以判断是否消耗//这里判断如果第一个元素的位置是大于TOP_CHILD_FLING_THRESHOLD的//认为已经被消耗,在animateScroll里不会对velocityY<0时做处理if (target instanceof RecyclerView && velocityY < 0) {final RecyclerView recyclerView = (RecyclerView) target;final View firstChild = recyclerView.getChildAt(0);final int childAdapterPosition = recyclerView.getChildAdapterPosition(firstChild);Log.d(TAG,"onNestedFling childAdapterPosition" + childAdapterPosition);consumed = childAdapterPosition > TOP_CHILD_FLING_THRESHOLD;}if (!consumed) {animateScroll(velocityY, computeDuration(0),consumed);} else {animateScroll(velocityY, computeDuration(velocityY),consumed);}return true;}@Overridepublic boolean onNestedPreFling(View target, float velocityX, float velocityY){//不做拦截 可以传递给子Viewreturn false;}@Overridepublic int getNestedScrollAxes(){Log.e(TAG, "getNestedScrollAxes");return 0;}/*** 根据速度计算滚动动画持续时间* @param velocityY* @return*/private int computeDuration(float velocityY) {final int distance;if (velocityY > 0) {distance = Math.abs(mTop.getHeight() - getScrollY());} else {distance = Math.abs(mTop.getHeight() - (mTop.getHeight() - getScrollY()));}final int duration;velocityY = Math.abs(velocityY);if (velocityY > 0) {duration = 3 * Math.round(1000 * (distance / velocityY));} else {final float distanceRatio = (float) distance / getHeight();duration = (int) ((distanceRatio + 1) * 150);}return duration;}private void animateScroll(float velocityY, final int duration,boolean consumed) {final int currentOffset = getScrollY();final int topHeight = mTop.getHeight();if (mOffsetAnimator == null) {mOffsetAnimator = new ValueAnimator();mOffsetAnimator.setInterpolator(mInterpolator);mOffsetAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {if (animation.getAnimatedValue() instanceof Integer) {scrollTo(0, (Integer) animation.getAnimatedValue());}}});} else {mOffsetAnimator.cancel();}mOffsetAnimator.setDuration(Math.min(duration, 600));if (velocityY >= 0) {mOffsetAnimator.setIntValues(currentOffset, topHeight);mOffsetAnimator.start();}else {//如果子View没有消耗down事件 那么就让自身滑倒0位置if(!consumed){mOffsetAnimator.setIntValues(currentOffset, 0);mOffsetAnimator.start();}}}private View mTop;private View mNav;private ViewPager mViewPager;private int mTopViewHeight;private OverScroller mScroller;private VelocityTracker mVelocityTracker;private ValueAnimator mOffsetAnimator;private Interpolator mInterpolator;private int mTouchSlop;private int mMaximumVelocity, mMinimumVelocity;private float mLastY;private boolean mDragging;public StickyNavLayout(Context context, AttributeSet attrs){super(context, attrs);setOrientation(LinearLayout.VERTICAL);mScroller = new OverScroller(context);mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();mMaximumVelocity = ViewConfiguration.get(context).getScaledMaximumFlingVelocity();mMinimumVelocity = ViewConfiguration.get(context).getScaledMinimumFlingVelocity();}private void initVelocityTrackerIfNotExists(){if (mVelocityTracker == null){mVelocityTracker = VelocityTracker.obtain();}}private void recycleVelocityTracker(){if (mVelocityTracker != null){mVelocityTracker.recycle();mVelocityTracker = null;}}//    @Override
//    public boolean onTouchEvent(MotionEvent event)
//    {
//        initVelocityTrackerIfNotExists();
//        mVelocityTracker.addMovement(event);
//        int action = event.getAction();
//        float y = event.getY();
//
//        switch (action)
//        {
//            case MotionEvent.ACTION_DOWN:
//                if (!mScroller.isFinished())
//                    mScroller.abortAnimation();
//                mLastY = y;
//                return true;
//            case MotionEvent.ACTION_MOVE:
//                float dy = y - mLastY;
//
//                if (!mDragging && Math.abs(dy) > mTouchSlop)
//                {
//                    mDragging = true;
//                }
//                if (mDragging)
//                {
//                    scrollBy(0, (int) -dy);
//                }
//
//                mLastY = y;
//                break;
//            case MotionEvent.ACTION_CANCEL:
//                mDragging = false;
//                recycleVelocityTracker();
//                if (!mScroller.isFinished())
//                {
//                    mScroller.abortAnimation();
//                }
//                break;
//            case MotionEvent.ACTION_UP:
//                mDragging = false;
//                mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
//                int velocityY = (int) mVelocityTracker.getYVelocity();
//                if (Math.abs(velocityY) > mMinimumVelocity)
//                {
//                    fling(-velocityY);
//                }
//                recycleVelocityTracker();
//                break;
//        }
//
//        return super.onTouchEvent(event);
//    }@Overrideprotected void onFinishInflate(){super.onFinishInflate();mTop = findViewById(R.id.id_stickynavlayout_topview);mNav = findViewById(R.id.id_stickynavlayout_indicator);View view = findViewById(R.id.id_stickynavlayout_viewpager);if (!(view instanceof ViewPager)){throw new RuntimeException("id_stickynavlayout_viewpager show used by ViewPager !");}mViewPager = (ViewPager) view;}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){//不限制顶部的高度super.onMeasure(widthMeasureSpec, heightMeasureSpec);getChildAt(0).measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));ViewGroup.LayoutParams params = mViewPager.getLayoutParams();params.height = getMeasuredHeight() - mNav.getMeasuredHeight();setMeasuredDimension(getMeasuredWidth(), mTop.getMeasuredHeight() + mNav.getMeasuredHeight() + mViewPager.getMeasuredHeight());}@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh){super.onSizeChanged(w, h, oldw, oldh);mTopViewHeight = mTop.getMeasuredHeight();}public void fling(int velocityY){mScroller.fling(0, getScrollY(), 0, velocityY, 0, 0, 0, mTopViewHeight);invalidate();}@Overridepublic void scrollTo(int x, int y){if (y < 0){y = 0;}if (y > mTopViewHeight){y = mTopViewHeight;}if (y != getScrollY()){super.scrollTo(x, y);}}@Overridepublic void computeScroll(){Log.d(TAG,"computeScroll getCurrY():" + mScroller.getCurrY());if (mScroller.computeScrollOffset()){scrollTo(0, mScroller.getCurrY());invalidate();}}}

主要原理就是,子View和父view一起处理一个滑动事件。在public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) 中获取父view在x,y轴上要滑动的距离,并调用scrollBy(0, dy); 不断进行滑动。

对Fling的滑动,则在public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed)中获取到y轴的滑动速度,并利用ValueAnimator .addUpdateListener 获取不断变化的y轴值,调用scrollTo来进行滑动操作。

2.使用

详情页布局如下:

    <com.zhy.view.StickyNavLayout xmlns:tools="http://schemas.android.com/tools"  xmlns:android="http://schemas.android.com/apk/res/android"  android:layout_width="match_parent"  android:layout_height="match_parent"  android:orientation="vertical" >  <RelativeLayout  android:id="@id/id_stickynavlayout_topview"  android:layout_width="match_parent"  android:layout_height="300dp"  android:background="#4400ff00" >  <TextView  android:layout_width="match_parent"  android:layout_height="match_parent"  android:gravity="center"  android:text="软件介绍"  android:textSize="30sp"  android:textStyle="bold" />  </RelativeLayout>  <com.zhy.view.SimpleViewPagerIndicator  android:id="@id/id_stickynavlayout_indicator"  android:layout_width="match_parent"  android:layout_height="50dp"  android:background="#ffffffff" >  </com.zhy.view.SimpleViewPagerIndicator>  <android.support.v4.view.ViewPager  android:id="@id/id_stickynavlayout_viewpager"  android:layout_width="match_parent"  android:layout_height="wrap_content"  android:background="#44ff0000" >  </android.support.v4.view.ViewPager>  </com.zhy.view.StickyNavLayout>  

最外层是父view StickyNavLayout 再往下,依次是header, viewpageIndicator, 和viewpager



这篇关于利用NestedScrolling实现悬浮式导航详情页的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/163028

相关文章

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

C#实战|大乐透选号器[6]:实现实时显示已选择的红蓝球数量

哈喽,你好啊,我是雷工。 关于大乐透选号器在前面已经记录了5篇笔记,这是第6篇; 接下来实现实时显示当前选中红球数量,蓝球数量; 以下为练习笔记。 01 效果演示 当选择和取消选择红球或蓝球时,在对应的位置显示实时已选择的红球、蓝球的数量; 02 标签名称 分别设置Label标签名称为:lblRedCount、lblBlueCount

Kubernetes PodSecurityPolicy:PSP能实现的5种主要安全策略

Kubernetes PodSecurityPolicy:PSP能实现的5种主要安全策略 1. 特权模式限制2. 宿主机资源隔离3. 用户和组管理4. 权限提升控制5. SELinux配置 💖The Begin💖点点关注,收藏不迷路💖 Kubernetes的PodSecurityPolicy(PSP)是一个关键的安全特性,它在Pod创建之前实施安全策略,确保P

工厂ERP管理系统实现源码(JAVA)

工厂进销存管理系统是一个集采购管理、仓库管理、生产管理和销售管理于一体的综合解决方案。该系统旨在帮助企业优化流程、提高效率、降低成本,并实时掌握各环节的运营状况。 在采购管理方面,系统能够处理采购订单、供应商管理和采购入库等流程,确保采购过程的透明和高效。仓库管理方面,实现库存的精准管理,包括入库、出库、盘点等操作,确保库存数据的准确性和实时性。 生产管理模块则涵盖了生产计划制定、物料需求计划、

C++——stack、queue的实现及deque的介绍

目录 1.stack与queue的实现 1.1stack的实现  1.2 queue的实现 2.重温vector、list、stack、queue的介绍 2.1 STL标准库中stack和queue的底层结构  3.deque的简单介绍 3.1为什么选择deque作为stack和queue的底层默认容器  3.2 STL中对stack与queue的模拟实现 ①stack模拟实现

基于51单片机的自动转向修复系统的设计与实现

文章目录 前言资料获取设计介绍功能介绍设计清单具体实现截图参考文献设计获取 前言 💗博主介绍:✌全网粉丝10W+,CSDN特邀作者、博客专家、CSDN新星计划导师,一名热衷于单片机技术探索与分享的博主、专注于 精通51/STM32/MSP430/AVR等单片机设计 主要对象是咱们电子相关专业的大学生,希望您们都共创辉煌!✌💗 👇🏻 精彩专栏 推荐订阅👇🏻 单片机