我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Android笔记之:App列表之下拉刷新的使用

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Android笔记之:App列表之下拉刷新的使用

Android的ListView是应用最广的一个组件,功能强大,扩展性灵活(不局限于ListView本身一个类),前面的文章有介绍分组,拖拽,3D立体,游标,圆角,而今天我们要介绍的是另外一个扩展ListView:下拉刷新的ListView。
    下拉刷新界面最初流行于iphone应用界面,如图:


    然后在Android中也逐渐被应用,比如微博,资讯类。
    所以,今天要实现的结果应该也是类似的,先贴出最终完成效果,如下图,接下来我们一步一步实现。

 

1. 流程分析
    下拉刷新最主要的流程是:
    (1). 下拉,显示提示头部界面(HeaderView),这个过程提示用户"下拉刷新"
    (2). 下拉到一定程度,超出了刷新最基本的下拉界限,我们认为达到了刷新的条件,提示用户可以"松手刷新"了,效果上允许用户继续下拉
    (3). 用户松手,可能用户下拉远远不止提示头部界面,所以这一步,先反弹回仅显示提示头部界面,然后提示用户"正在加载"。
    (4). 加载完成后,隐藏提示头部界面。
    示意图如下:

->->

2. 实现分析
    当前我们要实现上述流程,是基于ListView的,所以对应ListView本身的功能我们来分析一下实现原理:
    (1). 下拉,显示提示头部界面,这个过程提示用户"下拉刷新"
        a. 下拉的操作,首先是监听滚动,ListView提供了onScroll()方法
        b. 与下拉类似一个动作向下飞滑,所以ListView的scrollState有3种值:SCROLL_STATE_IDLE, SCROLL_STATE_TOUCH_SCROLL, SCROLL_STATE_FLING,意思容易理解,而我们要下拉的触发条件是SCROLL_STATE_TOUCH_SCROLL。判断当前的下拉操作状态,ListView提供了public void onScrollStateChanged(AbsListView view, int scrollState) {}。
    c. 下拉的过程中,我们可能还需要下拉到多少的边界值处理,重写onTouchEvent(MotionEvent ev){}方法,可依据ACTION_DOWN,ACTION_MOVE,ACTION_UP实现更精细的判断。
    (2). 下拉到一定程度,超出了刷新最基本的下拉界限,我们认为达到了刷新的条件,提示用户可以"松手刷新"了,效果上允许用户继续下拉
        a. 达到下拉刷新界限,一般指达到header的高度的,所以有两步,第一,获取header的高度,第二,当header.getBottom()>=header的高度时,我们认为就达到了刷新界限值
        b. 继续允许用户下拉,当header完全下拉后,默认无法继续下拉,但是可以增加header的PaddingTop实现这种效果
    (3). 用户松手,可能用户下拉远远不止提示头部界面,所以这一步,先反弹回仅显示提示头部界面,然后提示用户"正在加载"。
        a. 松手后反弹,这个不能一下子弹回去,看上去太突然,需要一步一步柔性的弹回去,像弹簧一样,我们可以new一个Thread循环计算减少PaddingTop,直到PaddingTop为0,反弹结束。
        b. 正在加载,在子线程里处理后台任务
    (4). 加载完成后,隐藏提示头部界面。
        a. 后台任务完成后,我们需要隐藏header,setSelection(1)即实现了从第2项开始显示,间接隐藏了header。
上面我们分析了实现过程的轮廓,接下来,通过细节说明和代码具体实现。

3. 初始化
    一切状态显示都是用HeaderView显示的,所以我们需要一个HeaderView的layout,使用addHeaderView方法添加到ListView中。
    同时,默认状态下,HeaderView是不显示的,只是在下拉后才显示,所以我们需要隐藏HeaderView且不影响后续的下拉显示,用setSelection(1)。
    refresh_list_header.xml布局如下:
