在前一篇博文中已经实现过一个仿魅族flyme6应用市场应用详情弹出式layout: Android自定义控件:从零开始实现魅族flyme6应用市场应用详情弹出式layout,主要是通过viewDragHelper来实现,大部分效果算是实现了,但是在最后还是有一些bug。
趁着这段时间工作比较轻松一点,这次再通过NestedScrolling来实现一次这个自定义控件,对比前面的实现方法,通过NestedScrolling实现起来会简单许多。
老规矩,先看看最终要实现的效果图:

()

NestedScrolling

NestedScrolling是个啥玩意呢?这是Google官方从5.0后引入的滑动嵌套解决方案。
看效果图看的出来,这次我们要实现的效果的难点就在嵌套滑动,因为手指放到scrollview中,然后实际滚动的是却外部的ViewGroup,在ViewGroup滚动到顶部的时候呢,内部的Scrollview又继续滚动。按照传统的View事件拦截和处理方式,那首先要保证ViewGroup拦截事件,否则事件会被内部的scrollview消费掉。但是如果拦截了,当ViewGroup滚动到顶部的时候又如何让scrollview又持续滑动呢?按照传统的方式,一次事件拦截就是一次性处理的事情,ViewGroup如果拦截了这次滑动事件,那么scrollview肯定是没法继续处理这次滑动事件的。
我们上篇博文是通过事件拦截和分发人为的在ViewGroup中更动态的修改scrollView的滑动,从视觉上实现一次滑动事件ViewGroup和子view嵌套的滚动效果。实际上从本质上来讲,还是ViewGroup拦截和消费了事件,第一次ViewGroup中的事件并没有到子view中去处理。

那么NestedScrolling如何实现嵌套滑动呢?
NestedScrollingParent内部实现了NestedScrollingChild接口的子View会优先获得事件处理权,然后滑动的时候,会先将dx、dy传入给NestedScrollingParent,NestedScrollingParent可以决定是否对其进行消耗,也就是说NestedScrollingParent可以消费部分dx、dy,余下的未消费完的dx、dy交还给子view去消费。

这样看实际上要实现本次的效果就很简单了,话不多说,贴代码。

先让我们的自定义ScrollView实现NestedScrollingChild接口,并且将NestedScrolling相关的处理全部交给ScrollingChildHelper处理。

public class MyScrollView extends ScrollView implements NestedScrollingChild{private boolean isScrollToTop = true;private boolean isScrollToBottom = false;private OnScrollLimitListener mOnScrollLimitListener;private NestedScrollingChildHelper mScrollingChildHelper;public MyScrollView(Context context) {this(context, null);}public MyScrollView(Context context, AttributeSet attrs) {this(context, attrs, 0);}public MyScrollView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}@Overridepublic void setNestedScrollingEnabled(boolean enabled) {getScrollingChildHelper().setNestedScrollingEnabled(enabled);}@Overridepublic boolean isNestedScrollingEnabled() {return getScrollingChildHelper().isNestedScrollingEnabled();}@Overridepublic boolean startNestedScroll(int axes) {return getScrollingChildHelper().startNestedScroll(axes);}@Overridepublic void stopNestedScroll() {getScrollingChildHelper().stopNestedScroll();}@Overridepublic boolean hasNestedScrollingParent() {return getScrollingChildHelper().hasNestedScrollingParent();}@Overridepublic boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed,int dyUnconsumed, int[] offsetInWindow) {return getScrollingChildHelper().dispatchNestedScroll(dxConsumed, dyConsumed,dxUnconsumed, dyUnconsumed, offsetInWindow);}@Overridepublic boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {return getScrollingChildHelper().dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);}@Overridepublic boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {return getScrollingChildHelper().dispatchNestedFling(velocityX, velocityY, consumed);}@Overridepublic boolean dispatchNestedPreFling(float velocityX, float velocityY) {return getScrollingChildHelper().dispatchNestedPreFling(velocityX, velocityY);}private NestedScrollingChildHelper getScrollingChildHelper() {if (mScrollingChildHelper == null) {mScrollingChildHelper = new NestedScrollingChildHelper(this);setNestedScrollingEnabled(true);}return mScrollingChildHelper;}/*** 设置ScrollView滑动到边界监听** @param onScrollLimitListener ScrollView滑动到边界监听*/public void setOnScrollLimitListener(OnScrollLimitListener onScrollLimitListener) {mOnScrollLimitListener = onScrollLimitListener;}@Overrideprotected void onScrollChanged(int l, int t, int oldl, int oldt) {super.onScrollChanged(l, t, oldl, oldt);if (getScrollY() == 0) {//滑动到顶部isScrollToTop = true;isScrollToBottom = false;isScrollToBottom = false;} else if (getScrollY() + getHeight() - getPaddingTop() - getPaddingBottom() ==getChildAt(0).getHeight()) {// 小心踩坑: 这里不能是 >=// 小心踩坑:这里最容易忽视的就是ScrollView上下的padding isScrollToTop = false;isScrollToBottom = true;} else {isScrollToTop = false;isScrollToBottom = false;}notifyScrollChangedListeners();}/*** 回调*/private void notifyScrollChangedListeners() {if (isScrollToTop) {if (mOnScrollLimitListener != null) {mOnScrollLimitListener.onScrollTop();}} else if (isScrollToBottom) {if (mOnScrollLimitListener != null) {mOnScrollLimitListener.onScrollBottom();}} else {if (mOnScrollLimitListener != null) {mOnScrollLimitListener.onScrollOther();}}}/*** scrollview滑动到边界监听接口*/public interface OnScrollLimitListener {/*** 滑动到顶部*/void onScrollTop();/*** 滑动到顶部和底部之间的位置(既不是顶部也不是底部)*/void onScrollOther();/*** 滑动到底部*/void onScrollBottom();}
}

然后是我们的PopupLayout,上一篇博文是通过自定义FrameLayout的方式实现的,这次由于是通过NestedScrolling实现,所以一次滑动事件其实是针对整个ViewGroup的,所以本次采取自定义LinearLayout的方式去实现。

在这里我们重点看下面几个方法,首先是onMeasure方法。因为初始状态下ContentView是在界面之外的,所以要确定ContentView的高度。

