Android实际开发问题02------PullToRefresh

原本一直是用前辈写的一个PullToRefresh控件,但是有些地方和需求不符,所以对其进行了重写,下面展示重写后的代码:

最主要的改动就是一个页面,里面出现了PullToRefresh控件,控件里面又包含了Viewpager,这时,由于PullToRefresh的onInterceptTouchEvent的拦截事件,导致Viewpager左右滑动效果并不理想,所以需要对其进行修改,另外就是上下拉动的速率.


package com.cxd.pulltorefresh;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.LinearLayout;
import android.widget.ScrollView;

import java.util.IllegalFormatException;

/**
 * @author cxd
 * @ClassName: com.cxd.pulltorefresh
 * @Description:
 * @date 2015/12/16
 */

public class PullToRefresh extends LinearLayout {

    private String TAG = "PullToRefresh";

    //顶部或底部视图控件刷新状态
    //拉动去刷新
    private static final int PULL_TO_REFRESH = 2;
    //释放去刷新
    private static final int RELEASE_TO_REFRESH = 3;
    //正在刷新中
    private static final int REFRESHING = 4;

    // 当前刷新控件状态
    // 向上拉
    private static final int PULL_UP_STATE = 0;
    // 向下拉
    private static final int PULL_DOWN_STATE = 1;

    // 布局填充器
    private LayoutInflater mInflater;

    // 头部视图
    private View mHeaderView;

    // 头部视图高度
    private int mHeaderViewHeight;

    // 底部视图
    private View mFooterView;

    // 底部视图高度
    private int mFooterViewHeight;

    // 需适配view
    private AdapterView<?> mAdapterView;

    // 滚动view
    private ScrollView mScrollView;

    // y的最后距离
    private int mLastMotiony;

    // 头部刷新是否可用
    private boolean isHeadable;

    // 底部刷新是否可用
    private boolean isFootable;

    // 当前的拉动状态
    private int mPullState;

    //头部当前所处状态
    private int mHeadState;

    //底部当前所处状态
    private int mFootState;

    //头部刷新有效度(触发刷新向下的拉动距离)
    private float HeaderPower = 0.3f;

    //底部刷新有效度(触发刷新向上的拉动距离)
    private float FooterPower = 0.3f;

    //头部刷新事件
    private onHeaderRefreshListener onHeaderRefreshListener;

    //底部刷新事件
    private onFooterRefreshListener onFooterRefreshListener;


    public PullToRefresh(Context context) {
        super(context);
        init();
    }

    public PullToRefresh(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public PullToRefresh(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    /**
     * 初始化
     */
    private void init() {
        mInflater = LayoutInflater.from(getContext());
        addHeaderView();
    }

    /**
     * 加载xml文件完成的时候触发
     */
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        addFooterView();
        initContentAdapterView();
    }

    /**
     * 添加头部视图
     */
    private void addHeaderView() {
        // 获取头部视图
        mHeaderView = mInflater.inflate(R.layout.refresh_header, this, false);
        // 计算头部视图宽高
        measureView(mHeaderView);
        // 设置头部视图所在位置
        mHeaderViewHeight = mHeaderView.getMeasuredHeight();
        LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, mHeaderViewHeight);
        params.topMargin = -(mHeaderViewHeight);
        Log.e(TAG, params.topMargin + "");
        addView(mHeaderView, params);
    }

