Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> RefreshRecyclerView下拉刷新,加載更多

RefreshRecyclerView下拉刷新,加載更多

編輯:關於Android編程

ListView已經用了很多年了,後來又有了RecyclerView,基本可以代替ListView/GridView了,還有瀑布流的模式,加上各種特效,於是就嘗試用RecyclerView替代listview。實際使用過之後發現,確實不錯,但是還存在一些問題(比如卡頓)

如果UI要求不嚴格,那麼有一個很簡單的方式實現下拉刷新:SwipeRefreshLayout ,然後基本你就可以開開心心做完了。

但是如果你們的UI或者產品說,我們要一個什麼什麼樣的功能(反正就是不造輪子不行的需求),那只能自己做了。

先看一下效果:

\\

 

簡單說一下思路:

下拉刷新:

1.自定義一個線性布局linearlayout,裡面先添加一個header,然後添加recycclerview。

2.計算出header的高度h1,然後設置linearlayout布局的的padding為-h1(也可以設置margin為-h,都可以實現,本文采用的是padding)

3.利用消息傳遞機制及對recyclerview的滾動監聽來判斷是否可以下拉刷新。

4.根據用戶的手勢移動來設置padding值,再加上一些回滾效果就ok了。

 

加載更多:

1.首先寫一個BaseAdapter抽象類繼承

import android.support.v7.widget.RecyclerView.Adapter;

2.在BaseAdapter中添加一個footerViewHolder,並設置開關,如果有加載更多的時候,就顯示。如果沒有,則不顯示。

3.監聽RecyclerView的滾動,如果滾動到末尾並且有加載更多,則調用加載更多的方法。

 

有了上面的思路寫起來就簡單多了。

 

接下來分析代碼:

下拉刷新:

 

public class RefreshRecyclerView extends LinearLayout{

    public static final long ONE_MINUTE = 60 * 1000;//一分鐘的毫秒值,用於判斷上次的更新時間

    public static final long ONE_HOUR = 60 * ONE_MINUTE;//一小時的毫秒值,用於判斷上次的更新時間

    public static final long ONE_DAY = 24 * ONE_HOUR;//一天的毫秒值,用於判斷上次的更新時間

    public static final long ONE_MONTH = 30 * ONE_DAY;//一月的毫秒值,用於判斷上次的更新時間

    public static final long ONE_YEAR = 12 * ONE_MONTH;//一年的毫秒值,用於判斷上次的更新時間

    private String MYRECYCLERVIEW = "MyRecyclerView";//保存用的

    private int id;//用來區分不同頁面

    private LinearLayout headerView;//刷新頭部布局

    public static final int SCROLL_SPEED = -10;//下拉頭部回滾的速度

    private int topPadding;//距離頂部的padding值

    private RecyclerView mRecyclerView;//列表控件

    private LinearLayoutManager mLayoutManager;//RecyclerView布局管理器

    private int firstVisibleItemPostion;//第一個可見item的position

    private int mHeaderHeight;//頭部高度

    private boolean hasMore;//設置是否有加載更多(用戶設置)

    private boolean hasRefresh;//設置是否有下拉刷新(用戶設置)

    private boolean canRefresh;//是否可以下拉刷新(邏輯判斷)

    private boolean isLoading;//列表是否正在刷新或加載更多中

    private boolean canScroll;//列表是否可以滾動(刷新加載中禁止用戶進行列表操作)

    private LoadLinsteners loadLinsteners;//加載監聽器

    private ScrollLinsteners scrollLinsteners;//滾動監聽器

    private BaseAdapter mAdapter;//Item適配器

    //頭部相關
    private TextView description, updated_at;//描述

    private Long lastUpdateTime;//上次更新時間

    private ImageView arrow;//箭頭

    private ProgressBar progress_bar;

    private ImageView iv_head;//頭部圖片

    private Context context;

    private int readCount;//判斷當前列表最大滾動位置

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

    public RefreshRecyclerView(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.context = context;
        initContents();
    }

    /**
     * 初始化
     */
    private void initContents() {
        //默認可下拉刷新,無加載更多
        canRefresh = true;
        hasMore = false;
        hasRefresh = true;
        isLoading = false;
        canScroll = true;

        //頭部
        headerView = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.item_recyler_header, null);
        iv_head = (ImageView)headerView.findViewById(R.id.iv_head);
        description = (TextView)headerView.findViewById(R.id.description);
        updated_at = (TextView)headerView.findViewById(R.id.updated_at);
        arrow = (ImageView)headerView.findViewById(R.id.arrow);
        progress_bar = (ProgressBar)headerView.findViewById(R.id.progress_bar);

