項目地址:https://github.com/razerdp/FriendCircle
一起擼個朋友圈吧這是本文所處文集,所有更新都會在這個文集里面哦箍铭,歡迎關(guān)注
上篇鏈接:http://www.reibang.com/p/7fa237cfddbb
下篇鏈接:http://www.reibang.com/p/68e13214cde4
上一篇我們初步弄出了一個Header萤悴,雖然這個header實現(xiàn)的僅僅是弄了一個灰色的圖層访圃,但我們需要的是它的回調(diào)理郑。
這一篇肴甸,我們針對框架封裝一個listview出來寂殉。
這里簡要說說android-Ultra-Pull-To-Refresh這個框架,這個框架繼承viewgroup原在,其實現(xiàn)原理是只能夠add2個view友扰,一個作為header,一個作為content庶柿,事件分發(fā)在dispatchTouchEvent處理村怪,由于繼承的viewgroup,所以理論上來說可以添加任何view來實現(xiàn)下拉刷新浮庐。
那我們目的就很明確甚负,要將這個框架弄成一個listview(起碼讓使用的人看起來就是一個listview),我們就要按照listview的風(fēng)格去弄這個控件审残,首先當(dāng)然是定義我們的attrs梭域,我們的attrs屬性直接拉官方的包,在as中切換到project標(biāo)簽搅轿,依次打開<android api platform> ->res->values->attrs.xml病涨,然后ctrl+f找到abslistview和listview,把你覺得常用的都拉到我們自己新建的attrs.xml里面璧坟。
經(jīng)過篩選没宾,初步提取出以下屬性:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="FriendCirclePtrListView">
<!--abslistview start-->
<!--=====================================-->
<attr name="listSelector" format="color|reference" />
<attr name="transcriptMode">
<enum name="disabled" value="0"/>
<enum name="normal" value="1" />
<enum name="alwaysScroll" value="2" />
</attr>
<attr name="cacheColorHint" format="color" />
<attr name="fastScrollEnabled" format="boolean" />
<attr name="fastScrollStyle" format="reference" />
<attr name="smoothScrollbar" format="boolean" />
<attr name="choiceMode">
<!-- Normal list that does not indicate choices. -->
<enum name="none" value="0" />
<!-- The list allows up to one choice. -->
<enum name="singleChoice" value="1" />
<!-- The list allows multiple choices. -->
<enum name="multipleChoice" value="2" />
<!-- The list allows multiple choices in a custom selection mode. -->
<enum name="multipleChoiceModal" value="3" />
</attr>
<!--=====================================-->
<!--abslistview end-->
<!--=====================================-->
<!--listview start-->
<attr name="listview_divider" format="reference|color" />
<attr name="dividerHeight" format="dimension" />
<attr name="overScrollHeader" format="reference|color" />
<attr name="overScrollFooter" format="reference|color" />
</declare-styleable>
</resources>
然后在我們的構(gòu)造器中直接拉官方源碼:
private void initAttrs(Context context, AttributeSet attrs) {
final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.FriendCirclePtrListView);
final Drawable selector = a.getDrawable(R.styleable.FriendCirclePtrListView_listSelector);
if (selector != null) {
mListView.setSelector(selector);
}
mListView.setTranscriptMode(a.getInt(R.styleable.FriendCirclePtrListView_transcriptMode, 0));
mListView.setCacheColorHint(a.getColor(R.styleable.FriendCirclePtrListView_cacheColorHint, 0));
mListView.setFastScrollEnabled(a.getBoolean(R.styleable.FriendCirclePtrListView_fastScrollEnabled, false));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mListView.setFastScrollStyle(a.getResourceId(R.styleable.FriendCirclePtrListView_fastScrollStyle, 0));
}
mListView.setSmoothScrollbarEnabled(a.getBoolean(R.styleable.FriendCirclePtrListView_smoothScrollbar, true));
mListView.setChoiceMode(a.getInt(R.styleable.FriendCirclePtrListView_choiceMode, 0));
final Drawable d = a.getDrawable(R.styleable.FriendCirclePtrListView_listview_divider);
if (d != null) {
// Use an implicit divider height which may be explicitly
// overridden by android:dividerHeight further down.
mListView.setDivider(d);
}
// Use an explicit divider height, if specified.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
if (a.hasValueOrEmpty(R.styleable.FriendCirclePtrListView_dividerHeight)) {
final int dividerHeight = a.getDimensionPixelSize(R.styleable.FriendCirclePtrListView_dividerHeight, 0);
if (dividerHeight != 0) {
mListView.setDividerHeight(dividerHeight);
}
}
}
else {
final int dividerHeight = a.getDimensionPixelSize(R.styleable.FriendCirclePtrListView_dividerHeight, 0);
if (dividerHeight != 0) {
mListView.setDividerHeight(dividerHeight);
}
}
final Drawable osHeader = a.getDrawable(R.styleable.FriendCirclePtrListView_overScrollHeader);
if (osHeader != null) {
mListView.setOverscrollHeader(osHeader);
}
final Drawable osFooter = a.getDrawable(R.styleable.FriendCirclePtrListView_overScrollFooter);
if (osFooter != null) {
mListView.setOverscrollFooter(osFooter);
}
a.recycle();
}
值得注意的是dividerheight這個屬性,需要區(qū)分一下SDK版本沸柔,另外我的divider這個屬性不知道為什么會提示重復(fù)屬性循衰,于是我只好改了一下名字改為listview_divider
初始化中進(jìn)行各種各樣的框架屬性定義,代碼如下:
private void initView(Context context) {
//header
mHeader = new FriendCirclePtrHeader(context);
//listview
mListView = new ListView(context);
mListView.setSelector(android.R.color.transparent);
mListView.setLayoutParams(
new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
//footer
mFooter = new FriendCirclePtrFooter(context);
//view add
setHeaderView(mHeader);
addView(mListView);
//ptr option
addPtrUIHandler(mHeader.getPtrUIHandler());
setPtrHandler(this);
setResistance(2.3f);
setRatioOfHeaderHeightToRefresh(.25f);
setDurationToClose(200);
setDurationToCloseHeader(1000);
//刷新時的固定的偏移量
setOffsetToKeepHeaderWhileLoading(0);
//下拉刷新褐澎,即下拉到距離就刷新而不是松開刷新
setPullToRefresh(false);
//刷新的時候保持頭部会钝?
setKeepHeaderWhenRefresh(false);
setScrollListener();
}
我們在控件中new一個listview,作為content,然后new一個header迁酸,就是上一篇的那個header先鱼,作為我們的header,接著footer備用奸鬓,用于滑到底部自動加載時顯示用的焙畔,這里沒有什么技術(shù)含量,在setScrollListener()串远,我們對listview進(jìn)行滑動監(jiān)聽宏多,當(dāng)滑動到底部的時候,進(jìn)行加載更多的操作(本篇暫未實現(xiàn))
int lastItem = 0;
private void setScrollListener() {
mListView.setOnScrollListener(new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (mOnLoadMoreRefreshListener != null) {
if (SCROLL_STATE_IDLE == scrollState &&
0 != mListView.getFirstVisiblePosition() && lastItem == mListView.getCount()) {
if (hasMore && loadmoreState != PullStatus.REFRESHING) {
// TODO: 2016/2/10 待完成
//當(dāng)有更多同時當(dāng)前加載更多布局不再刷新狀態(tài)澡罚,則執(zhí)行刷新
}
}
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
lastItem = firstVisibleItem + visibleItemCount;
}
});
}
那么伸但,現(xiàn)在listview有了,滑動監(jiān)聽也有了留搔,我們該如何實現(xiàn)下拉刷新的監(jiān)聽呢更胖,在框架中有這么一個接口PtrHandler,這個接口需要我們實現(xiàn)兩個回調(diào):
public interface PtrHandler {
/**
* Check can do refresh or not. For example the content is empty or the first child is in view.
* <p/>
* {@link in.srain.cube.views.ptr.PtrDefaultHandler#checkContentCanBePulledDown}
*/
public boolean checkCanDoRefresh(final PtrFrameLayout frame, final View content, final View header);
/**
* When refresh begin
*
* @param frame
*/
public void onRefreshBegin(final PtrFrameLayout frame);
}
根據(jù)官方文檔隔显,第一個回調(diào)是我們決定能否下拉却妨,通常返回官方自帶的判斷工具類就可以了,第二個就是刷新回調(diào)了括眠。
為了方便控制彪标,我們在控件里定義兩個枚舉:
- 當(dāng)前模式:下拉刷新、上拉加載
- 當(dāng)前狀態(tài):普通(無狀態(tài))哺窄、正在刷新
定義這兩個狀態(tài)的目的是為了方便我們以后擴(kuò)展的時候用捐下,比如如果當(dāng)前狀態(tài)是正在刷新账锹,我們就禁用掉下拉功能什么的萌业。。奸柬。生年。
public enum PullStatus {
NORMAL,REFRESHING
}
public enum PullMode {
FROM_START,FROM_BOTTOM
}
同時,我們定義兩個接口廓奕,這兩個接口用于外部回調(diào)抱婉,方便控制狀態(tài):
/**
* Created by 大燈泡 on 2016/2/9.
* 下拉刷新接口
*/
public interface OnPullDownRefreshListener {
void onRefreshing(PtrFrameLayout frame);
}
/**
* Created by 大燈泡 on 2016/2/9.
* 加載更多接口
*/
public interface OnLoadMoreRefreshListener {
void onRefreshing();
}
接下來在我們的框架回調(diào)中執(zhí)行下面步驟:
@Override
public void onRefreshBegin(PtrFrameLayout frame) {
curMode = PullMode.FROM_START;
loadmoreState = PullStatus.NORMAL;
if (mOnPullDownRefreshListener != null) mOnPullDownRefreshListener.onRefreshing(frame);
}
根據(jù)官方文檔,官方并未提供上拉加載更多的接口桌粉,也就是說這個回調(diào)必定是下拉刷新的回調(diào)蒸绩,所以我們的模式指定為from_start,loadmoreState(加載更多狀態(tài))則是normal铃肯,另外還有一個pullState患亿,這個是下拉狀態(tài),該狀態(tài)由header對應(yīng)ui接口回調(diào)控制。(詳情看上篇)
做完這一系列的操作后步藕,我們的下拉刷新基本完成了惦界,但是還有一個很重要的東東,就是刷新的icon咙冗,但是這個icon我們的listview不負(fù)責(zé)控制沾歪,控制在header里面(詳情看上篇),listview僅用于傳值雾消。
在中篇最后讓我們分析一下:
到目前為止:
- 我們寫了一個header灾搏,一個listview(繼承PtrFrameLayout)
- 其中:
- header有兩個作用,一個是控制自身下拉的展示仪或,另一個是控制刷新icon的展示
- listview則是繼承框架确镊,其作用是做刷新相關(guān)操作以及暴露listview接口,讓外界看起來像是一個listview
寫到這里我思考到一個問題:刷新icon范删,listview蕾域,header這三者的耦合度是不是有點太高了
另外,關(guān)于icon使用margintop來更新是否會重復(fù)導(dǎo)致measure和layout的問題到旦,在我的測試打印日志里面沒有發(fā)生旨巷。
//更正:、
另外添忘,關(guān)于icon使用margintop來更新是否會重復(fù)導(dǎo)致measure和layout的問題采呐,在我的測試打印日志里面沒有發(fā)生。
這個有誤搁骑,在setAdapter后發(fā)現(xiàn)采用relativelayout的話在不斷的改變margin時會導(dǎo)致多次測量(如果布局復(fù)雜斧吐,將會導(dǎo)致測量時間較長,在視覺上表現(xiàn)為掉幀)仲器,現(xiàn)改正布局根節(jié)點為FrameLayout煤率,多次測量消失。
//更正結(jié)束
關(guān)于這個問題乏冀,待我查查官方資料蝶糯,以及思考一下,在下篇討論一下辆沦。