    /**
     * 增加底部视图
     */
    private void addFooterView() {
        mFooterView = mInflater.inflate(R.layout.refresh_footer, this, false);
        measureView(mFooterView);
        mFooterViewHeight = mFooterView.getMeasuredHeight();
        LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, mFooterViewHeight);
        addView(mFooterView, params);
    }

    /**
     * 初始化内容,分为两种情况,一种是AdaptervIEW,一种是ScrollView
     */
    private void initContentAdapterView() {
        int count = getChildCount();
        // 如果包含的子视图小于3个,则说明没有里面没有内容,报异常
        if (count < 3) {
            throw new IllegalArgumentException("The Layout must contains 3 Views");
        }
        View view = null;
        for (int i = 0; i < count; i++) {
            view = getChildAt(i);
            if (view instanceof AdapterView<?>) {
                mAdapterView = ((AdapterView<?>) view);
            }
            if (view instanceof ScrollView) {
                mScrollView = ((ScrollView) view);
            }
        }
        // 内容必须是adapterView和ScrollView中的一种
        if (mAdapterView == null && mScrollView == null) {
            throw new IllegalArgumentException("The Content of Layout must instanceof AdapterView/ScrollView");
        }
    }

    /**
     * 计算控件的高宽
     *
     * @param view
     */
    private void measureView(View view) {
        ViewGroup.LayoutParams params = view.getLayoutParams();
        if (params == null) {
            params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT
                    , ViewGroup.LayoutParams.WRAP_CONTENT);
        }

        // 获取宽度(外间距,内间距,view的宽度)
        int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, params.width);
        int pHeight = params.height;
        int childHeightSpec;

        //  MeasureSpec 它有三种模式:
        //  UNSPECIFIED(未指定),父元素部队自元素施加任何束缚,子元素可以得到任意想要的大小
        //  EXACTLY(完全),父元素决定自元素的确切大小,子元素将被限定在给定的边界里而忽略它本身大小
        //  AT_MOST(至多),子元素至多达到指定大小的值
        if (pHeight > 0) {
            childHeightSpec = MeasureSpec.makeMeasureSpec(pHeight, MeasureSpec.EXACTLY);
        } else {
            childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
        }
        view.measure(childWidthSpec, childHeightSpec);
    }


    /**
     * 拦截触摸事件
     *
     * @param e
     * @return
     */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent e) {
        int y = (int) e.getRawY();
        switch (e.getAction()) {
            case MotionEvent.ACTION_DOWN:// 按下时
                Log.e(TAG, "onInterceptTouchEvent-->ACTION_DOWN");
                mLastMotiony = y;
                break;
            case MotionEvent.ACTION_MOVE:// 移动时
                Log.e(TAG, "onInterceptTouchEvent-->ACTION_MOVE");
                int deltaY = y - mLastMotiony;
                return isRefreshViewScroll(deltaY);
        }
        return false;
    }

    /**
     * 触发事件
     *
     * @param e
     * @return
     */
    @Override
    public boolean onTouchEvent(MotionEvent e) {
        int y = (int) e.getRawY();
        switch (e.getAction()) {
            case MotionEvent.ACTION_DOWN:
                Log.e(TAG, "onTouchEvent-->ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.e(TAG, "onTouchEvent-->ACTION_MOVE");
                int deltaY = y - mLastMotiony;
                if (mPullState == PULL_DOWN_STATE) {
                    headerPrepareToRefresh(deltaY);
                } else if (mPullState == PULL_UP_STATE) {
                    footerPrepareToRefresh(deltaY);
                }
                mLastMotiony = y;
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                int topMargin = getHeadViewTopMargin();
                if (mPullState == PULL_DOWN_STATE) {
                    if (topMargin >= 0) {
                        headRefreshing();
                    } else {
                        setHeaderViewTopMargin(-mHeaderViewHeight);
                    }
                } else if (mPullState == PULL_UP_STATE) {
                    if (Math.abs(topMargin) > (mHeaderViewHeight + mFooterViewHeight)) {
                        footRefreshing();
                    } else {
                        setHeaderViewTopMargin(-mHeaderViewHeight);
                    }
                }
                break;
        }
        return super.onTouchEvent(e);
    }

    /**
     * 判断当前是否需要拦截触摸事件,做出上拉和下拉的反应
     *
     * @param delta
     * @return
     */
    private boolean isRefreshViewScroll(int delta) {
        //头部或者底部正在刷新中.....
        if (mHeadState == REFRESHING || mFootState == REFRESHING) {
            return false;
        }
        if (mAdapterView != null) {
            // 下拉
            if (delta > 1 && isHeadable) {
                View child = mAdapterView.getChildAt(0);
                // 判断adapterView里面是否含有东西
                // 如果为空的话,则返回false
                if (child == null) {
                    return false;
                }
                // child距离父容器顶部的距离?
                int top = child.getTop();
                // 如果AdapterView当前第一个可见的位置是数据的第一个,且处于AdapterView顶部
                if (mAdapterView.getFirstVisiblePosition() == 0
                        && top == 0) {
                    mPullState = PULL_DOWN_STATE;
                    return true;
                }
                int pTop = mAdapterView.getPaddingTop();
                if (mAdapterView.getFirstVisiblePosition() == 0
                        && Math.abs(pTop - top) <= 8) {// ????为什么小于8
                    mPullState = PULL_DOWN_STATE;
                    return true;
                }
            } else if (delta < 0 && isFootable) {// 上拉
                // 获取AdapterView里面子视图的个数
                int childCount = mAdapterView.getChildCount();
                // 判断其中视图个数是否小于1,防止getChildAt时出错
                if (childCount < 1) {
                    return false;
                }
                // 获取AdapterView中最后一个子视图
                View lastChild = mAdapterView.getChildAt(childCount - 1);
                if (lastChild == null) {
                    return false;
                }
                // 如果最后一个子视图距底部距离小于刷新控件的高度
                // AdapterView的最后一个显示的位置是最后一个子视图
                if (lastChild.getBottom() <= getHeight()
                        && mAdapterView.getLastVisiblePosition() == childCount - 1) {
                    mPullState = PULL_UP_STATE;
                    return true;
                }
            }
        } else if (mScrollView != null) {//滚动视图
            View child = mScrollView.getChildAt(0);
            if (child == null) {
                return false;
            }
            if (delta > 0 && mScrollView.getScrollY() == 0) {//处于顶部且做出下拉的动作
                mPullState = PULL_DOWN_STATE;
                return true;
            } else if (child.getMeasuredHeight() <= (getHeight() + mScrollView.getScrollY())
                    && delta < 0) {//处于底部且做出上拉的动作
                mPullState = PULL_UP_STATE;
                return true;
            }
        }
        return false;
    }

    /**
     * 头部准备刷新
     *
     * @param delta
     */
    private void headerPrepareToRefresh(int delta) {
        int newTopMargin = changeingHeaderViewTopMargin(delta);

        //当头部全部展示出来时,设置当前状态为释放去刷新
        if (newTopMargin >= 0 && mHeadState != RELEASE_TO_REFRESH) {
            mHeadState = RELEASE_TO_REFRESH;
        } else if (newTopMargin <= 0 && Math.abs(newTopMargin) > mHeaderViewHeight) {
            mHeadState = PULL_TO_REFRESH;
        }
    }

    /**
     * 底部准备刷新
     *
     * @param delta
     */
    private void footerPrepareToRefresh(int delta) {
        int newTopMargin = changeingHeaderViewTopMargin(delta);
        //如果底部拉开距离足够展示底部视图,则设置当前状态为释放刷新,反之则为拖动去刷新
        if (Math.abs(newTopMargin) > (mHeaderViewHeight + mFooterViewHeight)
                && mFootState != RELEASE_TO_REFRESH) {
            mFootState = RELEASE_TO_REFRESH;
        } else if (Math.abs(newTopMargin) <= (mHeaderViewHeight + mFooterViewHeight)) {
            mFootState = PULL_TO_REFRESH;
        }
    }

    /**
     * 改变头部视图距上距离
     */
    private int changeingHeaderViewTopMargin(int delta) {
        LayoutParams params = (LayoutParams) mHeaderView.getLayoutParams();
        if (delta > 0 && mPullState == PULL_UP_STATE
                && Math.abs(params.topMargin) <= mHeaderViewHeight) {
            return params.topMargin;
        }
        if (delta < 0 && mPullState == PULL_DOWN_STATE
                && Math.abs(params.topMargin) >= mHeaderViewHeight) {
            return params.topMargin;
        }
        float newTopMargin;
        if (mPullState == PULL_UP_STATE) {
            newTopMargin = params.topMargin + delta * FooterPower;
        } else if (mPullState == PULL_DOWN_STATE) {
            newTopMargin = params.topMargin + delta * HeaderPower;
        } else {
            newTopMargin = params.topMargin;
        }
        params.topMargin = (int) newTopMargin;
        mHeaderView.setLayoutParams(params);
        invalidate();
        return params.topMargin;
    }


    /**
     * 头部刷新
     */
    private void headRefreshing() {
        //设置当前状态为刷新中
        mHeadState = REFRESHING;
        //设置头部视图据顶部距离为0,使其恰好包括头部视图
        setHeaderViewTopMargin(0);
        if (onHeaderRefreshListener != null) {
            onHeaderRefreshListener.onHeaderRefresh(this);
        }
    }

    /**
     * 底部刷新
     */
    private void footRefreshing() {
        //设置当前状态为刷新中
        mFootState = REFRESHING;
        //获取头部视图和底部视图高度,设置头部距离,使其恰好包括底部视图
        int top = mHeaderViewHeight + mFooterViewHeight;
        setHeaderViewTopMargin(-top);
        if (onFooterRefreshListener != null) {
            onFooterRefreshListener.onFoorerRefresh(this);
        }
    }

    /**
     * 刷新完成
     */
    public void onRefreshComplete() {
        mHeadState = PULL_TO_REFRESH;
        mFootState = PULL_TO_REFRESH;
        setHeaderViewTopMargin(-mHeaderViewHeight);
    }

    /**
     * 头部刷新完成
     */
    public void onHeaderComplete() {
        mHeadState = PULL_TO_REFRESH;
        setHeaderViewTopMargin(-mHeaderViewHeight);
    }

    /**
     * 底部刷新完成
     */
    public void onFooterComplete() {
        mFootState = PULL_TO_REFRESH;
        setHeaderViewTopMargin(-mHeaderViewHeight);
    }

    /**
     * 获得头部视图距上距离
     *
     * @return
     */
    private int getHeadViewTopMargin() {
        LayoutParams params = (LayoutParams) mHeaderView.getLayoutParams();
        return params.topMargin;
    }

    /**
     * 设置头部视图距上距离
     */
    private void setHeaderViewTopMargin(int topMargin) {
        LayoutParams params = (LayoutParams) mHeaderView.getLayoutParams();
        params.topMargin = topMargin;
        mHeaderView.setLayoutParams(params);
    }

    /**
     * 下拉刷新事件
     */
    public interface onHeaderRefreshListener {
        void onHeaderRefresh(PullToRefresh view);
    }

    /**
     * 上拉刷新事件
     */
    public interface onFooterRefreshListener {
        void onFoorerRefresh(PullToRefresh view);
    }

    /**
     * 设置刷新是否可用
     *
     * @param isRefresh
     */
    public void setRefreshable(boolean isRefresh) {
        this.isHeadable = isRefresh;
        this.isFootable = isRefresh;
    }

    /**
     * 设置头部刷新是否可用
     *
     * @param isHeadable
     */
    public void setIsHeadable(boolean isHeadable) {
        this.isHeadable = isHeadable;
    }

    /**
     * 设置底部刷新是否可用
     *
     * @param isFootable
     */
    public void setIsFootable(boolean isFootable) {
        this.isFootable = isFootable;
    }

    /**
     * 设置头部拉动距离的有效度
     * @param headerPower
     */
    public void setHeaderPower(float headerPower) {
        HeaderPower = headerPower;
    }

    /**
     * 设置底部拉动距离的有效度
     * @param footerPower
     */
    public void setFooterPower(float footerPower) {
        FooterPower = footerPower;
    }
}