        //測量並獲取頭部高度
        measureView(headerView);
        mHeaderHeight = headerView.getMeasuredHeight();

        //列表
        mRecyclerView = new RecyclerView(context);
        mRecyclerView.setOverScrollMode(View.OVER_SCROLL_NEVER);
        //創建默認的線性LayoutManager
        mLayoutManager = new LinearLayoutManager(getContext());
        mRecyclerView.setLayoutManager(mLayoutManager);

        //添加布局
        setOrientation(VERTICAL);
        addView(headerView, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
        addView(mRecyclerView, new LayoutParams(LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT, 1));

        //隱藏頭部
        setTopPadding(-mHeaderHeight);

        setLinsteners();

        /**
         * 添加旋轉動畫
         */
        Animation animation= AnimationUtils.loadAnimation(context, R.anim.anim_rotate_earth);
        LinearInterpolator lin = new LinearInterpolator();
        animation.setInterpolator(lin);
        iv_head.startAnimation(animation);

    }

    /**
     * 設置布局管理器
     * @param layoutManager
     */
    public void setmLayoutManager(LinearLayoutManager layoutManager){
        this.mLayoutManager = layoutManager;
        mRecyclerView.setLayoutManager(mLayoutManager);
    }

    /**
     * 獲取布局管理器
     * @return
     */
    public RecyclerView.LayoutManager getmLayoutManager(){
        return mLayoutManager;
    }


    /**
     * 設置adapter
     * @param mAdapter
     */
    public void setAdapter(BaseAdapter mAdapter){
        this.mAdapter = mAdapter;
        mRecyclerView.setAdapter(mAdapter);
    }

    /**
     * 獲取recyclerview
     * @return
     */
    public RecyclerView getmRecyclerView(){
        return mRecyclerView;
    }

    /**
     * 加載監聽器
     */
    public interface LoadLinsteners{
        void onLoadMore();
        void onRefresh();
    }

    /**
     * 設置監聽器
     * @param loadLinsteners
     * @param id
     */
    public void setLoadLinsteners(LoadLinsteners loadLinsteners, int id){
        this.loadLinsteners = loadLinsteners;
        this.id = id;
        refreshUpdatedAtValue();
    }
    /**
     * 設置監聽器
     * @param loadLinsteners
     */
    public void setLoadLinsteners(LoadLinsteners loadLinsteners){
        this.loadLinsteners = loadLinsteners;
        this.id = -1;
        refreshUpdatedAtValue();
    }

    /**
     * 滾動監聽接口
     */
    public  interface ScrollLinsteners{
        void onScrolled(int firstVisibleItem, int dx, int dy);
    }

    /**
     * 設置滾動監聽
     * @param scrollLinsteners
     */
    public void setScrollLinsteners(ScrollLinsteners scrollLinsteners){
        this.scrollLinsteners = scrollLinsteners;
    }

