自定义View:通用的下拉刷新

我们知道ListView是可以addHeadView和addFooterView的,其他的RecyclerView 和scrollView等是不能添加的,如果为每个都单独写又太麻烦了,所以一般自定义ViewGroup来做一个通用刷新的控件。网上有BAGRefresh这类通用的刷新,看了写的东西比较多,所以我按照自己想法写了一个简单的demo说明一下原理。


老规矩先来张图,背景图是受到一个国外app的启发去找的类似的图,大家不要在意这些细节

自定义View:通用的下拉刷新_第1张图片

暂时只做了ListView的,其他列表控件,只需要把判断顶部的操作完成就可以轻松加进去



使用方法也很简单

    
        
        
    

涉及到的知识点:

自定义View

事件分发原理

ListView顶部底部判断

GestureDetector使用

滑动的scollTo()方法与scroller的使用(我这个demo分别使用了这两种来实现头布局的滑动)


大致步骤

1.mHeaderView与mContentView两个子View的测量与绘制。

2.ListView滑动监听看是否到顶部

3.ListView到顶部并且还在下拉ViewGroup将Move事件拦截

4.消耗时onTouchEvent将事件交给GestureDetector处理

5.在GestureDetector的onScroll方法中分别使用了scrollTo和scroller实现头部的滑动效果

6.就是一个提示文字的改变和刷新后状态的恢复


RefreshLayout类的源码

package com.roman.refreshlayout;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.ListView;
import android.widget.Scroller;
import android.widget.TextView;


/**
 * Created by roman
 * On 2016/8/5.
 */
public class RefreshLayout extends ViewGroup implements GestureDetector.OnGestureListener {

    private static final String TAG = "RefreshLayout";
    private static final int OFFSET_Y = 5;//使用scrollTo时Y每次变化的常量

    private LayoutInflater mInflater;
    private View mHeaderView;//下拉刷新头部View
    private View mContentView;//包裹的列表View
    private ListView mListView;
    private GestureDetector mGestureDetector;
    private Scroller mScroller;
    private TextView mTextView;//刷新文字

    private boolean isFirstItem = false;//是否滚动第一条
    private boolean isFirst = true;//是否是第一次进来
    private boolean isRefreshing = false;//是否正在刷新
    private int mCnt = 0;
    private int mDownY;
    private int mCurrentY;

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

    public RefreshLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

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

    private void initView() {
        mGestureDetector = new GestureDetector(getContext(), this);
        mScroller = new Scroller(getContext());

        mInflater = LayoutInflater.from(getContext());
        mHeaderView = mInflater.inflate(R.layout.view_refresh_head, null);
        mTextView = (TextView) mHeaderView.findViewById(R.id.refresh_tv);
        addView(mHeaderView);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if (mContentView == null) {
            mContentView = this.getChildAt(1);
            mListView = (ListView) mContentView;

            mListView.setOnScrollListener(new AbsListView.OnScrollListener() {
                @Override
                public void onScrollStateChanged(AbsListView view, int scrollState) {

                }

                @Override
                public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
                    if (firstVisibleItem == 0) {
                        isFirstItem = true;
                    } else if (firstVisibleItem + visibleItemCount == totalItemCount) {
                        if (isRefreshing) {
                            scrollTo(0, mHeaderView.getMeasuredHeight());
                        }
                    } else {
                        if (isRefreshing) {
                            scrollTo(0, mHeaderView.getMeasuredHeight());
                        }
                        isFirstItem = false;
                    }
                }
            });
        }
        measureChildren(widthMeasureSpec, heightMeasureSpec);//测量子view
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        mCnt = mHeaderView.getMeasuredHeight();

        mHeaderView.layout(0, 0, mHeaderView.getMeasuredWidth(), mHeaderView.getMeasuredHeight());//在最上面
        mContentView.layout(0, mHeaderView.getMeasuredHeight(), mContentView.getMeasuredWidth(),
                mHeaderView.getMeasuredHeight()  + mContentView.getMeasuredHeight());
        scrollTo(0, mHeaderView.getMeasuredHeight());//第一次
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean isIntercepet = false;

        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mDownY = (int) ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                mCurrentY = (int) ev.getY();
                if (isTop(mCurrentY - mDownY)) {//到顶的判断,传是上滑还是下滑进去
                    isIntercepet = true;//拦截
                }
                break;
            case MotionEvent.ACTION_UP:
                break;
        }

        if (isIntercepet) {
            return true;
        } else {
            return super.onInterceptTouchEvent(ev);//默认false
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        if (event.getAction() == MotionEvent.ACTION_UP) {
            if (getScrollY() >  30) {//自动弹回去
                scrollTo(0, mHeaderView.getMeasuredHeight());
                return super.onTouchEvent(event);
            }
        }

        return mGestureDetector.onTouchEvent(event);
    }

    private boolean isTop(int distance) {
        if (distance > 0) {//下拉
            if (isFirstItem) {
                return true;
            }
        } 
        return false;
    }


    //----------------------GestureDetector-----------------------------
    @Override
    public boolean onDown(MotionEvent e) {
        return false;
    }

    @Override
    public void onShowPress(MotionEvent e) {

    }

    @Override
    public boolean onSingleTapUp(MotionEvent e) {
        return false;
    }

    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        //1.使用ScrollTo利用常量来产生滑动效果,最简单最暴力的是直接scrollTo(0, 0),这样很生硬
        //所以下面用一个常量来进行平滑效果模拟,当拉到一半松手自动收回来,差不多拉到尽头,自动滑到尽头
        if (mCnt >= 0 && distanceY < 0) {//下拉
            if (getScrollY() <= 30) {//自动拉出来
                scrollTo(0, 0);
                refreshing();
            } else {//在拉
                scrollTo(0, mCnt -= OFFSET_Y);
            }
        } else if (distanceY > 0) {//下拉过程中上滑
            scrollTo(0, mHeaderView.getMeasuredHeight());
        }else {
            refreshing();
        }

        //2.使用scroller做平缓滑动
//        if (!isRefreshing) {
//            mScroller.startScroll(0, getScrollY(), 0, -mHeaderView.getMeasuredHeight() , 2000);
//            refreshing();
//            invalidate();
//        }
        return false;
    }

    @Override
    public void onLongPress(MotionEvent e) {

    }

    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        return false;
    }

    //----------------------GestureDetector-----------------------------


    @Override
    public void computeScroll() {
        super.computeScroll();
        if (mScroller.computeScrollOffset()) {
            scrollTo(0, mScroller.getCurrY());
            invalidate();
        }
    }

    /**
     * 正在刷新,已经3秒后恢复状态
     */
    private void refreshing() {
        isRefreshing = true;
        mTextView.setText("正在刷新...");
        mHeaderView.postDelayed(new Runnable() {
            @Override
            public void run() {
                scrollTo(0, mHeaderView.getMeasuredHeight());
                mCnt = mHeaderView.getMeasuredHeight();
                mTextView.setText("下拉刷新");
                isRefreshing = false;
            }
        }, 3000);
    }

}


源码在github https://github.com/PK0071/RefreshLayout









你可能感兴趣的:(android,android移动开发)