Android RecycletView ListView GridView ScrollView下拉刷新實現(xiàn)

前言
一直以來公司項目都沒有下拉刷新的功能法严,最近換了新產(chǎn)品瓤鼻,有了這部分的需求設計诽里。我看了下涉及到的頁面所用的控件即有ListView也有RecycleView辜膝,鑒于刷新效果是自己設計的无牵,想從網(wǎng)上直接找造好的輪子,不太現(xiàn)實厂抖,況且牽涉到兩個控件茎毁,以后要是再改到用了GridView的頁面呢。自己重寫這三個控件忱辅,牽扯到的頁面勢必要重新翻一遍七蜘,怎么看工作量都很大谭溉。后來想了下,干脆重寫一個可以下拉刷新的LinearLayout橡卤,包在外面夜只,這要是之前的所有代碼邏輯都不用動,也不用重新測試蒜魄,重要的是可以給任何你想加的控價添加下拉刷新扔亥,工作量都省了下來,說干就干谈为。
首先旅挤,會遇到以下幾個問題
1、下拉刷新觸發(fā)的時機伞鲫,也就是第一個item完全漏出的時候
2粘茄、事件分發(fā),也就是滑動事件什么時候交給ListView什么時候交給刷新LinearLayout
以上兩個問題秕脓,想詳細了解的柒瓣,直接看代碼注釋很清晰,在這里就不細說了吠架。
廢話不多說芙贫,直接上代碼。
一傍药、源碼
1磺平、頭部布局文件 header_refresh.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="100dip"
    android:background="#EFEFF4">
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="100dip"
        android:orientation="horizontal">
        <ImageView
            android:id="@+id/iv_refresh"
            android:layout_width="25dip"
            android:layout_height="25dip"
            android:layout_toLeftOf="@+id/ll_refresh_right"
            android:layout_marginRight="10dip"
            android:layout_centerVertical="true"
            android:src="@drawable/refresh1"/>
        <LinearLayout
            android:id="@+id/ll_refresh_right"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerHorizontal="true"
            android:layout_centerVertical="true"
            android:gravity="center"
            android:orientation="vertical">
            <TextView
                android:id="@+id/tv_refresh"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textColor="#D7D6D8"
                android:textSize="14sp"
                android:text="下拉刷新"/>
            <TextView
                android:id="@+id/tv_last_refresh_time"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="10dip"
                android:textColor="#D7D6D8"
                android:textSize="13sp"
                android:text="最近更新:今天14:20"/>
        </LinearLayout>

    </RelativeLayout>
</LinearLayout>

2、刷新動畫refreshing_anim.xml寫在drawable里

<?xml version="1.0" encoding="utf-8"?>
<set android:shareInterpolator="false" xmlns:android="http://schemas.android.com/apk/res/android">
    <rotate   
        android:interpolator="@android:anim/linear_interpolator"  
        android:pivotX="50%"  
        android:pivotY="50%"  
        android:fromDegrees="0"  
        android:toDegrees="+360"  
        android:duration="300"
        android:startOffset="-1"  
        android:repeatMode="restart"  
        android:repeatCount="-1"/>  
</set>

3拐辽、重寫的PullToRefreshLinearLayout .java

import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.ScrollView;
import android.widget.TextView;

import com.xuetian.netschool.R;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * 下拉刷新控件拣挪,可以配合 RecyclerView剧蚣,Scrollview集绰,ListView等所有view使用
 *
 */

public class PullToRefreshLinearLayout extends LinearLayout {

    private View targetRefreshView;
    int downY = 0, moveY = 0;
    int downX = 0, moveX = 0;
    boolean isRefreshStart = false;
    int hearderViewHeight = 100;//刷新頭部高度dip,與布局文件中保持一致
    int instance;
    int paddingTop;
    View headerView;
    final String SHARE_KEY = "refreshTime";
    final SimpleDateFormat HHmm = new SimpleDateFormat("HH:mm");
    OnRefreshListener onRefreshListener;
    private SharedPreferences sharedPreferences;
    boolean isRefreshing = false;

    TextView textView, tvLastRefreshTime;
    ImageView ivRefresh;

    public PullToRefreshLinearLayout(Context context) {
        this(context, null);
    }