    /**
     * 獲取最大滾動位置
     * @return
     */
    public int getReadCount(){
        return readCount;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {

        if(!canScroll) {//禁止事件傳遞
            return true;
        }else {
            return false;
        }
    }

    private float moveY, startY = -1, dY;

    private void setLinsteners() {
        /**
         * 添加觸摸監聽,實現下拉效果
         */
        mRecyclerView.setOnTouchListener(new OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                if (canRefresh && firstVisibleItemPostion == 0 && !isLoading && hasRefresh) {
                    switch (event.getAction()) {
                        case MotionEvent.ACTION_DOWN:
                            startY = -1;
                            Log.i("move---", "down:-1");
                            break;
                        case MotionEvent.ACTION_MOVE:
                            moveY = event.getRawY();
                            Log.i("move----1", "startY:"+ startY + "  moveY:" + moveY + "  dY:" + dY + "  headerHeight:" + mHeaderHeight);
                            if(startY == -1) {
                                startY = moveY;
                            }
                            dY = moveY - startY;
                            Log.i("move----2", "startY:"+ startY + "  moveY:" + moveY + "  dY:" + dY + "  headerHeight:" + mHeaderHeight);
                            if (dY > 30) {
                                dY = dY - 30;
                                int maxLength = mHeaderHeight * 6;
                                if (dY < maxLength && dY >= 30) {
                                    description.setText(R.string.pull_to_refresh);
                                } else if (dY >= maxLength) {
                                    description.setText(R.string.release_to_refresh);
                                    dY = maxLength;
                                }

                                if(dY > mHeaderHeight){
                                    dY = (dY + mHeaderHeight)/2;
                                }
                                setTopPadding((int)(dY - mHeaderHeight));
                            } else {
                                Log.i("move---", "reset");
                                setTopPadding(- mHeaderHeight);
                                return false;
                            }
                            break;
                        case MotionEvent.ACTION_UP:
                            Log.i("move---", "up:-1");
                            if (dY > mHeaderHeight / 2) {
                                isLoading = true;
                                canScroll = false;
                                canRefresh = false;
                                description.setText(R.string.refreshing);
                                arrow.setVisibility(View.GONE);
                                progress_bar.setVisibility(View.VISIBLE);
                                new RefreshingTask().execute();
                                if(loadLinsteners != null) {
                                    loadLinsteners.onRefresh();
                                }
                            } else {
                                startY = -1;
                                new HideHeaderTask().execute();
                                return false;
                            }
                            dY = 0;
                            startY = -1;
                            moveY = 0;
                            break;
                    }
                    return true;
                }
                return false;
            }
        });

        /**
         * 添加滾動監聽,判斷加載更多
         */
        mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                firstVisibleItemPostion = mLayoutManager.findFirstVisibleItemPosition();
                int lastVisibleItem = mLayoutManager.findLastVisibleItemPosition();
                int totalItemCount = mLayoutManager.getItemCount();
                if (readCount <= lastVisibleItem) {
                    readCount = lastVisibleItem;
                }

                //lastVisibleItem >= totalItemCount - 4 表示剩下4個item自動加載
                // dy > 0 表示向下滑動
                if (lastVisibleItem >= totalItemCount - 4 && dy >= 0) {
                    if (hasMore && !isLoading) {//可以下拉刷新並且沒有加載中
                        isLoading = true;
                        canScroll = true;
                        if (loadLinsteners != null) {
                            loadLinsteners.onLoadMore();
                        }
                    }
                }

                //如果列表在頭部
                if (firstVisibleItemPostion == 0 && mLayoutManager.getChildAt(0).getTop() == 0 && hasRefresh) {
                    Log.i("move---", "recyclerview:reset:-1");
                    canRefresh = true;
                    startY = -1;
                } else {
                    canRefresh = false;
                }