代码如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:gravity="center">
    <ProgressBar android:id="@+id/refresh_list_header_progressbar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        style="?android:attr/progressBarStyleSmall"
        android:visibility="gone">
    </ProgressBar>
    <ImageView android:id="@+id/refresh_list_header_pull_down"
        android:layout_width="9dip"
        android:layout_height="25dip"
        android:layout_gravity="center"
        android:class="lazy" data-src="@drawable/refresh_list_pull_down" />
    <ImageView android:id="@+id/refresh_list_header_release_up"
        android:layout_width="9dip"
        android:layout_height="25dip"
        android:layout_gravity="center"
        android:class="lazy" data-src="@drawable/refresh_list_release_up"
        android:visibility="gone" />
    <RelativeLayout android:layout_width="180dip"
        android:layout_height="wrap_content">
        <TextView android:id="@+id/refresh_list_header_text"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:layout_alignParentTop="true"
            android:textSize="12dip"
            android:textColor="#192F06"
            android:paddingTop="8dip"
            android:text="@string/app_list_header_refresh_down"/>
        <TextView android:id="@+id/refresh_list_header_last_update"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:layout_below="@id/refresh_list_header_text"
            android:textSize="12dip"
            android:textColor="#192F06"
            android:padding Bottom="8dip"
            android:text="@string/app_list_header_refresh_last_update"/>
    </RelativeLayout>
</LinearLayout>   

代码中在构造函数中添加init()方法加载如下:
代码如下:
private LinearLayout mHeaderLinearLayout = null;
private TextView mHeaderTextView = null;
private TextView mHeaderUpdateText = null;
private ImageView mHeaderPullDownImageView = null;
private ImageView mHeaderReleaseDownImageView = null;
private ProgressBar mHeaderProgressBar = null;
public RefreshListView(Context context) {
    this(context, null);
}
public RefreshListView(Context context, AttributeSet attrs) {
    super(context, attrs);
    init(context);
}
void init(final Context context) {
    mHeaderLinearLayout = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.refresh_list_header, null);
    addHeaderView(mHeaderLinearLayout);
    mHeaderTextView = (TextView) findViewById(R.id.refresh_list_header_text);
    mHeaderUpdateText = (TextView) findViewById(R.id.refresh_list_header_last_update);
    mHeaderPullDownImageView = (ImageView) findViewById(R.id.refresh_list_header_pull_down);
    mHeaderReleaseDownImageView = (ImageView) findViewById(R.id.refresh_list_header_release_up);
    mHeaderProgressBar = (ProgressBar) findViewById(R.id.refresh_list_header_progressbar);
    setSelection(1);
}

   默认就显示完成了。

4. HeaderView的默认高度测量
    因为下拉到HeaderView全部显示出来,就由提示"下拉刷新"变为"松手刷新",全部显示的出来的测量标准就是header.getBottom()>=header的高度。
    所以,首先我们需要测量HeaderView的默认高度。
代码如下:
//因为是在构造函数里测量高度,应该先measure一下
private void measureView(View child) {
    ViewGroup.LayoutParams p = child.getLayoutParams();
    if (p == null) {
        p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
                ViewGroup.LayoutParams.WRAP_CONTENT);
    }
    int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width);
    int lpHeight = p.height;
    int childHeightSpec;
    if (lpHeight > 0) {
        childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight,
                MeasureSpec.EXACTLY);
    } else {
        childHeightSpec = MeasureSpec.makeMeasureSpec(0,
                MeasureSpec.UNSPECIFIED);
    }
    child.measure(childWidthSpec, childHeightSpec);
}

然后在init的上述代码后面加上调用measureView后,使用getMeasureHeight()方法获取header的高度:
代码如下:
private int mHeaderHeight;
void init(final Context context) {
    ... ...
    measureView(mHeaderLinearLayout);
    mHeaderHeight = mHeaderLinearLayout.getMeasuredHeight();
}

  后面我们就会用到这个mHeaderHeight.

5. scrollState监听记录
    scrollState有3种,使用onScrollStateChanged()方法监听记录。
代码如下:
private int mCurrentScrollState;
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
    mCurrentScrollState = scrollState;
}

    然后即可使用mCurrentScrollState作为后面判断的条件了。

6. 刷新状态分析
    因为一些地方需要知道我们处在正常状态下还是进入下拉刷新状态还是松手反弹状态,比如,
    (1). 在非正常的状态下,我们不小心飞滑了一下(松手的瞬间容易出现这种情况),我们不能setSelection(1)的,否则总是松手后header跳的一下消失掉了。
    (2). 下拉后要做一个下拉效果的特殊处理,需要用到OVER_PULL_REFRESH(松手刷新状态下)
    (3). 松手反弹后要做一个反弹效果的特殊处理,需要用到OVER_PULL_REFRESH和ENTER_PULL_REFRESH。