    public PullToRefreshLinearLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }
    Context context;
    public PullToRefreshLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.context = context;
        setOrientation(VERTICAL);
        sharedPreferences = context.getSharedPreferences(SHARE_KEY, Context.MODE_PRIVATE);
        initHeaderView();
    }
    private void initHeaderView(){
        LayoutInflater inflater = ((Activity)context).getLayoutInflater();
        headerView = inflater.inflate(R.layout.header_refresh, null);
        textView = headerView.findViewById(R.id.tv_refresh);
        ivRefresh = headerView.findViewById(R.id.iv_refresh);
        tvLastRefreshTime = headerView.findViewById(R.id.tv_last_refresh_time);
        tvLastRefreshTime.setText("最后更新:今天" + getHHmm(getRefreshTime()));
        headerView.setPadding(0, -dip2px(context, hearderViewHeight), 0, 0);
        addView(headerView,0);
    }
    @Override
    public void addView(View child) {
        if (getChildCount() > 1) {
            throw new IllegalStateException("RefreshLinearLayout can host only one direct child");
        }
        super.addView(child);
    }

    @Override
    public void addView(View child, int index) {
        if (getChildCount() > 1) {
            throw new IllegalStateException("RefreshLinearLayout can host only one direct child");
        }

        super.addView(child, index);
    }

    @Override
    public void addView(View child, ViewGroup.LayoutParams params) {
        if (getChildCount() > 1) {
            throw new IllegalStateException("RefreshLinearLayout can host only one direct child");
        }

        super.addView(child, params);
    }

    @Override
    public void addView(View child, int index, ViewGroup.LayoutParams params) {
        if (getChildCount() > 1) {
            throw new IllegalStateException("RefreshLinearLayout can host only one direct child");
        }
        targetRefreshView = child;
        super.addView(child, index, params);
    }
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if(targetRefreshView == null) return super.onTouchEvent(event);
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                isRefreshStart = isStartRefresh(targetRefreshView);
                downY = (int) event.getY();
                downX = (int) event.getX();
                break;
            case MotionEvent.ACTION_MOVE:
                moveY = (int) event.getY();
                instance = moveY - downY;
                if (instance > 0 && isRefreshStart) {
                    paddingTop = -dip2px(context, hearderViewHeight) + instance;
                    if(paddingTop >=dip2px(context, hearderViewHeight)) paddingTop = dip2px(context, 100);

                    headerView.setPadding(0, paddingTop, 0, 0);
                    if(instance >= dip2px(context, hearderViewHeight)) textView.setText("松開刷新");
                    else textView.setText("下拉刷新");
                    return true;
                }
                break;
            case MotionEvent.ACTION_UP:
                if(instance >= dip2px(context, hearderViewHeight)){
                    isRefreshing = true;
                    textView.setText("正在刷新...");
                    // 加載動畫
                    ivRefresh.setImageResource(R.drawable.refresh1);
                    Animation hyperspaceJumpAnimation = AnimationUtils.loadAnimation(
                            context, R.anim.refreshing_anim);
                    // 使用ImageView顯示動畫
                    ivRefresh.startAnimation(hyperspaceJumpAnimation);
                    new Thread(new Runnable() {
                        int i = 0;
                        @Override
                        public void run() {
                            for ( i = 0; i <= 50; i++){
                                try {
                                    Thread.sleep(5);
                                } catch (InterruptedException e) {
                                    e.printStackTrace();
                                }
                                ((Activity)context).runOnUiThread(new Runnable() {
                                    @Override
                                    public void run() {
                                        headerView.setPadding(0, paddingTop - i * paddingTop/50 , 0, 0);
                                    }
                                });
                            }
                            try {
                                Thread.sleep(100);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            ((Activity)context).runOnUiThread(new Runnable() {
                                @Override
                                public void run() {
                                    if(onRefreshListener != null)onRefreshListener.onRefresh();
                                }
                            });

                        }
                    }).start();

                }else {
                    new Thread(new Runnable() {
                        int i = 0;
                        @Override
                        public void run() {
                            for ( i = 0; i <= 100; i++){
                                try {
                                    Thread.sleep(5);
                                } catch (InterruptedException e) {
                                    e.printStackTrace();
                                }
                                ((Activity)context).runOnUiThread(new Runnable() {
                                    @Override
                                    public void run() {
                                        headerView.setPadding(0,
                                                paddingTop + i *(-dip2px(context, hearderViewHeight) - paddingTop)/100 , 0, 0);
                                    }
                                });
                            }
                        }
                    }).start();
                }

                break;

        }
        return super.onTouchEvent(event);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        if(targetRefreshView == null) return super.onInterceptTouchEvent(event);
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                isRefreshStart = isStartRefresh(targetRefreshView);
                downY = (int) event.getY();
                downX = (int) event.getX();
                break;
            case MotionEvent.ACTION_MOVE:
                moveY = (int) event.getY();
                moveX = (int) event.getX();
                instance = moveY - downY;
                if (!isRefreshing && instance > 0 && isRefreshStart) {
                    if(!isRefreshing){
                        tvLastRefreshTime.setText("最后更新:今天" + getHHmm(getRefreshTime()));
                    }
                    if(Math.abs(downX - moveX) > Math.abs(downY - moveY)){
                        return false;
                    }
                    return true;
                }else if(isRefreshing && instance < 0){
                    refreshComplete();
                }
                break;
            case MotionEvent.ACTION_UP:
                break;

        }
        return super.onInterceptTouchEvent(event);
    }

    private boolean isStartRefresh(View targetRefreshView){
        if(targetRefreshView != null && (targetRefreshView instanceof ListView ||
                targetRefreshView instanceof RecyclerView ||
                targetRefreshView instanceof GridView || targetRefreshView instanceof ScrollView)){
            if((targetRefreshView instanceof ListView) && ((ListView)targetRefreshView).getFirstVisiblePosition() == 0
                    && ((ListView)targetRefreshView).getChildAt(0).getTop() >= targetRefreshView.getPaddingTop())
                return true;

            else if ((targetRefreshView instanceof RecyclerView) && ((LinearLayoutManager)((RecyclerView)targetRefreshView)
                    .getLayoutManager()).findFirstVisibleItemPosition() == 0
                    && ((RecyclerView)targetRefreshView).getChildAt(0).getTop() >= targetRefreshView.getPaddingTop())
                return true;

            else if ((targetRefreshView instanceof GridView) && ((GridView)targetRefreshView).getFirstVisiblePosition() == 0
                    && ((GridView)targetRefreshView).getChildAt(0).getTop() >= targetRefreshView.getPaddingTop())
                return true;

            else if((targetRefreshView instanceof ScrollView) && ((ScrollView)targetRefreshView).getScrollY() == 0)
                return true;

            else return false;

        }

        else if(targetRefreshView != null)
            return true;

        else return false;
    }

    /**
     * 將刷新頭部復位
     * */
    private void headerRestoration(){
        setRefreshTime();
        if (!isRefreshing) return;
        isRefreshing = false;
        ivRefresh.clearAnimation();
        ivRefresh.setImageResource(R.drawable.refresh2);
        textView.setText("刷新成功");
        new Thread(new Runnable() {
            int i = 0;
            @Override
            public void run() {
                for ( i = 1; i <= 100; i++){
                    try {
                        Thread.sleep(5);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    ((Activity)context).runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            headerView.setPadding(0,
                                    i * (-dip2px(context, hearderViewHeight))/100 , 0, 0);
                        }
                    });
                }
                ((Activity)context).runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        textView.setText("下拉刷新");
                        ivRefresh.setImageResource(R.drawable.refresh1);
                        if(onRefreshListener != null)onRefreshListener.refreshData();
                    }
                });

            }
        }).start();
    }



    /**
     * 設置最近刷新時間
     *
     */
    private void setRefreshTime() {
        sharedPreferences.edit().putLong(context.getPackageName() + context.getClass().getName(), System.currentTimeMillis()).commit();
    }
    /**
     * 獲取最近刷新時間
     */
    private Long getRefreshTime() {
        return sharedPreferences.getLong(context.getPackageName() + context.getClass().getName(), System.currentTimeMillis());
    }

    /**
     * 根據(jù)手機的分辨率從 dp 的單位 轉成為 px(像素)
     */
    private int dip2px(Context context, float dpValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

    /**
     * HH:mm類型的日期
     * @param milliseconds
     * @return
     */
    private String getHHmm(long milliseconds){
        if(milliseconds == 0){
            return "";
        }
        String string;
        Date date = new Date(milliseconds);
        string = HHmm.format(date);
        return string;
    }

//=======================================以上代碼都不用看,獲取該控件實例后直接調(diào)用setOnRefreshListener即可淤刃, 會回調(diào)相應方法================================

    public void setOnRefreshListener(OnRefreshListener onRefreshListener){
        this.onRefreshListener = onRefreshListener;
    }

    //數(shù)據(jù)請求完畢睁搭,調(diào)用該方法
    public void refreshComplete(){
        headerRestoration();
    }
    /**
     * 刷新回掉接口
     * */
    public interface OnRefreshListener{
        //刷新開始赶诊,回調(diào)該方法;可在該方法中請求最新數(shù)據(jù)
        void onRefresh();
        //刷新頭部復位后介袜,會回調(diào)該方法甫何,可在該方法里將請求到的新數(shù)據(jù)渲染到頁面上
        void refreshData();
    }
}

