歡樂的票圈重構(gòu)之旅——RecyclerView的上下拉以及l(fā)ogo的聯(lián)動

項(xiàng)目重構(gòu)的Git地址:
https://github.com/razerdp/FriendCircle/tree/main-dev
項(xiàng)目同步更新的文集:
http://www.reibang.com/notebooks/3224048/latest

下集預(yù)告:歡樂的票圈重構(gòu)之旅——RecyclerView的頭尾布局增加

前言

在沉寂了五六個(gè)月的時(shí)間后棍矛,終于有空來收拾一下朋友圈項(xiàng)目的殘局了。
這次換了一個(gè)服務(wù)器阴孟,畢竟咱們不是寫后端的耘眨,當(dāng)時(shí)一時(shí)頭腦發(fā)熱暮屡,開了一個(gè)阿里云,其實(shí)是為了畢業(yè)設(shè)計(jì)項(xiàng)目著想毅桃,后來實(shí)在吃不消108軟妹幣每個(gè)月的負(fù)擔(dān)和代碼的維護(hù)褒纲,于是無奈關(guān)掉服務(wù)器。
而現(xiàn)在钥飞,在平衡了一下LearnCloud和Bmob之后莺掠,打算采用Bmob作為我們項(xiàng)目的后端支持。
于是乎读宙,在改造的過程中發(fā)現(xiàn)咱們的朋友圈項(xiàng)目似乎要大改彻秆,改著改著,干脆咬咬牙结闸,全部推倒從來算了(寫過的控件除外)唇兑。

所以,羽翼君又可以來簡書更新一下文章(騙贊了←_←)桦锄。

Rv的上下拉

廢話不多說了扎附,直接進(jìn)入主題。

這次重構(gòu)因?yàn)閷腖istView換成RecyclerView结耀,所以很多東西都要重新部署留夜,比如上下拉。

因?yàn)榕笥讶Φ奶厥庑酝继穑覀兊纳舷吕枰现辽賰蓚€(gè)條件:

  • 下拉刷新可以獲取到偏移量(用來聯(lián)動logo)
  • 下拉刷新時(shí)碍粥,可以隱藏刷新頭部,而只展示我們的logo動畫

對于懶惰的我來說黑毅,首當(dāng)其沖還是找?guī)彀山滥Α!?笫荨U砻妗=Y(jié)果找了一下,瞬間想哭了匪凡,因?yàn)橐瑫r(shí)符合上面兩個(gè)條件的膊畴,似乎還真的找不到。病游。唇跨。有一兩個(gè)比較接近的(比如:IRecyclerView)卻因?yàn)楦鞣N問題導(dǎo)致不能使用。衬衬。买猖。

沒辦法,只好強(qiáng)擼了滋尉。玉控。


先來一根煙壓壓驚

于是乎,在一次提交中狮惜,狂擼Touch事件高诺。碌识。。
commit here

提交部分截圖

寫著寫著虱而,想到了還得做動畫筏餐,還得做返回,還得做各種各樣的事件分發(fā)牡拇。魁瞪。。惠呼。天吶嚕导俘,我還是乖乖去上班吧。剔蹋。旅薄。

這時(shí)候忽然靈機(jī)一閃,想起以前擼ListView時(shí)不是有個(gè)overScroll的嗎滩租,那Rv也應(yīng)會有的赋秀,于是面向谷歌編程的我,雖然找不到比較好的描述律想,但找到了這么一個(gè)庫:
overscroll-decor

初步看了一下代碼猎莲,其核心相當(dāng)于接管了touch事件,通過setTranslationY來進(jìn)行View的移動的技即,而且最重要的是著洼,提供的接口有著狀態(tài)和偏移量的返回!6稹I眢浴!(拍黑板葵陵,這是重點(diǎn)液荸!

有了這兩個(gè)東西,那就可以嘿嘿嘿了脱篙。

控件的布局

首先娇钱,我們確定一下我們的控件應(yīng)該怎么寫。

在微信朋友圈中绊困,以我們的目測文搂,至少有三個(gè)要求(本項(xiàng)目以iOS的交互為標(biāo)準(zhǔn)):

謎一樣的截圖
  • (1) logo要隨著下拉的動作同時(shí)下拉
  • (2) RecyclerView拉下來之后,要露出后面的背景
  • (3) 咱們的logo是跟RecyclerView同級的

所以秤朗,咱們的布局肯定不能繼承RecyclerView然后干煤蹭,而是一個(gè)ViewGroup,這次我選擇了FrameLayout硝皂。

所以咱們的初始化這么寫:


  //構(gòu)造器什么的常挚,忽略啦~都指向于這里

  private void init(Context context) {
        //漸變背景(黑色的背景在上半部分,下半部分是白色的)
        GradientDrawable background = new GradientDrawable(GradientDrawable.Orientation.TOP_BOTTOM, new int[]{0xff323232, 0xff323232, 0xffffffff, 0xffffffff});
        setBackground(background);
        //rv初始化
        if (recyclerView == null) {
            recyclerView = new RecyclerView(context);
            recyclerView.setBackgroundColor(Color.WHITE);
            recyclerView.setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false));
        }
        //logo初始化
        if (refreshIcon == null) {
            refreshIcon = new ImageView(context);
            refreshIcon.setBackgroundColor(Color.TRANSPARENT);
            refreshIcon.setImageResource(R.drawable.rotate_icon);
        }
        FrameLayout.LayoutParams iconParam = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        iconParam.leftMargin = UIHelper.dipToPx(12);

        //add
        addView(recyclerView, RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.MATCH_PARENT);
        addView(refreshIcon, iconParam);

        //觸發(fā)刷新的警戒線
        refreshPosition = UIHelper.dipToPx(90);

        //logo的觀察類
        iconObserver = new InnerRefreshIconObserver(refreshIcon, refreshPosition);

    }

