Android自定义控件:NestedScrolling实现仿魅族flyme6应用市场应用详情弹出式layout
来源:互联网 发布:网络带来的利与弊论文 编辑:程序博客网 时间:2024/06/05 17:22
在前一篇博文中已经实现过一个仿魅族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); } @Override public void setNestedScrollingEnabled(boolean enabled) { getScrollingChildHelper().setNestedScrollingEnabled(enabled); } @Override public boolean isNestedScrollingEnabled() { return getScrollingChildHelper().isNestedScrollingEnabled(); } @Override public boolean startNestedScroll(int axes) { return getScrollingChildHelper().startNestedScroll(axes); } @Override public void stopNestedScroll() { getScrollingChildHelper().stopNestedScroll(); } @Override public boolean hasNestedScrollingParent() { return getScrollingChildHelper().hasNestedScrollingParent(); } @Override public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) { return getScrollingChildHelper().dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow); } @Override public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) { return getScrollingChildHelper().dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow); } @Override public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) { return getScrollingChildHelper().dispatchNestedFling(velocityX, velocityY, consumed); } @Override public 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; } @Override protected 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的高度。
@Override protected 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几个方法。
@Override public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) { Log.e(TAG, "onStartNestedScroll"); return true; } @Override public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes) { Log.e(TAG, "onNestedScrollAccepted"); } @Override public void onStopNestedScroll(View target) { Log.e(TAG, "onStopNestedScroll"); if (mDarkViewHeight - mOrginY - getScrollY() > mDragRange) {//向下拖拽,超出拖拽限定距离 dismiss(); } else if (mDarkViewHeight - mOrginY - getScrollY() > 0) {//向下拖拽,但是没有超出拖拽限定距离 springback(); } } @Override public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) { Log.e(TAG, "onNestedScroll"); } @Override public 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; } } @Override public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) { return true; } @Override public boolean onNestedPreFling(View target, float velocityX, float velocityY) { //不做拦截 可以传递给子View return false; } @Override public 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的状态。
@Override public 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
- Android自定义控件:从零开始实现魅族flyme6应用市场应用详情弹出式layout
- Android 跳转应用市场的应用详情页
- Android 跳转应用市场的应用详情页
- Android 跳转应用市场的应用详情页
- Android应用如何跳转到应用市场详情页面
- 修改android-自定义控件-悬浮控件-仿360手机助手应用详情页
- (4.2.16) Android 跳转应用市场的应用详情页
- Android 跳转到指定应用市场,并进入应用详情页
- Android自定义控件:小米应用市场Banner轮播、可拉伸回弹的ListView与ScrollView
- Android 自定义控件 轻松实现360软件详情页
- Android 自定义控件 轻松实现360软件详情页
- Android 自定义控件 轻松实现360软件详情页
- Android 自定义控件 轻松实现360软件详情页
- Android 自定义控件 轻松实现360软件详情页
- Android 自定义控件 轻松实现360软件详情页
- Android 自定义控件 轻松实现360软件详情页
- Android 自定义控件 轻松实现360软件详情页
- JavaScript 产生不重复的随机数三种实现思路
- Linux下开启禁用ping
- 单调栈--poj2796 Feel good
- web App开发中手机滑动的流畅性
- app开发中的异步处理(三)
- Android自定义控件:NestedScrolling实现仿魅族flyme6应用市场应用详情弹出式layout
- Session
- Android开发之利用MQTT协议实现消息的即时推送
- oracle自带表SQL练习(三)
- Android灵魂画家的18种混合模式
- 微信支付开发调试填坑过程记录
- Ubuntu如何添加删除PPA
- EFT电快速脉冲群测试
- Java并发编程学习记录#2