注:刷新的頭部有用到兩個圖片出吹,在此就不上傳了遇伞,可以根據(jù)需要自己選擇
二、使用方式
獲取到下拉刷新控件設置回調(diào)監(jiān)聽接口即可

pullToRefreshLinearLayout.setOnRefreshListener(new PullToRefreshLinearLayout().OnRefreshListener() {
            @Override
            public void onRefresh() {
                
            }

            @Override
            public void refreshData() {

            }
        });

總結:只要講該下拉刷新控件包在任何一個View或者Viewroup的外面捶牢,都可以給出下拉刷新效果

著作:一點愁
原創(chuàng)博客轉載請注明出處……

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末鸠珠,一起剝皮案震驚了整個濱河市巍耗,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌渐排,老刑警劉巖炬太,帶你破解...
    沈念sama閱讀 221,406評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異驯耻,居然都是意外死亡亲族,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,395評論 3 398
  • 文/潘曉璐 我一進店門可缚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來霎迫,“玉大人,你說我怎么就攤上這事帘靡≈” “怎么了?”我有些...
    開封第一講書人閱讀 167,815評論 0 360
  • 文/不壞的土叔 我叫張陵描姚,是天一觀的道長涩赢。 經(jīng)常有香客問我,道長轩勘,這世上最難降的妖魔是什么筒扒? 我笑而不...
    開封第一講書人閱讀 59,537評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮绊寻,結果婚禮上霎肯,老公的妹妹穿的比我還像新娘。我一直安慰自己榛斯,他們只是感情好观游,可當我...
    茶點故事閱讀 68,536評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著驮俗,像睡著了一般懂缕。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上王凑,一...
    開封第一講書人閱讀 52,184評論 1 308
  • 那天搪柑,我揣著相機與錄音,去河邊找鬼索烹。 笑死工碾,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的百姓。 我是一名探鬼主播渊额,決...
    沈念sama閱讀 40,776評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了旬迹?” 一聲冷哼從身側響起火惊,我...
    開封第一講書人閱讀 39,668評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎奔垦,沒想到半個月后屹耐,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,212評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡椿猎,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,299評論 3 340
  • 正文 我和宋清朗相戀三年惶岭,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片犯眠。...
    茶點故事閱讀 40,438評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡俗他,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出阔逼,到底是詐尸還是另有隱情兆衅,我是刑警寧澤,帶...
    沈念sama閱讀 36,128評論 5 349
  • 正文 年R本政府宣布嗜浮,位于F島的核電站羡亩,受9級特大地震影響,放射性物質發(fā)生泄漏危融。R本人自食惡果不足惜畏铆,卻給世界環(huán)境...
    茶點故事閱讀 41,807評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望吉殃。 院中可真熱鬧辞居,春花似錦、人聲如沸蛋勺。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,279評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽抱完。三九已至贼陶,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間巧娱,已是汗流浹背碉怔。 一陣腳步聲響...
    開封第一講書人閱讀 33,395評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留禁添,地道東北人撮胧。 一個月前我還...
    沈念sama閱讀 48,827評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像老翘,于是被迫代替她去往敵國和親芹啥。 傳聞我的和親對象是個殘疾皇子锻离,可洞房花燭夜當晚...
    茶點故事閱讀 45,446評論 2 359

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