    @Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);Log.e("tag", "onMeasure");ViewGroup.LayoutParams params = contentView.getLayoutParams();params.height = darkView.getMeasuredHeight() - mOrginY;setMeasuredDimension(getMeasuredWidth(), contentView.getMeasuredHeight() + darkView.getMeasuredHeight());}

接下来看看重写的NestedScrollingParent几个方法。

@Overridepublic boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {Log.e(TAG, "onStartNestedScroll");return true;}@Overridepublic void onNestedScrollAccepted(View child, View target, int nestedScrollAxes) {Log.e(TAG, "onNestedScrollAccepted");}@Overridepublic void onStopNestedScroll(View target) {Log.e(TAG, "onStopNestedScroll");if (mDarkViewHeight - mOrginY - getScrollY() > mDragRange) {//向下拖拽,超出拖拽限定距离dismiss();} else if (mDarkViewHeight - mOrginY - getScrollY() > 0) {//向下拖拽,但是没有超出拖拽限定距离springback();}}@Overridepublic void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, intdyUnconsumed) {Log.e(TAG, "onNestedScroll");}@Overridepublic void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {boolean patchDown = dy < 0 && mIsScrollInTop;//下滑boolean patchUp = dy > 0 && getScrollY() < (mDarkViewHeight - UIUtils.getStatusBarHeight(target));//上滑if (patchDown || patchUp) {scrollBy(0, dy);consumed[1] = dy;}}@Overridepublic boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {return true;}@Overridepublic boolean onNestedPreFling(View target, float velocityX, float velocityY) {//不做拦截 可以传递给子Viewreturn false;}@Overridepublic int getNestedScrollAxes() {Log.e(TAG, "getNestedScrollAxes");return 0;}

onNestedPreScroll中,我们判断,如果是上滑且contentView未滑动到顶部,则消耗掉dy,即consumed[1]=dy。如果是下滑且内部scrollview已经滑动到顶,则消耗掉dy,即consumed[1]=dy,消耗掉的意思,就是自己去执行scrollBy,实际上就是滑动PopupLayout本身。

onStopNestedScroll中,我们判断向下滑动的距离,来确定是dismiss PopupLayout还是回弹到初始位置。

最后由于需要更新TitleBar的状态,所以重写了scrollTo方法,在scrollTo方法中更新TitleBar的状态。

    @Overridepublic void scrollTo(int x, int y) {if (y >= mDarkViewHeight - UIUtils.getStatusBarHeight(this)) {y = mDarkViewHeight - UIUtils.getStatusBarHeight(this);darkView.setBackgroundColor(Color.WHITE);//拖动到顶部时darkview背景设置白色titleBar.setBackImageResource(R.mipmap.back);} else {darkView.setBackgroundResource(R.color.dark);//没有拖动到顶部时darkview背景设置暗色titleBar.setBackImageResource(R.mipmap.close);}if (y != getScrollY()) {super.scrollTo(x, y);}}

本次的要点基本就这么多,总的来说相较上一篇博文各种绞尽脑汁想着事件处理,这次通过NestedScrolling就重写几个方法,然后根据自己的实际需求做一些判断,实现起来还是很简单的。

最后附上源码链接:https://github.com/Horrarndoo/PopupLayoutNew

Android自定义控件:NestedScrolling实现仿魅族flyme6应用市场应用详情弹出式layout相关推荐

  1. Android自定义控件:从零开始实现魅族flyme6应用市场应用详情弹出式layout

    前几天无意中发现魅族flyme6应用市场的应用详情界面非常有意思,作为一枚程序员,看到有意思的东西怎么办?当然是想办法自己也整一个啦,哈哈. 废话不多说,下面先看看魅族flyme6应用市场详情页弹出时 ...

  2. 魅族新系统android o,Android O新特性 原来魅族Flyme6早已实现

    原标题:Android O新特性 原来魅族Flyme6早已实现 在Android N系统发布一年之后,谷歌3月22日又推出了新一代的 Android O 开发者预览版.在Android O的开发者预览 ...

  3. Android自定义弹窗模仿微信,Android 仿微信朋友圈点赞和评论弹出框功能

    本文简单模仿微信朋友圈的点赞和评论弹出框,布局等细节请忽略,着重实现弹出框.发评论,及弹出位置的控制. 1. 微信弹出框 微信朋友圈的点赞和评论功能,有2个组成部分: 点击左下角的"更多&q ...

  4. 仿闲鱼 底部菜单html,GitHub - 494293346/rotateMenu: 仿闲鱼首页,“底部加号弹出菜单选项” 界面,动画效果可能跟闲鱼有点不一样...

    rotateMenu 仿闲鱼首页,"底部加号弹出菜单选项" 界面,动画效果可能跟闲鱼有点不一样 ##简介 仿闲鱼首页,"底部加号弹出菜单选项" 界面,动画效果可 ...

  5. Android之AlertDialog(弹出式对话框)的使用

    一.简单的内容文本弹出式对话框 还是一样MainActivity的布局文件就不放上了,就是一个简单的Button控件,在Java代码中为其绑定了一个监听器. 首先我们需要创建这个AlertDialog ...

  6. 透明设置Android:将activity设置为弹出式的并设置为透明的

    首先声明,我是一个菜鸟.一下文章中出现技术误导情况盖不负责 1. 在res/values 下建立color.xml <resources><colorname="trans ...

  7. Android源码编译:任意界面屏幕边缘上滑弹出快捷操作栏【一键加速、开关控制】

    <The Fucking Source Code> 注:以下均为android源码Framework层修改. 设计实现在任意界面从屏幕边缘上滑弹出快捷操作栏,包括亮度调节.正在后台运行的程 ...

  8. Android之打开继承DialogFragment对话框里面EditText获取光标并且弹出键盘把底部布局顶上去

    1 需求 打开继承DialogFragment对话框里面EditText获取光标并且弹出键盘把底部布局顶上去 2 效果爆照如下 打开这个DialogFragment 3 关键代码实现 override ...

  9. android dialog隐藏虚拟按键,dialog全屏且不会弹出虚拟按键

    1.首先在res/values/styles中自定义dialog属性 @android:color/transparent true @null true false 2.在dialog的构造函数中s ...

最新文章

  1. windows路径操作API函数
  2. android入门学习-天气预报app(一)
  3. 关于没有commit的死锁问题
  4. 如何用shell脚本编译java工程
  5. 总体方差的充分统计量_R方是否衡量预测能力或统计充分性?
  6. 手动安装lzop压缩工具 - JerryMo06的专栏 - 博客频道 - CSDN.NET
  7. (转)理解SQLSERVER中的排序规则
  8. python语言创始人中文名_十大编程语言创始人,看看有没有你在用的语言?
  9. 音乐艺术与科技有何相关?Erkki Kurenniemi的音讯是如此
  10. 安卓图片轮播(banner)
  11. 服务器加根网线用不用修改路由器,Tenda腾达路由器ads拨号上网设置教程
  12. 浓眉大眼的Google Wave怎么也会死?
  13. 【vue】bable的介绍以及编写vue文件
  14. 打印机服务器属性添加哪个文件,如何设置打印机服务器属性如何找到打印机服务器属性...
  15. Gbit以太交换BCM56504
  16. DDD | 领域驱动设计 Vs 敏捷 Vs 面向对象
  17. ssm学生综合素质评价系统
  18. k8s集群Deployment与Service+名称空间
  19. ioncube 加密项目本地搭建
  20. 宝塔linux shell定时访问url,bt(宝塔)面板添加shell 脚本通过nginx日志封访问频率过高的IP...

热门文章

  1. 【Python】对英文文本进行词频统计(分词、字典排序、文件读写)
  2. win10计算机怎么新增用户,win10 如何添加管理员账户_win10 添加管理员账户方法-win7之家...
  3. php $_SERVER 学习详解
  4. SQL总结 学期前8周学习内容
  5. Neo4j CQL基础
  6. 近年来机器人主流抓取估计方法总结
  7. Python netCDF4
  8. 走着走着,就剩下了沉默
  9. python中squeeze函数_详解pytorch中squeeze()和unsqueeze()函数介绍
  10. 修复win7开机很丑