擼起袖子自己寫一個Android通用的刷新控件

項目中我們經常有上拉阔馋、下拉刷新的需求,幾乎所有的listView娇掏、RecyclerView都會伴隨著上拉呕寝、下拉刷新的需求,如果我們使用一些開源控件婴梧,換了控件我們就要更新下梢,現(xiàn)在我們自己擼起袖子寫一個通用的刷新控件

思路:

寫一個繼承RelativeLayout的RefreshLayout
添加頭尾控件作為刷新控件
通過事件分發(fā)來進行刷新操作
通過動畫來控制控件移動
目的:讓他的所有子控件都可以使用,哪怕是一個TextView

public class RefreshLayout extends RelativeLayout {

/**
 * 滑動控件時拉去的速度比例
 */
private final int V_REFRESH = 2;
/**
 * 是否是刷新過程
 * true 是
 * false 不是
 * 為false的時候才可以進行刷新
 */
private boolean mIsRefreshDuring;
/**
 * 可以進下拉刷新
 */
private boolean mCanDownPull;
/**
 * 可以進行上拉刷新
 */
private boolean mCanUpPull;
/**
 * 判斷觸摸后是否是初次移動
 */
private boolean mIsFirstMove;
/**
 * y軸呢平移的距離
 */
private int mDistanceY;
/**
 * 刷新接口對象
 */
private OnRefresh mOnRefresh;
/**
 * 用于控制事件攔截的變量
 */
private boolean mCanIntercept;
private int mTouchSlop;
private int mDistance;
private LayoutParams mHeaderParams;
private View mHeaderView;
private View mFootView;
private int mHeaderMaxHeight;
private int mStartY;
private LayoutParams mFootParams;
private int mFootMaxHeight;
private PullCallBack mCallBack;
private View mChildView;
private ObjectAnimator mAnimator;

public RefreshLayout(Context context) {
    super(context);
    initData();
}

public RefreshLayout(Context context, AttributeSet attrs) {
    super(context, attrs);
    initData();
}

public RefreshLayout(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    initData();
}

/**
 * 必須讓頭尾控件實現(xiàn)的接口
 */
public interface HeadAndFootCallBack {
    //設置屬性
    void setAttribute();

    //開始刷新
    void startPull();

    //停止刷新
    void stopPull();
}

/**
 * 必須讓被拖動的控件子類實現(xiàn)
 */
public interface PullCallBack {
    boolean canDownPull();

    boolean canUpPull();
}

private void initData() {
    //不調用該方法不能進行繪制
    setWillNotDraw(false);
}

/**
 * 下拉刷新完成后必須使用該方法
 */
public void downPullFinish() {
    mAnimator.setFloatValues(mChildView.getTranslationY(), 0);
    mAnimator.start();
    ((HeadAndFootCallBack) mHeaderView).stopPull();
}

/**
 * 上拉完成后必須調用該方法
 */
public void upPullFinish() {
    mAnimator.setFloatValues(mChildView.getTranslationY(), 0);
    mAnimator.start();
    ((HeadAndFootCallBack) mFootView).stopPull();
}

/**
 * 自動下拉刷新
 */
public void autoDownPullForHead() {
    postDelayed(new Runnable() {
        @Override
        public void run() {
            mCanDownPull = true;
            mCanUpPull = false;
            mAnimator.setFloatValues(10, mHeaderMaxHeight);
            mAnimator.start();
            ((HeadAndFootCallBack) mHeaderView).startPull();
            mOnRefresh.onDownPullRefresh();
        }
    }, 500);
}

/**
 * 自動下拉刷新
 */
public void autoUpPullForHead() {
    postDelayed(new Runnable() {
        @Override
        public void run() {
            mCanDownPull = false;
            mCanUpPull = true;
            mAnimator.setFloatValues(0, mFootMaxHeight);
            mAnimator.start();
            ((HeadAndFootCallBack) mFootView).startPull();
            mOnRefresh.onUpPullRefresh();
        }
    }, 500);
}

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    return mCanIntercept;
}

@Override
public boolean onTouchEvent(MotionEvent event) {
    return true;
}

