歡樂的票圈重構之旅——RecyclerView的上下拉以及logo的聯動

項目重構的Git地址:https://github.com/razerdp/FriendCirclejava

下集預告:歡樂的票圈重構之旅——RecyclerView的頭尾佈局增長git

前言

在沉寂了五六個月的時間後,終於有空來收拾一下朋友圈項目的殘局了。 此次換了一個服務器,畢竟我們不是寫後端的,當時一時頭腦發熱,開了一個阿里雲,實際上是爲了畢業設計項目着想,後來實在吃不消108軟妹幣每月的負擔和代碼的維護,因而無奈關掉服務器。 而如今,在平衡了一下LearnCloud和Bmob以後,打算採用Bmob做爲咱們項目的後端支持。 因而乎,在改造的過程當中發現我們的朋友圈項目彷佛要大改,改着改着,乾脆咬咬牙,所有推倒歷來算了(寫過的控件除外)。github

因此,羽翼君又能夠來簡書更新一下文章(騙讚了←_←)。編程

Rv的上下拉

廢話很少說了,直接進入主題。後端

此次重構由於將會從ListView換成RecyclerView,因此不少東西都要從新部署,好比上下拉。服務器

由於朋友圈的特殊性,咱們的上下拉須要符合至少兩個條件:微信

  • 下拉刷新能夠獲取到偏移量(用來聯動logo)
  • 下拉刷新時,能夠隱藏刷新頭部,而只展現咱們的logo動畫

對於懶惰的我來講,首當其衝仍是找庫吧。。。。結果找了一下,瞬間想哭了,由於要同時符合上面兩個條件的,彷佛還真的找不到。。。有一兩個比較接近的(好比:IRecyclerView)卻由於各類問題致使不能使用。。。ide

沒辦法,只好強擼了。。 佈局

先來一根菸壓壓驚

因而乎,在一次提交中,狂擼Touch事件。。。 commit here動畫

提交部分截圖

寫着寫着,想到了還得作動畫,還得作返回,還得作各類各樣的事件分發。。。。天吶嚕,我仍是乖乖去上班吧。。。

這時候突然靈機一閃,想起之前擼ListView時不是有個overScroll的嗎,那Rv也應會有的,因而面向谷歌編程的我,雖然找不到比較好的描述,但找到了這麼一個庫: overscroll-decor

初步看了一下代碼,其核心至關於接管了touch事件,經過setTranslationY來進行View的移動的,並且最重要的是,提供的接口有着狀態和偏移量的返回!!!!(拍黑板,這是重點!

有了這兩個東西,那就能夠嘿嘿嘿了。

控件的佈局

首先,咱們肯定一下咱們的控件應該怎麼寫。

在微信朋友圈中,以咱們的目測,至少有三個要求(本項目以iOS的交互爲標準):

謎同樣的截圖

  • (1) logo要隨着下拉的動做同時下拉
  • (2) RecyclerView拉下來以後,要露出後面的背景
  • (3) 我們的logo是跟RecyclerView同級的

因此,我們的佈局確定不能繼承RecyclerView而後幹,而是一個ViewGroup,此次我選擇了FrameLayout。

因此我們的初始化這麼寫:

//構造器什麼的,忽略啦~都指向於這裏

  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);

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

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

    }
複製代碼

接下來就是我們的下拉刷新了。前面說過,咱麼用的是overscroll那個庫,咱們針對的是偏移量,因此咱們全部的工做都依賴於這個偏移:

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變成返回時,意味着已經鬆手了,則進行刷新邏輯
                        if (currentStatus != REFRESHING) {
                            setCurrentStatus(REFRESHING);
                            if (onRefreshListener != null) {
                                Log.i(TAG, "refresh");
                                onRefreshListener.onRefresh();
                            }
                            iconObserver.catchRefreshEvent();
                        }
                    }
                } else if (offset < 0) {
                    //底部的overscroll
                }
            }
        });
    }

複製代碼

代碼很少,由於多的東西都在庫裏面幹完了。。。

在調用了setAdapter以後,咱們執行這個初始化方法,從回調的接口處,不難看到offset的回調有兩種,分別是大於0和小於0,其中大於0是從頂部下拉(下拉刷新),而小於0則是從底部上拉(上拉加載)。

可是,有一個問題是,咱們沒有辦法知道鬆手的觸發,也就是至關於touch的up事件。不過幸虧,接口同時還返回了狀態,當狀態發生改變的時候,就確定是手勢發生了變化,經過狀態,咱們就至關於捕捉到了up事件。因此就有了以上的代碼。

由於朋友圈並不須要上拉加載,而是滑動到底部自動加載更多,因此這offset<0的地方我就沒有作任何邏輯了,若是有需求的話,也是能夠作到上拉加載更多的。

作完上下拉的邏輯以後,接下來就是logo的聯動。

從代碼上來看,我把全部的邏輯都封到了iconObserver裏面了(其實我以爲起名叫iconHelper可能更好,但就是以爲Observer高大上一點←_←)。

在observer裏面,咱們主要作的東西都是跟UI有關的。代碼比較簡單,全部就把解釋寫到代碼裏面了

/** * 刷新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;
            }

        }

        /** * 調整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

本篇比較簡單,算是一個開始吧,接下來的重構咱麼就愉快地進行吧-V-

相關文章
相關標籤/搜索