                if (scrollLinsteners != null) {
                    scrollLinsteners.onScrolled(firstVisibleItemPostion, dx, dy);
                }
            }

            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
            }
        });
    }


    /**
     * 設置頂部padding值
     */
    public void setTopPadding(int top){
        topPadding = top;
        setPadding(0, top, 0, 0);
    }


    /**
     * 停止下拉刷新
     */
    public void onStopRefresh(){
        new HideHeaderTask().execute();
        canRefresh = true;
        isLoading = false;
        canScroll = true;
        arrow.setVisibility(View.GONE);
        progress_bar.setVisibility(View.GONE);
        saveTv_refresh_time();
    }

    /**
     * 停止加載更多
     */
    public void onStopMore(){
        isLoading = false;
    }

    /**
     * 保存刷新時間
     */
    public void saveTv_refresh_time(){
        if(id == -1)return;
        saveSPLongData(MYRECYCLERVIEW + id, System.currentTimeMillis());
        refreshUpdatedAtValue();
    }



    public void setId(int id){
        this.id = id;
    }

    /**
     * 是否可以加載更多
     * @param hasMore
     */
    public void setHasMore(boolean hasMore){
        this.hasMore = hasMore;
        if(mAdapter != null) {
            mAdapter.setHasMore(hasMore);
        }
    }

    /**
     * 是否可以下拉刷新
     * @param hasRefresh
     */
    public void setHasRefresh(boolean hasRefresh){
        this.hasRefresh = hasRefresh;
    }

    /**
     * 測量布局
     */
    private void measureView(View child) {
        ViewGroup.LayoutParams params = child.getLayoutParams();
        if (params == null) {
            params = new ViewGroup.LayoutParams(
                    ViewGroup.LayoutParams.MATCH_PARENT,
                    ViewGroup.LayoutParams.WRAP_CONTENT);
        }
        int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0, params.width);
        int lpHeight = params.height;
        int childHeightSpec;
        if (lpHeight > 0) {
            childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
        } else {
            childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
        }
        child.measure(childWidthSpec, childHeightSpec);
    }


    /**
     * 刷新下拉頭中上次更新時間的文字描述。
     */
    private void refreshUpdatedAtValue() {
        if(id == -1)return;
        lastUpdateTime = getSPLongData(MYRECYCLERVIEW + id);
        long currentTime = System.currentTimeMillis();
        long timePassed = currentTime - lastUpdateTime;
        long timeIntoFormat;
        String updateAtValue;
        if (lastUpdateTime == -1) {
            updateAtValue = getResources().getString(R.string.not_updated_yet);
        } else if (timePassed < 0) {
            updateAtValue = getResources().getString(R.string.time_error);
        } else if (timePassed < ONE_MINUTE) {
            updateAtValue = getResources().getString(R.string.updated_just_now);
        } else if (timePassed < ONE_HOUR) {
            timeIntoFormat = timePassed / ONE_MINUTE;
            String value = timeIntoFormat + "分鐘";
            updateAtValue = String.format(getResources().getString(R.string.updated_at), value);
        } else if (timePassed < ONE_DAY) {
            timeIntoFormat = timePassed / ONE_HOUR;
            String value = timeIntoFormat + "小時";
            updateAtValue = String.format(getResources().getString(R.string.updated_at), value);
        } else if (timePassed < ONE_MONTH) {
            timeIntoFormat = timePassed / ONE_DAY;
            String value = timeIntoFormat + "天";
            updateAtValue = String.format(getResources().getString(R.string.updated_at), value);
        } else if (timePassed < ONE_YEAR) {
            timeIntoFormat = timePassed / ONE_MONTH;
            String value = timeIntoFormat + "個月";
            updateAtValue = String.format(getResources().getString(R.string.updated_at), value);
        } else {
            timeIntoFormat = timePassed / ONE_YEAR;
            String value = timeIntoFormat + "年";
            updateAtValue = String.format(getResources().getString(R.string.updated_at), value);
        }
        updated_at.setText(updateAtValue);
    }


    /**
     * 正在刷新的任務,在此任務中會去回調注冊進來的下拉刷新監聽器。
     */
    class RefreshingTask extends AsyncTask {

        @Override
        protected Integer doInBackground(Void... params) {
            int tp = topPadding;
            long curMills = System.currentTimeMillis();
            while (true) {
                long nowMills = System.currentTimeMillis();
                if(nowMills - curMills >= 10){
                    curMills = nowMills;
                    tp = tp + SCROLL_SPEED * 2;
                    if (tp <= 0) {
                        tp = 0;
                        break;
                    }
                    publishProgress(tp);
                }
            }
            return tp;
        }

        @Override
        protected void onProgressUpdate(Integer... tp) {
            setTopPadding(tp[0]);
        }

        @Override
        protected void onPostExecute(Integer tp) {
            setTopPadding(tp);
        }
    }

    /**
     * 隱藏下拉頭的任務,當未進行下拉刷新或下拉刷新完成後,此任務將會使下拉頭重新隱藏。
     */
    class HideHeaderTask extends AsyncTask {

        @Override
        protected Integer doInBackground(Void... params) {
            int tp = topPadding;
            long curMills = System.currentTimeMillis();
            while (true) {
                long nowMills = System.currentTimeMillis();
                if(nowMills - curMills >= 10) {
                    curMills = nowMills;
                    tp = tp + SCROLL_SPEED;
                    if (tp <= -mHeaderHeight) {
                        tp = -mHeaderHeight;
                        break;
                    }
                    publishProgress(tp);
                }
            }
            return tp;
        }

        @Override
        protected void onProgressUpdate(Integer... tp) {
            setTopPadding(tp[0]);
        }

        @Override
        protected void onPostExecute(Integer tp) {
            setTopPadding(tp);
        }
    }

    /**
     * 獲取賬戶管理sp
     * @return
     */
    private SharedPreferences getAccountSP() {
        return context.getSharedPreferences("account", Context.MODE_PRIVATE);
    }

    /**
     * 保存Long值
     * @param key
     * @param value
     */
    public void saveSPLongData(String key, long value) {
        getAccountSP().edit().putLong(key, value).commit();
    }


    /**
     * 獲取存儲的Long值
     * @param key
     * @return
     */
    public Long getSPLongData(String key) {
        return getAccountSP().getLong(key, -1);
    }

}

 