@Override
public boolean dispatchTouchEvent(MotionEvent event) {
    Log.e("shen", "mIsRefreshDuring=" + mIsRefreshDuring);
    if (mIsRefreshDuring)/*如果正在進行刷新將不會獲取MotionEvent*/ {
        return super.dispatchTouchEvent(event);
    }
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            mStartY = (int) event.getY();
            initPull();
            break;
        case MotionEvent.ACTION_MOVE:
            if (event.getPointerCount() == 1) {
                int moveY = (int) event.getY();
                mDistanceY = (moveY - mStartY) / V_REFRESH;
                if (!mIsFirstMove && mDistanceY != 0 && mDistanceY < mTouchSlop) {
                    mCanDownPull = mDistanceY > 0;
                    mCanUpPull = !mCanDownPull;
                    mIsFirstMove = true;
                }
                if (mCanDownPull && mCallBack.canDownPull()) {
                    upDataForDownPull();//下拉刷新
                    mChildView.setEnabled(false);
                    mCanIntercept = true;
                }
                if (mCanUpPull && mCallBack.canUpPull()) {
                    upDataForUpPull();//上拉加載
                    mChildView.setEnabled(false);
                    mCanIntercept = true;
                }
                mStartY = moveY;
            }
            break;
        case MotionEvent.ACTION_UP:
            mIsRefreshDuring = true;
            mIsFirstMove = false;
            if (mHeaderParams.height >= mHeaderMaxHeight)/*可以下拉刷新*/ {
                ((HeadAndFootCallBack) mHeaderView).startPull();
                mOnRefresh.onDownPullRefresh();
            } else if (mFootParams.height >= mFootMaxHeight)/*可以上拉刷新*/ {
                ((HeadAndFootCallBack) mFootView).startPull();
                mOnRefresh.onUpPullRefresh();
            } else if (mHeaderParams.height > 0 && mHeaderParams.height < mHeaderMaxHeight)/*不能進行下拉刷新塞蹭,收回*/ {
                releaseForDownFinished();
            } else if (mFootParams.height > 0 && mFootParams.height < mFootMaxHeight)/*不能進行下拉刷新孽江,收回*/ {
                releaseForUpFinished();
            } else {
                mIsRefreshDuring = false;
                mCanIntercept = false;
            }
            break;
    }
    super.dispatchTouchEvent(event);
    return true;
}

/**
 * 每次進行觸摸都需要進行初始化
 */
private void initPull() {
    mCanDownPull = false;
    mCanUpPull = false;
}

/**
 * 不需要進行上拉刷新
 */
private void releaseForUpFinished() {
    mAnimator.setFloatValues(mChildView.getTranslationY(), 0);
    mAnimator.start();
}

/**
 * 不需要進行下拉刷新
 */
private void releaseForDownFinished() {
    mAnimator.setFloatValues(mChildView.getTranslationY(), 0);
    mAnimator.start();
}

/**
 * 上拉時處理手勢
 */
private void upDataForUpPull() {
    if (mDistanceY != 0) {
        mFootParams.height -= mDistanceY;
        if (mFootParams.height <= 0) {
            mFootParams.height = 0;
        }
        if (mFootParams.height >= mFootMaxHeight) {
            mFootParams.height = mFootMaxHeight;
        }
        mChildView.setTranslationY(-mFootParams.height);
        mFootView.requestLayout();
    }
}

/**
 * 下拉時處理手勢
 */
private void upDataForDownPull() {
    if (mDistanceY != 0) {
        mHeaderParams.height += mDistanceY;
        if (mHeaderParams.height >= mHeaderMaxHeight) { //最大
            mHeaderParams.height = mHeaderMaxHeight;
        }
        if (mHeaderParams.height <= 0) { //最小
            mHeaderParams.height = 0;
        }
        mChildView.setTranslationY(mHeaderParams.height);
        mHeaderView.requestLayout();
    }
}

@Override
protected void onAttachedToWindow() {
    super.onAttachedToWindow();
}

@Override
protected void onFinishInflate() {
    super.onFinishInflate();
    //加載頭
    mHeaderView = getChildAt(0);
    if (!(mHeaderView instanceof HeadAndFootCallBack)) {
        new IllegalStateException("HeaderView必須實現(xiàn)HeadAndFootCallBack接口");
    }
    ((HeadAndFootCallBack) mHeaderView).setAttribute();
    mHeaderParams = (LayoutParams) mHeaderView.getLayoutParams();
    mHeaderParams.addRule(RelativeLayout.ALIGN_PARENT_TOP);

    //加載尾
    mFootView = getChildAt(2);
    if (!(mFootView instanceof HeadAndFootCallBack)) {
        new IllegalStateException("FootView必須實現(xiàn)HeadAndFootCallBack接口");
    }
    ((HeadAndFootCallBack) mFootView).setAttribute();
    mFootParams = (LayoutParams) mFootView.getLayoutParams();
    mFootParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);

    mChildView = getChildAt(1);
    if (!(mChildView instanceof HeadAndFootCallBack)) {
        new IllegalStateException("ChildView必須實現(xiàn)PullCallBack接口");
    }
    mCallBack = (PullCallBack) getChildAt(1);

    //設置動畫
    mAnimator = ObjectAnimator.ofFloat(mChildView, "translationY", 0);
    mAnimator.setInterpolator(new DecelerateInterpolator());
    mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            int translationY = (int) mChildView.getTranslationY();
            if (mCanUpPull) { //從移動到的位置往下滑
                mFootParams.height = Math.abs(translationY);
                mFootView.requestLayout();
            } else if (mCanDownPull) {
                mHeaderParams.height = Math.abs(translationY);
                mHeaderView.requestLayout();
            }
            Log.e("shen", "translationY=" + translationY);
            Log.e("shen", "mHeaderParams.height=" + mHeaderParams.height);
            if (translationY == 0) {
                mChildView.setEnabled(true);
                mDistanceY = 0; //重置
                mIsRefreshDuring = false; //重置
                mCanIntercept = false;
            } else {
                mIsRefreshDuring = true;
            }
        }
    });
}

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
    mDistance = mTouchSlop * 5;
    //設置下拉頭初始屬性
    mHeaderMaxHeight = mHeaderParams.height;
    mHeaderParams.height = 0;
    mHeaderView.requestLayout();
    //設置上拉尾初始屬性
    mFootMaxHeight = mFootParams.height;
    mFootParams.height = 0;
    mFootView.requestLayout();
}