接下來就是咱們的下拉刷新了。前面說過怨酝,咱么用的是overscroll那個(gè)庫傀缩,我們針對的是偏移量,所以我們所有的工作都依賴于這個(gè)偏移:

private void initOverScroll() {
        IOverScrollDecor decor = new VerticalOverScrollBounceEffectDecorator(new RecyclerViewOverScrollDecorAdapter(recyclerView), 2f, 1f, 2f);
        decor.setOverScrollUpdateListener(new IOverScrollUpdateListener() {
            @Override
            public void onOverScrollUpdate(IOverScrollDecor decor, int state, float offset) {
                if (offset > 0) {
                    //正在刷新就不鳥它
                    if (currentStatus == REFRESHING) return;
                    //更新logo的位置
                    iconObserver.catchPullEvent(offset);
                    if (offset >= refreshPosition && state == STATE_BOUNCE_BACK) {
                        //state變成返回時(shí)农猬,意味著已經(jīng)松手了赡艰,則進(jìn)行刷新邏輯
                        if (currentStatus != REFRESHING) {
                            setCurrentStatus(REFRESHING);
                            if (onRefreshListener != null) {
                                Log.i(TAG, "refresh");
                                onRefreshListener.onRefresh();
                            }
                            iconObserver.catchRefreshEvent();
                        }
                    }
                } else if (offset < 0) {
                    //底部的overscroll
                }
            }
        });
    }

代碼不多,因?yàn)槎嗟臇|西都在庫里面干完了斤葱。慷垮。。

在調(diào)用了setAdapter之后揍堕,我們執(zhí)行這個(gè)初始化方法料身,從回調(diào)的接口處,不難看到offset的回調(diào)有兩種衩茸,分別是大于0和小于0芹血,其中大于0是從頂部下拉(下拉刷新),而小于0則是從底部上拉(上拉加載)楞慈。

但是幔烛,有一個(gè)問題是,我們沒有辦法知道松手的觸發(fā)囊蓝,也就是相當(dāng)于touch的up事件饿悬。不過幸好,接口同時(shí)還返回了狀態(tài)聚霜,當(dāng)狀態(tài)發(fā)生改變的時(shí)候狡恬,就肯定是手勢發(fā)生了變化,通過狀態(tài)俯萎,我們就相當(dāng)于捕捉到了up事件傲宜。所以就有了以上的代碼。

因?yàn)榕笥讶Σ⒉恍枰侠虞d夫啊,而是滑動到底部自動加載更多函卒,所以這offset<0的地方我就沒有做任何邏輯了,如果有需求的話,也是可以做到上拉加載更多的报嵌。

做完上下拉的邏輯之后虱咧,接下來就是logo的聯(lián)動。

從代碼上來看锚国,我把所有的邏輯都封到了iconObserver里面了(其實(shí)我覺得起名叫iconHelper可能更好腕巡,但就是覺得Observer高大上一點(diǎn)←_←)。