(已經很詳細的注釋了,就不多說了,這裡有一個注意點就是:

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:23.3.0'
    compile 'com.android.support:design:23.2.0'
}
紅色區域是需要注意的,如果你用的是23.3.0會有問題。。。原因未知。。。其它版本未測試)

 


加載更多:

 

public abstract class BaseAdapter extends Adapter {

    protected final LayoutInflater layoutInflater;
    public List mMessages;
    private static final int TYPE_FOOTER = -2;
    public boolean hasMore;
    public Context context;
    private int  mResources;
    private ItemOnclickLinstener itemOnclickLinstener;

    public BaseAdapter(Context context, List mMessages, int mResources) {
        this.mMessages = mMessages;
        this.context = context;
        this.mResources = mResources;
        layoutInflater = LayoutInflater.from(context);
        hasMore = false;
    }

    @Override
    public K onCreateViewHolder(ViewGroup parent, int viewType) {
        // type == TYPE_FOOTER 返回footerView
        if (viewType == TYPE_FOOTER) {
            View view = layoutInflater.inflate(R.layout.item_footer, null);
            view.setLayoutParams(new ViewGroup.LayoutParams(
                    ViewGroup.LayoutParams.MATCH_PARENT,
                    ViewGroup.LayoutParams.WRAP_CONTENT));
            return (K)new FooterViewHolder(view);
        }else {
            final K vh = createViewHolder(viewType, parent);
            vh.itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if(itemOnclickLinstener != null) {
                        itemOnclickLinstener.setItemOnclickLinsteners(v, vh.getLayoutPosition(), vh.getItemViewType());
                    }
                }
            });
            return vh;
        }
    }

    @Override
    public void onBindViewHolder(final K viewHolder, final int position) {
        if(!(hasMore && position == getItemCount() -1)){
            setOnBindViewHolder(viewHolder, position);
        }
    }

    @Override
    public int getItemCount() {
        int size = 0;
        if(mMessages != null){
            size = mMessages.size();
        }
        return hasMore ? size + 1 : size;
    }

    @Override
    public int getItemViewType(int position) {
        if (position + 1 == getItemCount() && hasMore) {
            return TYPE_FOOTER;
        } else {
            return setItemViewType(position);
        }
    }

    /**
     * footer ViewHolder
     */
    class FooterViewHolder extends ViewHolder {
        public FooterViewHolder(View view) {
            super(view);
        }
    }

    /**
     * 是否顯示加載更多
     * @param hasMore
     */
    public void setHasMore(boolean hasMore){
        this.hasMore = hasMore;
    }

    /**
     * 創建ViewHolder
     * @param viewType
     * @return
     */
    protected abstract K createViewHolder(int viewType, ViewGroup parent);

    /**
     * 設置ViewHolder類型
     * @param position
     * @return
     */
    protected abstract int setItemViewType(int position);

    /**
     * 數據填充
     */
    protected abstract void setOnBindViewHolder(K viewHolder, int position);

    /**
     * Item點擊事件
     * @param
     * @return
     */
    public void setItemOnClickLinsteners(ItemOnclickLinstener itemOnclickLinstener){
        this.itemOnclickLinstener = itemOnclickLinstener;
    }

    /**
     * item點擊接口
     */
    public interface ItemOnclickLinstener{
        void setItemOnclickLinsteners(View v, int postion, int type);
    }

    protected View getView(int resource){
        View view = layoutInflater.inflate(resource, null);
        view.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.WRAP_CONTENT));
        return view;
    }

    protected View getView(){
        View view = layoutInflater.inflate(mResources, null);
        view.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.WRAP_CONTENT));
        return view;
    }

    protected View getView(ViewGroup parent){
        View view = layoutInflater.inflate(mResources, parent, false);
        view.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.WRAP_CONTENT));
        return view;
    }

}

只需要繼承此類即可實現加載更多的功能。

 

源碼地址:https://github.com/736791050/RefreshRecyclerView

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved