這篇文章適合看了眾多講解下拉刷新、視圖測量與繪制、事件分發(fā)仍然模糊不清的同學(xué),android下拉刷新控件不知從何時起已經(jīng)成為項(xiàng)目標(biāo)配,所以熟悉下拉刷新控件變得尤為重要,本文將從下拉刷新控件入手,順便學(xué)習(xí)下自定義控件和事件分發(fā)機(jī)制。
我們可以點(diǎn)進(jìn)當(dāng)下可拓展性最高、最流行的下拉刷新項(xiàng)目:android-Ultra-Pull-To-Refresh,發(fā)現(xiàn)里面有個核心類PtrFrameLayout,乍一看1368行,不管你暈不暈,反正我是暈的。好了,我們來個簡易版的(Ps:引用NsRefreshLayout項(xiàng)目的代碼來講解,并且都只是偽代碼或部分代碼):
一、定義些可拓展的屬性
像這樣寫在values/attrs.xml里(ps:其實(shí)你寫在strings.xml里都沒問題,xml的名字也可以自己取,只需要保證根標(biāo)簽是declare-styleaqle即可,而且這樣分開寫更方便查找):
<declare-styleable name="PtrFrameLayout">
<attr name="ptr_pull_to_fresh" format="boolean" />
</declare-styleable>
二、獲取到這些屬性
確保在每三個構(gòu)造函數(shù)里都可以拿到這些屬性
public PtrFrameLayout(Context context) {
this(context, null);
}
public PtrFrameLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public PtrFrameLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray arr = context.obtainStyledAttributes(attrs, R.styleable.PtrFrameLayout, 0, 0);
mPullToRefresh = arr.getBoolean(R.styleable.PtrFrameLayout_ptr_pull_to_fresh, mPullToRefresh);
arr.recycle();
}
三、給布局添加頭布局或底布局
在方法onFinishInflate()里以addView的形式添加自定義的頭布局或者底布局,并使用第二部接收的值來填充屬性(比如顏色,字體什么的)。
四、重寫事件分發(fā)
目前主要有2種方式來重寫事件分發(fā)。
- 重寫onInterceptTouchEvent和onTouchEvent的方式
- 重寫onInterceptTouchEvent
public boolean onInterceptTouchEvent(MotionEvent ev) {
case MotionEvent.ACTION_MOVE: {
//判斷是下拉刷新還是上拉加載更多
if (disY > 0 && !canChildScrollUp() && mPullRefreshEnable) {
mCurrentAction = ACTION_PULL_DOWN_REFRESH;
return true;
} else {
return super.onInterceptTouchEvent(ev);
}
}
對touch事件為MOVE類型的進(jìn)行判斷處理,如果滿足攔截條件,進(jìn)行攔截并返回true,如不滿足條件或是類型不是MOVE的其他touch事件,執(zhí)行super.onInterceptTouchEvent(ev),代表不攔截,由系統(tǒng)幫我們向下傳遞,遇到需要消費(fèi)該事件的content(比如listview滑動)消費(fèi)掉就完事了
- 重寫onTouchEvent
public boolean onTouchEvent(MotionEvent event){
case MotionEvent.ACTION_MOVE: {
handleScroll(dy);//處理頭試圖和內(nèi)容視圖的下滑
return true;//return super 或者false都沒事
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
return releaseTouch();//處理釋放操作,這里的返回值同上
}
}
```
原著在move那里返回的是true,其實(shí)無論返回什么都是可以的,因?yàn)橐呀?jīng)重寫了onInterceptTouchEvent,當(dāng)遇到滿足下拉刷新的情況自然會走自己的OnTouchEvent(),如果沒有不滿足條件,由于你是parentView,而且沒有攔截事件,所以子布局優(yōu)先消費(fèi),也跟你無關(guān)了,哈哈
- 重寫dispatchTouchEvent的方式
android-Ultra-Pull-To-Refresh就是用的這種方式,當(dāng)滿足刷新條件時return true表示自己處理了,如果沒滿足條件,執(zhí)行super.dispatchTouchEventSupper(e)繼續(xù)分發(fā)給孩子,個人覺得這種方式干預(yù)了系統(tǒng)的分發(fā)事件,畢竟孩子的所有觸摸事件都是通過這個方法得來的,說沒就沒了,不像onInterceptTouchEvent攔截了還會丟給孩子一個cancel事件,所以這種系統(tǒng)級的事情就要我們自己去寫了,不過癮。。。
五、布局位移
上個部分有個偽代碼handleScroll(),是用來位移孩子的方法,下面統(tǒng)計(jì)下位移視圖的幾種方式:
- layout()
- bringToFront()(需配合requestLayout使用)
- LayoutParams
- padding(設(shè)為負(fù)值就隱藏了)
- transactionY,transactionX
- offsetLeftAndRight() offsetTopAndBottom()
- Scroller
- scrollTo() scrollBy()
以上8種其實(shí)可以分為2類,1234需要requestLayout(),所以當(dāng)頻繁調(diào)用時會卡,5678只是影響drawable部分,頻繁調(diào)用也很順滑。
總結(jié)
如果你想通過看完上面的內(nèi)容,然后自己寫一個完美的下拉刷新控件,我想還是需要很長時間的,畢竟里面還有很多的小細(xì)節(jié)沒有涉及到,我只是幫大家把輪子分解得簡單點(diǎn),對輪子有一個大體的認(rèn)知,小細(xì)節(jié)的地方不明白也不影響大局觀嘛,畢竟寫代碼還是要有一個上帝視角,誰也不能一口吃個大胖子。