Android自定义控件:NestedScrolling实现仿魅族flyme6应用市场应用详情弹出式layout
在前一篇博文中已經(jīng)實現(xiàn)過一個仿魅族flyme6應(yīng)用市場應(yīng)用詳情彈出式layout: Android自定義控件:從零開始實現(xiàn)魅族flyme6應(yīng)用市場應(yīng)用詳情彈出式layout,主要是通過viewDragHelper來實現(xiàn),大部分效果算是實現(xiàn)了,但是在最后還是有一些bug。
趁著這段時間工作比較輕松一點,這次再通過NestedScrolling來實現(xiàn)一次這個自定義控件,對比前面的實現(xiàn)方法,通過NestedScrolling實現(xiàn)起來會簡單許多。
老規(guī)矩,先看看最終要實現(xiàn)的效果圖:
()
NestedScrolling
NestedScrolling是個啥玩意呢?這是Google官方從5.0后引入的滑動嵌套解決方案。
看效果圖看的出來,這次我們要實現(xiàn)的效果的難點就在嵌套滑動,因為手指放到scrollview中,然后實際滾動的是卻外部的ViewGroup,在ViewGroup滾動到頂部的時候呢,內(nèi)部的Scrollview又繼續(xù)滾動。按照傳統(tǒng)的View事件攔截和處理方式,那首先要保證ViewGroup攔截事件,否則事件會被內(nèi)部的scrollview消費掉。但是如果攔截了,當(dāng)ViewGroup滾動到頂部的時候又如何讓scrollview又持續(xù)滑動呢?按照傳統(tǒng)的方式,一次事件攔截就是一次性處理的事情,ViewGroup如果攔截了這次滑動事件,那么scrollview肯定是沒法繼續(xù)處理這次滑動事件的。
我們上篇博文是通過事件攔截和分發(fā)人為的在ViewGroup中更動態(tài)的修改scrollView的滑動,從視覺上實現(xiàn)一次滑動事件ViewGroup和子view嵌套的滾動效果。實際上從本質(zhì)上來講,還是ViewGroup攔截和消費了事件,第一次ViewGroup中的事件并沒有到子view中去處理。
那么NestedScrolling如何實現(xiàn)嵌套滑動呢?
NestedScrollingParent內(nèi)部實現(xiàn)了NestedScrollingChild接口的子View會優(yōu)先獲得事件處理權(quán),然后滑動的時候,會先將dx、dy傳入給NestedScrollingParent,NestedScrollingParent可以決定是否對其進(jìn)行消耗,也就是說NestedScrollingParent可以消費部分dx、dy,余下的未消費完的dx、dy交還給子view去消費。
這樣看實際上要實現(xiàn)本次的效果就很簡單了,話不多說,貼代碼。
先讓我們的自定義ScrollView實現(xiàn)NestedScrollingChild接口,并且將NestedScrolling相關(guān)的處理全部交給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;}/*** 設(shè)置ScrollView滑動到邊界監(jiān)聽** @param onScrollLimitListener ScrollView滑動到邊界監(jiān)聽*/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();}/*** 回調(diào)*/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滑動到邊界監(jiān)聽接口*/public interface OnScrollLimitListener {/*** 滑動到頂部*/void onScrollTop();/*** 滑動到頂部和底部之間的位置(既不是頂部也不是底部)*/void onScrollOther();/*** 滑動到底部*/void onScrollBottom();} }然后是我們的PopupLayout,上一篇博文是通過自定義FrameLayout的方式實現(xiàn)的,這次由于是通過NestedScrolling實現(xiàn),所以一次滑動事件其實是針對整個ViewGroup的,所以本次采取自定義LinearLayout的方式去實現(xiàn)。
在這里我們重點看下面幾個方法,首先是onMeasure方法。因為初始狀態(tài)下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。如果是下滑且內(nèi)部scrollview已經(jīng)滑動到頂,則消耗掉dy,即consumed[1]=dy,消耗掉的意思,就是自己去執(zhí)行scrollBy,實際上就是滑動PopupLayout本身。
onStopNestedScroll中,我們判斷向下滑動的距離,來確定是dismiss PopupLayout還是回彈到初始位置。
最后由于需要更新TitleBar的狀態(tài),所以重寫了scrollTo方法,在scrollTo方法中更新TitleBar的狀態(tài)。
@Overridepublic void scrollTo(int x, int y) {if (y >= mDarkViewHeight - UIUtils.getStatusBarHeight(this)) {y = mDarkViewHeight - UIUtils.getStatusBarHeight(this);darkView.setBackgroundColor(Color.WHITE);//拖動到頂部時darkview背景設(shè)置白色titleBar.setBackImageResource(R.mipmap.back);} else {darkView.setBackgroundResource(R.color.dark);//沒有拖動到頂部時darkview背景設(shè)置暗色titleBar.setBackImageResource(R.mipmap.close);}if (y != getScrollY()) {super.scrollTo(x, y);}}本次的要點基本就這么多,總的來說相較上一篇博文各種絞盡腦汁想著事件處理,這次通過NestedScrolling就重寫幾個方法,然后根據(jù)自己的實際需求做一些判斷,實現(xiàn)起來還是很簡單的。
最后附上源碼鏈接:https://github.com/Horrarndoo/PopupLayoutNew
總結(jié)
以上是生活随笔為你收集整理的Android自定义控件:NestedScrolling实现仿魅族flyme6应用市场应用详情弹出式layout的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 学习笔记(4):【数据分析实战训练营】
- 下一篇: unity 自动寻路