代码如下:
private final static int NONE_PULL_REFRESH = 0;   //正常状态
private final static int ENTER_PULL_REFRESH = 1;  //进入下拉刷新状态
private final static int OVER_PULL_REFRESH = 2;   //进入松手刷新状态
private final static int EXIT_PULL_REFRESH = 3;     //松手后反弹后加载状态
private int mPullRefreshState = 0;                         //记录刷新状态
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
    if (mCurrentScrollState ==SCROLL_STATE_TOUCH_SCROLL
            && firstVisibleItem == 0
            && (mHeaderLinearLayout.getBottom() >= 0 && mHeaderLinearLayout.getBottom() < mHeaderHeight)) {
        //进入且仅进入下拉刷新状态
        if (mPullRefreshState == NONE_PULL_REFRESH) {
            mPullRefreshState = ENTER_PULL_REFRESH;
        }
    } else if (mCurrentScrollState ==SCROLL_STATE_TOUCH_SCROLL
            && firstVisibleItem == 0
            && (mHeaderLinearLayout.getBottom() >= mHeaderHeight)) {
        //下拉达到界限,进入松手刷新状态
        if (mPullRefreshState == ENTER_PULL_REFRESH || mPullRefreshState == NONE_PULL_REFRESH) {
            mPullRefreshState = OVER_PULL_REFRESH;
            //下面是进入松手刷新状态需要做的一个显示改变
            mDownY = mMoveY;//用于后面的下拉特殊效果
            mHeaderTextView.setText("松手刷新");
            mHeaderPullDownImageView.setVisibility(View.GONE);
            mHeaderReleaseDownImageView.setVisibility(View.VISIBLE);
        }
    } else if (mCurrentScrollState ==SCROLL_STATE_TOUCH_SCROLL && firstVisibleItem != 0) {
        //不刷新了
        if (mPullRefreshState == ENTER_PULL_REFRESH) {
            mPullRefreshState = NONE_PULL_REFRESH;
        }
    } else if (mCurrentScrollState == SCROLL_STATE_FLING && firstVisibleItem == 0) {
        //飞滑状态,不能显示出header,也不能影响正常的飞滑
        //只在正 常情况下才纠正位置
        if (mPullRefreshState == NONE_PULL_REFRESH) {
            setSelection(1);
        }
    }
}

  mPullRefreshState将是后面我们处理边界的重要变量。

6. 下拉效果的特殊处理
    所谓的特殊处理,当header完全显示后,下拉只按下拉1/3的距离下拉,给用户一种艰难下拉,该松手的弹簧感觉。
    这个在onTouchEvent里处理比较方便:
代码如下:
private float mDownY;
private float mMoveY;
@Override
public boolean onTouchEvent(MotionEvent ev) {
    switch (ev.getAction()) {
        case MotionEvent.ACTION_DOWN:
            //记下按下位置
            //改变
            mDownY = ev.getY();
            break;
        case MotionEvent.ACTION_MOVE:
            //移动时手指的位置
            mMoveY = ev.getY();
            if (mPullRefreshState == OVER_PULL_REFRESH) {
                //注意下面的mDownY在onScroll的第二个else中被改变了
                mHeaderLinearLayout.setPadding(mHeaderLinearLayout.getPaddingLeft(),
                        (int)((mMoveY - mDownY)/3), //1/3距离折扣
                        mHeaderLinearLayout.getPaddingRight(),
                        mHeaderLinearLayout.getPaddingBottom());
            }
            break;
        case MotionEvent.ACTION_UP:
            ... ...
            break;
    }
    return super.onTouchEvent(ev);
}
//重复贴出下面这段需要注意的代码
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
    ... ...
    else if (mCurrentScrollState == SCROLL_STATE_TOUCH_SCROLL
            && firstVisibleItem == 0
            && (mHeaderLinearLayout.getBottom() >= mHeaderHeight)) {
        //下拉达到界限,进入松手刷新状态
        if (mPullRefreshState == ENTER_PULL_REFRESH || mPullRefreshState == NONE_PULL_REFRESH) {
            mPullRefreshState = OVER_PULL_REFRESH;
            mDownY = mMoveY; //为下拉1/3折扣效果记录开始位置
            mHeaderTextView.setText("松手刷新");//显示松手刷新
            mHeaderPullDownImageView.setVisibility(View.GONE);//隐藏"下拉刷新"
            mHeaderReleaseDownImageView.setVisibility(View.VISIBLE);//显示向上的箭头
        }
    }
    ... ...< BR>}

  onScroll里监听到了进入松手刷新状态,onTouchEvent就开始在ACTION_MOVE中处理1/3折扣问题。

