一起擼個朋友圈吧(step1) - ListView(中)篇

項目地址: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)于這個問題乏冀,待我查查官方資料蝶糯,以及思考一下,在下篇討論一下辆沦。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末昼捍,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子肢扯,更是在濱河造成了極大的恐慌妒茬,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蔚晨,死亡現(xiàn)場離奇詭異乍钻,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進(jìn)店門团赁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來育拨,“玉大人,你說我怎么就攤上這事欢摄“旧ィ” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵怀挠,是天一觀的道長析蝴。 經(jīng)常有香客問我,道長绿淋,這世上最難降的妖魔是什么闷畸? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮吞滞,結(jié)果婚禮上佑菩,老公的妹妹穿的比我還像新娘。我一直安慰自己裁赠,他們只是感情好殿漠,可當(dāng)我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著佩捞,像睡著了一般绞幌。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上一忱,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天莲蜘,我揣著相機(jī)與錄音,去河邊找鬼帘营。 笑死票渠,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的仪吧。 我是一名探鬼主播庄新,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼鞠眉,長吁一口氣:“原來是場噩夢啊……” “哼薯鼠!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起械蹋,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤出皇,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后哗戈,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體郊艘,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了纱注。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片畏浆。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖狞贱,靈堂內(nèi)的尸體忽然破棺而出刻获,到底是詐尸還是另有隱情,我是刑警寧澤瞎嬉,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布蝎毡,位于F島的核電站,受9級特大地震影響氧枣,放射性物質(zhì)發(fā)生泄漏沐兵。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一便监、第九天 我趴在偏房一處隱蔽的房頂上張望扎谎。 院中可真熱鬧,春花似錦烧董、人聲如沸簿透。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽老充。三九已至,卻和暖如春螟左,著一層夾襖步出監(jiān)牢的瞬間啡浊,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工胶背, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留巷嚣,地道東北人。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓钳吟,卻偏偏與公主長得像廷粒,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子红且,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容