可能有的时候头部和底部视图需要自己设计,为了偷懒,我主要都是以图片为主,用progressbar作为轮播图片的控件

refresh_header内代码:

<?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="match_parent"
    android:gravity="center"
    android:orientation="vertical"
    android:paddingBottom="15dp"
    android:paddingTop="10dp">

    <ProgressBar
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:indeterminateDrawable="@anim/hz_progress_bottom" />
</LinearLayout>


主要起作用的是android:indeterminateDrawable="@anim/hz_progress_bottom"这句代码,这是一个不断轮换图片(其实是动画)的设置,

然后再看一眼这里面是怎么写的

<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="false">

    <item android:duration="40">
        <clip
            android:clipOrientation="horizontal"
            android:drawable="@mipmap/load_small_01"
            android:gravity="center" />
    </item>
    <item android:duration="40">
        <clip
            android:clipOrientation="horizontal"
            android:drawable="@mipmap/load_small_02"
            android:gravity="center" />
    </item>

   ......重复这里,这里和百度查到的不太一样,(百度里查到的经常是这种

<item android:drawable="@drawable/p1" android:duration="150" />
      <item android:drawable="@drawable/p2" android:duration="150" />
)但是会导致图片会根据大小进行重复和其他意外情况,特地又找了这种方法,至于这个的具体方式,暂时还不是很明白,求大神赐教...

</animation-list>


补充下:有的小伙伴可能直接拿过去就用了,不知道布局文件里怎么写,导致没有效果,现在补充一下,上拉和下拉是纵向的,这个就不多解释了

<com.cxd.pulltorefresh.PullToRefresh   android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <ScrollView
            android:layout_width="match_parent"
            android:layout_height="match_parent">
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"/>
        </ScrollView>
    </com.cxd.pulltorefresh.PullToRefresh>



今天就主要总结这个了,之所以是02,是因为01现存在草稿箱中,书写当天由于一些工作上的事情打断了,会尽快补上....



,




你可能感兴趣的:(android,viewpager,pulltorefresh)