7. 反弹效果的特殊处理
    松手后我们需要一个柔性的反弹效果,意味着我们弹回去的过程需要分一步步走,我的解决方案是:
    在子线程里计算PaddingTop,并减少到原来的3/4,循环通知主线程,直到PaddingTop小于1(这个值取一个小值,合适即可)。
    松手后,当然是在onTouchEvent的ACTION_UP条件下处理比较方便:
代码如下:
//因为涉及到handler数据处理,为方便我们定义如下常量
private final static int REFRESH_BACKING = 0;      //反弹中
private final static int REFRESH_BACED = 1;        //达到刷新界限,反弹结束后
private final static int REFRESH_RETURN = 2;       //没有达到刷新界限,返回
private final static int REFRESH_DONE = 3;         //加载数据结束
@Override
public boolean onTouchEvent(MotionEvent ev) {
    switch (ev.getAction()) {
        ... ...
        case MotionEvent.ACTION_UP:
            //when you action up, it will do these:
            //1. roll back util header topPadding is 0
            //2. hide the header by setSelection(1)
            if (mPullRefreshState == OVER_PULL_REFRESH || mPullRefreshState == ENTER_PULL_REFRESH) {
                new Thread() {
                    public void run() {
                        Message msg;
                        while(mHeaderLinearLayout.getPaddingTop() > 1) {
                            msg = mHandler.obtainMessage();
                            msg.what = REFRESH_BACKING;
                            mHandler.sendMessage(msg);
                            try {
                                sleep(5);//慢一点反弹,别一下子就弹回去了
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
    &nbs p;                   msg = mHandler.obtainMessage();
                        if (mPullRefreshState == OVER_PULL_REFRESH) {
                            msg.what = REFRESH_BACED;//加载数据完成,结束返回
                        } else {
                            msg.what = REFRESH_RETURN;//未达到刷新界限,直接返回
                        }
                        mHandler.sendMessage(msg);
                    };
                }.start();
            }
            break;
    }
    return super.onTouchEvent(ev);
}
private Handler mHandler = new Handler(){
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
        case REFRESH_BACKING:
            mHeaderLinearLayout.setPadding(mHeaderLinearLayout.getPaddingLeft(),
                    (int) (mHeaderLinearLayout.getPaddingTop()*0.75f),
                    mHeaderLinearLayout.getPaddingRight(),
                    mHeaderLinearLayout.getPaddingBottom());
            break;
        case REFRESH_BACED:
            mHeaderTextView.setText("正在加载...");
            mHeaderProgressBar.setVisibility(View.VISIBLE);
            mHeaderPullDownImageView.setVisibility(View.GONE);
            mHeaderReleaseDownImageView.setVisibility(View.GONE);
            mPullRefreshState = EXIT_PULL_REFRESH;
            new Thread() {
                public void run() {
                    sleep(2000);//处理后台加载数据
                    Message msg = mHandler.obtainMessage();
                    msg.what = REFRESH_DONE;
                &nb sp;   //通知主线程加载数据完成
                    mHandler.sendMessage(msg);
                };
            }.start();
            break;
        case REFRESH_RETURN:
            //未达到刷新界限,返回
            mHeaderTextView.setText("下拉刷新");
            mHeaderProgressBar.setVisibility(View.INVISIBLE);
            mHeaderPullDownImageView.setVisibility(View.VISIBLE);
            mHeaderReleaseDownImageView.setVisibility(View.GONE);
            mHeaderLinearLayout.setPadding(mHeaderLinearLayout.getPaddingLeft(),
                    0,
                    mHeaderLinearLayout.getPaddingRight(),
                    mHeaderLinearLayout.getPaddingBottom());
            mPullRefreshState = NONE_PULL_REFRESH;
            setSelection(1);
            break;
        case REFRESH_DONE:
            //刷新结束后,恢复原始默认状态
            mHeaderTextView.setText("下拉刷新");
            mHeaderProgressBar.setVisibility(View.INVISIBLE);
            mHeaderPullDownImageView.setVisibility(View.VISIBLE);
            mHeaderReleaseDownImageView.setVisibility(View.GONE);
            mHeaderUpdateText.setText(getContext().getString(R.string.app_list_header_refresh_last_update,
                    mSimpleDateFormat.format(new Date())));
            mHeaderLinearLayout.setPadding(mHeaderLinearLayout.getPaddingLeft(),
                    0,
                    mHeaderLinearLayout.getPaddingRight(),
                    mHeaderLinearLayout.getPaddingBottom());
            mPullRefreshState = NONE_PULL_REFRESH;
            setSelection(1);
            break;
        default:
            break;
        }
    }
};

    为了一下子看的明确,我把效果中的数据处理代码也贴出来� ��。