/**
 * 下拉/上拉事件監(jiān)聽
 */
public interface OnRefresh {
    /**
     * 下拉刷新
     */
    void onDownPullRefresh();

    /**
     * 上拉加載
     */
    void onUpPullRefresh();
}

public void setOnRefresh(OnRefresh onRefresh) {
    mOnRefresh = onRefresh;
}

給他添加三個控件,頭尾就是刷新頭番电、尾岗屏,第二個就是正常顯示的控件。必須讓頭尾實現(xiàn)HeadAndFootCallBack接口漱办,來設置屬性这刷,通知開始刷新、結束刷新

難點: 現(xiàn)在來說下開發(fā)時遇到的難點

由于判斷在dispatchTouchEvent中洼冻,導致如果該控件以及子控件都不消費該事件的話崭歧,就會造成事件不會發(fā)送到它,因為如果不消費DOWN事件的話撞牢,之后所有的事件都不會在進行接收。解決方式叔营,讓該控件onTouchEvent方法消返回true屋彪,當子控件不進行事件消費的話,就會返回由該控件消費绒尊,不會造成因DOWN事件不消費而無法接收到事件畜挥,導致dispatchTouchEvent也不消費事件
動畫,動畫就是我的傷痛婴谱,最近在學習估值器
這個控件自認為寫的不錯蟹但,通過他可以幫我們學習事件分發(fā)、動畫谭羔、接口回調华糖,也是有一定的學習意義

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市瘟裸,隨后出現(xiàn)的幾起案子客叉,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,576評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件兼搏,死亡現(xiàn)場離奇詭異卵慰,居然都是意外死亡,警方通過查閱死者的電腦和手機佛呻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評論 3 399
  • 文/潘曉璐 我一進店門裳朋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人吓著,你說我怎么就攤上這事鲤嫡。” “怎么了夜矗?”我有些...
    開封第一講書人閱讀 168,017評論 0 360
  • 文/不壞的土叔 我叫張陵泛范,是天一觀的道長。 經常有香客問我紊撕,道長罢荡,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,626評論 1 296
  • 正文 為了忘掉前任对扶,我火速辦了婚禮区赵,結果婚禮上,老公的妹妹穿的比我還像新娘浪南。我一直安慰自己笼才,他們只是感情好,可當我...
    茶點故事閱讀 68,625評論 6 397
  • 文/花漫 我一把揭開白布络凿。 她就那樣靜靜地躺著骡送,像睡著了一般。 火紅的嫁衣襯著肌膚如雪絮记。 梳的紋絲不亂的頭發(fā)上摔踱,一...
    開封第一講書人閱讀 52,255評論 1 308
  • 那天,我揣著相機與錄音怨愤,去河邊找鬼派敷。 笑死,一個胖子當著我的面吹牛撰洗,可吹牛的內容都是我干的篮愉。 我是一名探鬼主播,決...
    沈念sama閱讀 40,825評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼差导,長吁一口氣:“原來是場噩夢啊……” “哼试躏!你這毒婦竟也來了?” 一聲冷哼從身側響起柿汛,我...
    開封第一講書人閱讀 39,729評論 0 276
  • 序言:老撾萬榮一對情侶失蹤冗酿,失蹤者是張志新(化名)和其女友劉穎埠对,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體裁替,經...
    沈念sama閱讀 46,271評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡项玛,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,363評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了弱判。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片襟沮。...
    茶點故事閱讀 40,498評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖昌腰,靈堂內的尸體忽然破棺而出开伏,到底是詐尸還是另有隱情,我是刑警寧澤遭商,帶...
    沈念sama閱讀 36,183評論 5 350
  • 正文 年R本政府宣布固灵,位于F島的核電站,受9級特大地震影響劫流,放射性物質發(fā)生泄漏巫玻。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,867評論 3 333
  • 文/蒙蒙 一祠汇、第九天 我趴在偏房一處隱蔽的房頂上張望仍秤。 院中可真熱鬧,春花似錦可很、人聲如沸诗力。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽苇本。三九已至,卻和暖如春菜拓,著一層夾襖步出監(jiān)牢的瞬間圈澈,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評論 1 272
  • 我被黑心中介騙來泰國打工尘惧, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人递递。 一個月前我還...
    沈念sama閱讀 48,906評論 3 376
  • 正文 我出身青樓喷橙,卻偏偏與公主長得像,于是被迫代替她去往敵國和親登舞。 傳聞我的和親對象是個殘疾皇子贰逾,可洞房花燭夜當晚...
    茶點故事閱讀 45,507評論 2 359

推薦閱讀更多精彩內容