在observer里面血筑,我們主要做的東西都是跟UI有關(guān)的绘沉。代碼比較簡單,所有就把解釋寫到代碼里面了

   /**
     * 刷新Icon的動作觀察者
     */

    private static class InnerRefreshIconObserver {
        private ImageView refreshIcon;
        private final int refreshPosition;
        private float lastOffset = 0.0f;
        private RotateAnimation rotateAnimation;
        private ValueAnimator mValueAnimator;

        public InnerRefreshIconObserver(ImageView refreshIcon, int refreshPosition) {
            this.refreshIcon = refreshIcon;
            this.refreshPosition = refreshPosition;

            rotateAnimation = new RotateAnimation(0, 360, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
            rotateAnimation.setDuration(600);
            rotateAnimation.setInterpolator(new LinearInterpolator());
            rotateAnimation.setRepeatCount(Animation.INFINITE);

        }

        public void catchPullEvent(float offset) {
            if (checkHacIcon()) {
                refreshIcon.setRotation(offset * 2);
                if (offset >= refreshPosition) {
                    offset = refreshPosition;
                }
                int resultOffset = (int) (offset - lastOffset);
                refreshIcon.offsetTopAndBottom(resultOffset);
                Log.d(TAG, "pull  >>  " + offset + "  resultOffset   >>>   " + resultOffset);
                adjustRefreshIconPosition();
                lastOffset = offset;
            }

        }

        /**
         * 調(diào)整icon的位置界限
         */
        private void adjustRefreshIconPosition() {
            if (refreshIcon.getTop() < 0) {
                refreshIcon.offsetTopAndBottom(Math.abs(refreshIcon.getTop()));
            } else if (refreshIcon.getTop() > refreshPosition) {
                refreshIcon.offsetTopAndBottom(-(refreshIcon.getTop() - refreshPosition));
            }
        }

        public void catchRefreshEvent() {
            if (checkHacIcon()) {
                refreshIcon.clearAnimation();
                refreshIcon.startAnimation(rotateAnimation);
            }
        }

        public void catchResetEvent() {
            refreshIcon.clearAnimation();
            if (mValueAnimator == null) {
                mValueAnimator = ValueAnimator.ofFloat(refreshPosition, 0);
                mValueAnimator.setInterpolator(new DecelerateInterpolator());
                mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator animation) {
                        float result = (float) animation.getAnimatedValue();
                        catchPullEvent(result);
                    }
                });
                mValueAnimator.setDuration(300);
            }
            mValueAnimator.start();
        }

        private boolean checkHacIcon() {
            return refreshIcon != null;
        }
    }

最后是demo動圖:

demo

本篇比較簡單豺总,算是一個(gè)開始吧车伞,接下來的重構(gòu)咱么就愉快地進(jìn)行吧-V-

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市喻喳,隨后出現(xiàn)的幾起案子另玖,更是在濱河造成了極大的恐慌增蹭,老刑警劉巖芽卿,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件限嫌,死亡現(xiàn)場離奇詭異因苹,居然都是意外死亡晤硕,警方通過查閱死者的電腦和手機(jī)王悍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進(jìn)店門泰佳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來萎胰,“玉大人翔怎,你說我怎么就攤上這事窃诉。” “怎么了赤套?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵飘痛,是天一觀的道長。 經(jīng)常有香客問我容握,道長宣脉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任剔氏,我火速辦了婚禮塑猖,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘谈跛。我一直安慰自己羊苟,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布感憾。 她就那樣靜靜地躺著蜡励,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上凉倚,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天兼都,我揣著相機(jī)與錄音,去河邊找鬼稽寒。 笑死扮碧,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的杏糙。 我是一名探鬼主播慎王,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼宏侍!你這毒婦竟也來了柬祠?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤负芋,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后嗜愈,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體旧蛾,經(jīng)...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年蠕嫁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了锨天。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,505評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡剃毒,死狀恐怖病袄,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情赘阀,我是刑警寧澤益缠,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站基公,受9級特大地震影響幅慌,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜轰豆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一胰伍、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧酸休,春花似錦骂租、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春抽米,著一層夾襖步出監(jiān)牢的瞬間特占,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工云茸, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留是目,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓标捺,卻偏偏與公主長得像懊纳,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子亡容,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,515評論 2 359

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

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,288評論 25 707
  • 內(nèi)容抽屜菜單ListViewWebViewSwitchButton按鈕點(diǎn)贊按鈕進(jìn)度條TabLayout圖標(biāo)下拉刷新...
    皇小弟閱讀 46,791評論 22 665
  • 最終還是趕在今天晚上去看了《百鳥朝鳳》闺兢,只因?yàn)楹ε旅魈煸倏淳鸵呀?jīng)沒有排片茂缚。可能有些心靈的震顫是說不清道不明的...
    pheobe小九閱讀 639評論 0 0
  • (一) 秦青的尸體是在她死后的第二天被發(fā)現(xiàn)的屋谭,早上6點(diǎn)晨練的大叔到樹林里方便脚囊,大早上,一個(gè)女青年獨(dú)坐在樹林桐磁,任誰都...
    心向大海閱讀 789評論 1 2
  • 2016年最后一天,你過的好嗎校摩?今天你都有做些什么看峻?是像我一樣,繼續(xù)16年最后一天的工作衙吩;還是在享受16年最后一天...
    流涓閱讀 238評論 0 0