8. 切入数据加载过程
    上面数据后台处理我们用sleep(2000)来处理,实际处理中,作为公共组件,我们也不好把具体代码直接写在这里,我们需要一个更灵活的分离:
    (1). 定义接口
    (2). 注入接口
代码如下:
//定义接口
public interface RefreshListener {
    Object refreshing();                //加载数据
    void refreshed(Object obj);    //外部可扩展加载完成后的操作
}
//注入接口
private Object mRefreshObject = null; //传值
private RefreshListener mRefreshListener = null;
public void setOnRefreshListener(RefreshListener refreshListener) {
    this.mRefreshListener = refreshListener;
}
 
//我们需要重写上面的mHandler如下代码
case REFRESH_BACED:
    ... ...
    new Thread() {
        public void run() {
            if (mRefreshListener != null) {
                mRefreshObject = mRefreshListener.refreshing();
            }
            Message msg = mHandler.obtainMessage();
            msg.what = REFRESH_DONE;
            mHandler.sendMessage(msg);
        };
    }.start();
    break;
case REFRESH_DONE:
    ... ...
    mPullRefreshState = NONE_PULL_REFRESH;
    setSelection(1);
    if (mRefreshListener != null) {
        mRefreshListener.refreshed(mRefreshObject);
    }
    break;

在其他地方我们就可以不修改这个listview组件的代码,使用如下:
代码如下:
public xxx implements RefreshListener{
@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //类似如下
        ((RefreshListView) listView).setOnRefreshListener(this);
    }
    @Override
    public Object refreshing() {
        String result = null;
        //result = FileUtils.readTextFile(file);
        return result;
    }
    @Override
    public void refreshed(Object obj) {
        if (obj != null) {
           //扩展操作
        }
    };
}

  很方便了。

9. 扩展"更多"功能
    下拉刷新之外,我们也可以通过相同方法使用FooterView切入底部"更多"过程,这里我就不详细说明了

10. 源码
    上面的每段代码都看做是"零部件",需要组合一下。
    因为我们上面实现了下拉刷新,还增加了"更多"功能,我们直接命名这个类为RefreshListView吧:
代码如下:
package com.tianxia.lib.baseworld.widget;
import java.text.SimpleDateFormat;
import java.util.Date;
import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.tianxia.lib.baseworld.R;

public class RefreshListView extends ListView implements OnScrollListener{
    private float mDownY;
    private float mMoveY;
    private int mHeaderHeight;
    private int mCurrentScrollState;
    private final static int NONE_PULL_REFRESH = 0;    //正常状态
    private final static int ENTER_PULL_REFRESH = 1;   //进入下拉刷新状态
    private final static int OVER_PULL_REFRESH = 2;    //进入松手刷新状态
    private final static int EXIT_PULL_REFRESH = 3;    //松手后反弹和加载状态
    private int mPullRefreshState = 0;                 //记录刷新状态
    private final static int REFRESH_BACKING = 0;      //反弹中
    private final static int REFRESH_BACED = 1;        //达到刷新界限,反弹结束后
    private final static int REFRESH_RETURN = 2;       //没有达到刷新界限,返回
    private final static int REFRESH_DONE = 3;         //加载数据结束
    private LinearLayout mHeaderLinearLayout = null;
    private LinearLayout mFooterLinearLayout = null;
    private TextView mHeaderTextView = null;
    private TextView mHeaderUpdateText = null;
    private ImageView mHeaderPullDownImageView = null;
    private ImageView mHeaderReleaseDownImageView = null;
    private ProgressBar mHeaderProgressBar = null;
    private TextView mFooterTextView = null;
    private ProgressBar mFooterProgressBar = null;
    private SimpleDateFormat mSimpleDateFormat;
    private Object mRefreshObject = null;
    private RefreshListener mRefreshListener = null;
    public void setOnRefreshListener(RefreshListener refreshListener) {
        this.mRefreshListener = refreshListener;
    }
    public RefreshListView(Context context) {
        this(context, null);
    }
    public RefreshListView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }
    void init(final Context context) {
        mHeaderLinearLayout = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.refresh _list_header, null);
        addHeaderView(mHeaderLinearLayout);
        mHeaderTextView = (TextView) findViewById(R.id.refresh_list_header_text);
        mHeaderUpdateText = (TextView) findViewById(R.id.refresh_list_header_last_update);
        mHeaderPullDownImageView = (ImageView) findViewById(R.id.refresh_list_header_pull_down);
        mHeaderReleaseDownImageView = (ImageView) findViewById(R.id.refresh_list_header_release_up);
        mHeaderProgressBar = (ProgressBar) findViewById(R.id.refresh_list_header_progressbar);
        mFooterLinearLayout = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.refresh_list_footer, null);
        addFooterView(mFooterLinearLayout);
        mFooterProgressBar = (ProgressBar) findViewById(R.id.refresh_list_footer_progressbar);
        mFooterTextView = (TextView) mFooterLinearLayout.findViewById(R.id.refresh_list_footer_text);
        mFooterLinearLayout.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                if (context.getString(R.string.app_list_footer_more).equals(mFooterTextView.getText())) {
                    mFooterTextView.setText(R.string.app_list_footer_loading);
                    mFooterProgressBar.setVisibility(View.VISIBLE);
                    if (mRefreshListener != null) {
                        mRefreshListener.more();
                    }
                }
            }
        });
        setSelection(1);
        setOnScrollListener(this);
        measureView(mHeaderLinearLayout);
        mHeaderHeight = mHeaderLinearLayout.getMeasuredHeight();
        mSimpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm");
        mHeaderUpdateText.setText(context.getString(R.string.app_list_header_refresh_last_update, mSimpleDateFormat.format(new Date())));
    }
    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mDownY = ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                mMoveY = ev.getY();
     ;            if (mPullRefreshState == OVER_PULL_REFRESH) {
                    mHeaderLinearLayout.setPadding(mHeaderLinearLayout.getPaddingLeft(),
                            (int)((mMoveY - mDownY)/3),
                            mHeaderLinearLayout.getPaddingRight(),
                            mHeaderLinearLayout.getPaddingBottom());
                }
                break;
            case MotionEvent.ACTION_UP:
                //when you action up, it will do these:
                //1. roll back util header topPadding is 0
                //2. hide the header by setSelection(1)
                if (mPullRefreshState == OVER_PULL_REFRESH || mPullRefreshState == ENTER_PULL_REFRESH) {
                    new Thread() {
                        public void run() {
                            Message msg;
                            while(mHeaderLinearLayout.getPaddingTop() > 1) {
                                msg = mHandler.obtainMessage();
                                msg.what = REFRESH_BACKING;
                                mHandler.sendMessage(msg);
                                try {
                                    sleep(5);
                                } catch (InterruptedException e) {
                                    e.printStackTrace();
            &n bsp;                   }
                            }
                            msg = mHandler.obtainMessage();
                            if (mPullRefreshState == OVER_PULL_REFRESH) {
                                msg.what = REFRESH_BACED;
                            } else {
                                msg.what = REFRESH_RETURN;
                            }
                            mHandler.sendMessage(msg);
                        };
                    }.start();
                }
                break;
        }
        return super.onTouchEvent(ev);
    }
    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        if (mCurrentScrollState == SCROLL_STATE_TOUCH_SCROLL
                && firstVisibleItem == 0
                && (mHeaderLinearLayout.getBottom() >= 0 && mHeaderLinearLayout.getBottom() < mHeaderHeight)) {
            //进入且仅进入下拉刷新状态
            if (mPullRefreshState == NONE_PULL_REFRESH) {
                mPullRefreshState = ENTER_PULL_REFRESH;
            }
        } else if (mCurrentScrollState == SCROLL_STATE_TOUCH_SCROLL
                && firstVisibleItem == 0
                && (mHeaderLinearLayout.getBottom() >= mHeaderHeight)) {
            //下拉达到界限,进入松手刷新状态
            if (mPullRefreshState == ENTER_PULL_REFRESH || mPullRefreshState == NONE_PULL_REFRESH) {
                mPullRefreshState = OVER_PULL_REFRESH;
                mDownY = mMoveY; //为下拉1/3折扣效果记录开始位置
                mHeaderTextView.setText("松手刷新");//显示松手刷新
                mHeaderPullDownImageView.setVisibility(View.GONE);//隐藏"下拉刷新"
                mHeaderReleaseDownImageView.setVisibility(View.VISIBLE);//显示向上的箭头
            }
        } else if (mCurrentScrollState == SCROLL_STATE_TOUCH_SCROLL && firstVisibleItem != 0) {
            //不刷新了
            if (mPullRefreshState == ENTER_PULL_REFRESH) {
                mPullRefreshState = NONE_PULL_REFRESH;
            }
        } else if (mCurrentScrollState == SCROLL_STATE_FLING && firstVisibleItem == 0) {
            //飞滑状态,不能显示出header,也不能影响正常的飞滑
            //只在正常情况下才纠正位置
            if (mPullRefreshState == NONE_PULL_REFRESH) {
                setSelection(1);
            }
        }
    }
    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        mCurrentScrollState = scrollState;
    }
    @Override
    public void setAdapter(ListAdapter adapter) {
        super.setAdapter(adapter);
        setSelection(1);
    }
    private void measureView(View child) {
        ViewGroup.LayoutParams p = child.getLayoutParams();
        if (p == null) {
            p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
                    ViewGroup.LayoutParams.WRAP_CONTENT);
        }
        int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width);
        int lpHeight = p.height;
        int childHeightSpec;
        if (lpHeight > 0) {
            childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight,
                    MeasureSpec.EXACTLY);
        } else {
            childHeightSpec = MeasureSpec.makeMeasureSpec(0,
                    MeasureSpec.UNSPECIFIED);
        }
         child.measure(childWidthSpec, childHeightSpec);
    }
    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
            case REFRESH_BACKING:
                mHeaderLinearLayout.setPadding(mHeaderLinearLayout.getPaddingLeft(),
                        (int) (mHeaderLinearLayout.getPaddingTop()*0.75f),
                        mHeaderLinearLayout.getPaddingRight(),
                        mHeaderLinearLayout.getPaddingBottom());
                break;
            case REFRESH_BACED:
                mHeaderTextView.setText("正在加载...");
                mHeaderProgressBar.setVisibility(View.VISIBLE);
                mHeaderPullDownImageView.setVisibility(View.GONE);
                mHeaderReleaseDownImageView.setVisibility(View.GONE);
                mPullRefreshState = EXIT_PULL_REFRESH;
                new Thread() {
                    public void run() {
                        if (mRefreshListener != null) {
                            mRefreshObject = mRefreshListener.refreshing();
                        }
                        Message msg = mHandler.obtainMessage();
                        msg.what = REFRESH_DONE;
                        mHandler.sendMessage(msg);
                    };
                }.start();
                break;
            case REFRESH_RETURN:
                mHeaderTextView.setText("下拉刷新");
                mHeaderProgressBar.setVisibility(View.INVISIBLE);
  &n bsp;             mHeaderPullDownImageView.setVisibility(View.VISIBLE);
                mHeaderReleaseDownImageView.setVisibility(View.GONE);
                mHeaderLinearLayout.setPadding(mHeaderLinearLayout.getPaddingLeft(),
                        0,
                        mHeaderLinearLayout.getPaddingRight(),
                        mHeaderLinearLayout.getPaddingBottom());
                mPullRefreshState = NONE_PULL_REFRESH;
                setSelection(1);
                break;
            case REFRESH_DONE:
                mHeaderTextView.setText("下拉刷新");
                mHeaderProgressBar.setVisibility(View.INVISIBLE);
                mHeaderPullDownImageView.setVisibility(View.VISIBLE);
                mHeaderReleaseDownImageView.setVisibility(View.GONE);
                mHeaderUpdateText.setText(getContext().getString(R.string.app_list_header_refresh_last_update,
                        mSimpleDateFormat.format(new Date())));
                mHeaderLinearLayout.setPadding(mHeaderLinearLayout.getPaddingLeft(),
                        0,
                        mHeaderLinearLayout.getPaddingRight(),
                        mHeaderLinearLayout.getPaddingBottom());
                mPullRefreshState = NONE_PULL_REFRESH;
                setSelection(1);
                if (mRefreshListener != null) {
                  &n


免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Android笔记之:App列表之下拉刷新的使用

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Android笔记之:App列表之下拉刷新的使用

Android的ListView是应用最广的一个组件,功能强大,扩展性灵活(不局限于ListView本身一个类),前面的文章有介绍分组,拖拽,3D立体,游标,圆角,而今天我们要介绍的是另外一个扩展ListView:下拉刷新的ListView
2022-06-06

Android App使用RecyclerView实现上拉和下拉刷新的方法

关于RecyclerView RecyclerView在Android 5.0以来被引入,以前经常使用的ListView 继承的是AbsListView,而RecyclerView则直接继承 ViewGroup,并实现了ScrollingV
2022-06-06

Android笔记之:App应用之启动界面SplashActivity的使用

当前比较成熟一点的应用基本上都会在进入应用之显示一个启动界面.这个启动界面或简单,或复杂,或简陋,或华丽,用意不同,风格也不同.下面来观摩几个流行的应用的启动界面. 1. 货比三家以腾讯qq,新浪weibo,UC浏览器,游戏神庙逃亡等7个应
2022-06-06

Android程序开发之使用PullToRefresh实现下拉刷新和上拉加载

PullToRefresh是一套实现非常好的下拉刷新库,它支持: 1.ListView 2.ExpandableListView 3.GridView 4.WebView 等多种常用的需要刷新的View类型,而且使用起来也十分方便。 (下载
2022-06-06

Android使用RecyclerView实现自定义列表、点击事件以及下拉刷新

Android使用RecyclerView1. 什么是RecyclerView RecyclerView 是 Android-support-v7-21 版本中新增的一个 Widgets,官方对于它的介绍则是:RecyclerView 是
2022-06-06

Android笔记之:App自动化之使用Ant编译项目多渠道打包的使用详解

随着工程越来越复杂,项目越来越多,以及平台的迁移(我最近就迁了2回),还有各大市场的发布,自动化编译android项目的需求越来越强烈,后面如果考虑做持续集成的话,会更加强烈。 经过不断的尝试,在ubuntu环境下,以花界为例,我将一
2022-06-06

编程热搜

  • Android:VolumeShaper
    VolumeShaper(支持版本改一下,minsdkversion:26,android8.0(api26)进一步学习对声音的编辑,可以让音频的声音有变化的播放 VolumeShaper.Configuration的三个参数 durati
    Android:VolumeShaper
  • Android崩溃异常捕获方法
    开发中最让人头疼的是应用突然爆炸,然后跳回到桌面。而且我们常常不知道这种状况会何时出现,在应用调试阶段还好,还可以通过调试工具的日志查看错误出现在哪里。但平时使用的时候给你闹崩溃,那你就欲哭无泪了。 那么今天主要讲一下如何去捕捉系统出现的U
    Android崩溃异常捕获方法
  • android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
    系统的设置–>电池–>使用情况中,统计的能耗的使用情况也是以power_profile.xml的value作为基础参数的1、我的手机中power_profile.xml的内容: HTC t328w代码如下:
    android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
  • Android SQLite数据库基本操作方法
    程序的最主要的功能在于对数据进行操作,通过对数据进行操作来实现某个功能。而数据库就是很重要的一个方面的,Android中内置了小巧轻便,功能却很强的一个数据库–SQLite数据库。那么就来看一下在Android程序中怎么去操作SQLite数
    Android SQLite数据库基本操作方法
  • ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
    工作的时候为了方便直接打开编辑文件,一些常用的软件或者文件我们会放在桌面,但是在ubuntu20.04下直接直接拖拽文件到桌面根本没有效果,在进入桌面后发现软件列表中的软件只能收藏到面板,无法复制到桌面使用,不知道为什么会这样,似乎并不是很
    ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
  • android获取当前手机号示例程序
    代码如下: public String getLocalNumber() { TelephonyManager tManager =
    android获取当前手机号示例程序
  • Android音视频开发(三)TextureView
    简介 TextureView与SurfaceView类似,可用于显示视频或OpenGL场景。 与SurfaceView的区别 SurfaceView不能使用变换和缩放等操作,不能叠加(Overlay)两个SurfaceView。 Textu
    Android音视频开发(三)TextureView
  • android获取屏幕高度和宽度的实现方法
    本文实例讲述了android获取屏幕高度和宽度的实现方法。分享给大家供大家参考。具体分析如下: 我们需要获取Android手机或Pad的屏幕的物理尺寸,以便于界面的设计或是其他功能的实现。下面就介绍讲一讲如何获取屏幕的物理尺寸 下面的代码即
    android获取屏幕高度和宽度的实现方法
  • Android自定义popupwindow实例代码
    先来看看效果图:一、布局
  • Android第一次实验
    一、实验原理 1.1实验目标 编程实现用户名与密码的存储与调用。 1.2实验要求 设计用户登录界面、登录成功界面、用户注册界面,用户注册时,将其用户名、密码保存到SharedPreference中,登录时输入用户名、密码,读取SharedP
    Android第一次实验

目录