android UI进阶之实现listview的下拉加载

来源:互联网 发布:陈暖央淘宝店是啥 编辑:程序博客网 时间:2024/05/12 14:33

关于listview的操作五花八门,有下拉刷新,分级显示,分页列表,逐页加载等,以后会陆续和大家分享这些技术,今天讲下下拉加载这个功能的实现。

最初的下拉加载应该是ios上的效果,现在很多应用如新浪微博等都加入了这个操作。即下拉listview刷新列表,这无疑是一个非常友好的操作。今天就和大家分享下这个操作的实现。

先看下运行效果:


   

 

   


代码参考国外朋友Johan Nilsson的实现,http://johannilsson.com/2011/03/13/android-pull-to-refresh-update.html。

主要原理为监听触摸和滑动操作,在listview头部加载一个视图。那要做的其实很简单:1.写好加载到listview头部的view 2.重写listview,实现onTouchEvent方法和onScroll方法,监听滑动状态。计算headview全部显示出来即可实行加载动作,加载完成即刷新列表。重新隐藏headview。

首先写下headview的xml代码:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="fill_parent"    android:layout_height="fill_parent"    android:paddingTop="10dip"    android:paddingBottom="15dip"    android:gravity="center"        android:id="@+id/pull_to_refresh_header"    >    <ProgressBar         android:id="@+id/pull_to_refresh_progress"        android:indeterminate="true"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_marginLeft="30dip"        android:layout_marginRight="20dip"        android:layout_marginTop="10dip"        android:visibility="gone"        android:layout_centerVertical="true"        style="?android:attr/progressBarStyleSmall"        />    <ImageView        android:id="@+id/pull_to_refresh_image"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_marginLeft="30dip"        android:layout_marginRight="20dip"        android:visibility="gone"        android:layout_gravity="center"        android:gravity="center"        android:src="@drawable/ic_pulltorefresh_arrow"        />    <TextView        android:id="@+id/pull_to_refresh_text"        android:textAppearance="?android:attr/textAppearanceMedium"        android:textStyle="bold"        android:paddingTop="5dip"        android:layout_width="fill_parent"        android:layout_height="wrap_content"        android:layout_gravity="center"        android:gravity="center"        />    <TextView        android:id="@+id/pull_to_refresh_updated_at"        android:layout_below="@+id/pull_to_refresh_text"        android:visibility="gone"        android:textAppearance="?android:attr/textAppearanceSmall"        android:layout_width="fill_parent"        android:layout_height="wrap_content"        android:layout_gravity="center"        android:gravity="center"        /></RelativeLayout>

代码比较简单,即headview包括一个进度条一个箭头和两段文字(一个显示加载状态,另一个显示最后刷新时间,本例就不设置了)。

而后重写listview,代码如下:

package com.notice.pullrefresh;import android.content.Context;import android.util.AttributeSet;import android.view.LayoutInflater;import android.view.MotionEvent;import android.view.View;import android.view.ViewGroup;import android.view.animation.LinearInterpolator;import android.view.animation.RotateAnimation;import android.widget.AbsListView;import android.widget.AbsListView.OnScrollListener;import android.widget.ImageView;import android.widget.ListAdapter;import android.widget.ListView;import android.widget.ProgressBar;import android.widget.RelativeLayout;import android.widget.TextView; public class PullToRefreshListView extends ListView implements OnScrollListener {// 状态    private static final int TAP_TO_REFRESH = 1;    private static final int PULL_TO_REFRESH = 2;    private static final int RELEASE_TO_REFRESH = 3;    private static final int REFRESHING = 4;    private OnRefreshListener mOnRefreshListener;    // 监听对listview的滑动动作    private OnScrollListener mOnScrollListener;    private LayoutInflater mInflater;    //顶部刷新时出现的控件    private RelativeLayout mRefreshView;    private TextView mRefreshViewText;    private ImageView mRefreshViewImage;    private ProgressBar mRefreshViewProgress;    private TextView mRefreshViewLastUpdated;// 当前滑动状态    private int mCurrentScrollState;// 当前刷新状态    private int mRefreshState;    // 箭头动画效果    private RotateAnimation mFlipAnimation;    private RotateAnimation mReverseFlipAnimation;    private int mRefreshViewHeight;    private int mRefreshOriginalTopPadding;    private int mLastMotionY;private boolean mBounceHack;    public PullToRefreshListView(Context context) {        super(context);        init(context);    }    public PullToRefreshListView(Context context, AttributeSet attrs) {        super(context, attrs);        init(context);    }    public PullToRefreshListView(Context context, AttributeSet attrs, int defStyle) {        super(context, attrs, defStyle);        init(context);    }    /**     * 初始化控件和箭头动画(这里直接在代码中初始化动画而不是通过xml)     */    private void init(Context context) {        mFlipAnimation = new RotateAnimation(0, -180,                RotateAnimation.RELATIVE_TO_SELF, 0.5f,                RotateAnimation.RELATIVE_TO_SELF, 0.5f);        mFlipAnimation.setInterpolator(new LinearInterpolator());        mFlipAnimation.setDuration(250);        mFlipAnimation.setFillAfter(true);        mReverseFlipAnimation = new RotateAnimation(-180, 0,                RotateAnimation.RELATIVE_TO_SELF, 0.5f,                RotateAnimation.RELATIVE_TO_SELF, 0.5f);        mReverseFlipAnimation.setInterpolator(new LinearInterpolator());        mReverseFlipAnimation.setDuration(250);        mReverseFlipAnimation.setFillAfter(true);        mInflater = (LayoutInflater) context.getSystemService(                Context.LAYOUT_INFLATER_SERVICE);mRefreshView = (RelativeLayout) mInflater.inflate(R.layout.pull_to_refresh_header, this, false);        mRefreshViewText =            (TextView) mRefreshView.findViewById(R.id.pull_to_refresh_text);        mRefreshViewImage =            (ImageView) mRefreshView.findViewById(R.id.pull_to_refresh_image);        mRefreshViewProgress =            (ProgressBar) mRefreshView.findViewById(R.id.pull_to_refresh_progress);        mRefreshViewLastUpdated =            (TextView) mRefreshView.findViewById(R.id.pull_to_refresh_updated_at);        mRefreshViewImage.setMinimumHeight(50);        mRefreshOriginalTopPadding = mRefreshView.getPaddingTop();        mRefreshState = TAP_TO_REFRESH;                //为listview头部增加一个view        addHeaderView(mRefreshView);        super.setOnScrollListener(this);measureView(mRefreshView);        mRefreshViewHeight = mRefreshView.getMeasuredHeight();    }    @Override    protected void onAttachedToWindow() {        setSelection(1);    }    @Override    public void setAdapter(ListAdapter adapter) {        super.setAdapter(adapter);        setSelection(1);    }    /**     * 设置滑动监听器     *      */    @Override    public void setOnScrollListener(AbsListView.OnScrollListener l) {        mOnScrollListener = l;    }    /**     * 注册一个list需要刷新时的回调接口     *      */    public void setOnRefreshListener(OnRefreshListener onRefreshListener) {        mOnRefreshListener = onRefreshListener;    }    /** * 设置标签显示何时最后被刷新 *  * @param lastUpdated *            Last updated at. */    public void setLastUpdated(CharSequence lastUpdated) {        if (lastUpdated != null) {            mRefreshViewLastUpdated.setVisibility(View.VISIBLE);            mRefreshViewLastUpdated.setText(lastUpdated);        } else {            mRefreshViewLastUpdated.setVisibility(View.GONE);        }    }// 实现该方法处理触摸    @Override    public boolean onTouchEvent(MotionEvent event) {        final int y = (int) event.getY();        mBounceHack = false;        switch (event.getAction()) {            case MotionEvent.ACTION_UP:                if (!isVerticalScrollBarEnabled()) {                    setVerticalScrollBarEnabled(true);                }                if (getFirstVisiblePosition() == 0 && mRefreshState != REFRESHING) {// 拖动距离达到刷新需要                    if ((mRefreshView.getBottom() >= mRefreshViewHeight                            || mRefreshView.getTop() >= 0)                            && mRefreshState == RELEASE_TO_REFRESH) {// 把状态设置为正在刷新                        mRefreshState = REFRESHING;// 准备刷新                        prepareForRefresh();// 刷新                        onRefresh();                    } else if (mRefreshView.getBottom() < mRefreshViewHeight                            || mRefreshView.getTop() <= 0) {// 中止刷新                        resetHeader();                        setSelection(1);                    }                }                break;            case MotionEvent.ACTION_DOWN:// 获得按下y轴位置                mLastMotionY = y;                break;            case MotionEvent.ACTION_MOVE:// 计算边距                applyHeaderPadding(event);                break;        }        return super.onTouchEvent(event);    }// 获得header的边距    private void applyHeaderPadding(MotionEvent ev) {        int pointerCount = ev.getHistorySize();        for (int p = 0; p < pointerCount; p++) {            if (mRefreshState == RELEASE_TO_REFRESH) {                if (isVerticalFadingEdgeEnabled()) {                    setVerticalScrollBarEnabled(false);                }                int historicalY = (int) ev.getHistoricalY(p);// 计算申请的边距,除以1.7使得拉动效果更好                int topPadding = (int) (((historicalY - mLastMotionY)                        - mRefreshViewHeight) / 1.7);                mRefreshView.setPadding(                        mRefreshView.getPaddingLeft(),                        topPadding,                        mRefreshView.getPaddingRight(),                        mRefreshView.getPaddingBottom());            }        }    }    /** * 将head的边距重置为初始的数值 */    private void resetHeaderPadding() {        mRefreshView.setPadding(                mRefreshView.getPaddingLeft(),                mRefreshOriginalTopPadding,                mRefreshView.getPaddingRight(),                mRefreshView.getPaddingBottom());    }    /** * 重置header为之前的状态 */    private void resetHeader() {        if (mRefreshState != TAP_TO_REFRESH) {            mRefreshState = TAP_TO_REFRESH;            resetHeaderPadding();// 将刷新图标换成箭头            mRefreshViewImage.setImageResource(R.drawable.ic_pulltorefresh_arrow);// 清除动画            mRefreshViewImage.clearAnimation();// 隐藏图标和进度条            mRefreshViewImage.setVisibility(View.GONE);            mRefreshViewProgress.setVisibility(View.GONE);        }    }// 估算headview的width和height    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);    }    @Override    public void onScroll(AbsListView view, int firstVisibleItem,            int visibleItemCount, int totalItemCount) {// 在refreshview完全可见时,设置文字为松开刷新,同时翻转箭头        if (mCurrentScrollState == SCROLL_STATE_TOUCH_SCROLL                && mRefreshState != REFRESHING) {            if (firstVisibleItem == 0) {                mRefreshViewImage.setVisibility(View.VISIBLE);                if ((mRefreshView.getBottom() >= mRefreshViewHeight + 20                        || mRefreshView.getTop() >= 0)                        && mRefreshState != RELEASE_TO_REFRESH) {mRefreshViewText.setText("松开加载...");                    mRefreshViewImage.clearAnimation();                    mRefreshViewImage.startAnimation(mFlipAnimation);                    mRefreshState = RELEASE_TO_REFRESH;                } else if (mRefreshView.getBottom() < mRefreshViewHeight + 20                        && mRefreshState != PULL_TO_REFRESH) {mRefreshViewText.setText("下拉刷新...");                    if (mRefreshState != TAP_TO_REFRESH) {                        mRefreshViewImage.clearAnimation();                        mRefreshViewImage.startAnimation(mReverseFlipAnimation);                    }                    mRefreshState = PULL_TO_REFRESH;                }            } else {                mRefreshViewImage.setVisibility(View.GONE);                resetHeader();            }        } else if (mCurrentScrollState == SCROLL_STATE_FLING                && firstVisibleItem == 0                && mRefreshState != REFRESHING) {            setSelection(1);mBounceHack = true;} else if (mBounceHack && mCurrentScrollState == SCROLL_STATE_FLING) {            setSelection(1);        }        if (mOnScrollListener != null) {            mOnScrollListener.onScroll(view, firstVisibleItem,                    visibleItemCount, totalItemCount);        }    }    @Override    public void onScrollStateChanged(AbsListView view, int scrollState) {        mCurrentScrollState = scrollState;        if (mCurrentScrollState == SCROLL_STATE_IDLE) {            mBounceHack = false;        }        if (mOnScrollListener != null) {            mOnScrollListener.onScrollStateChanged(view, scrollState);        }    }    public void prepareForRefresh() {resetHeaderPadding();// 恢复header的边距        mRefreshViewImage.setVisibility(View.GONE);// 注意加上,否则仍然显示之前的图片        mRefreshViewImage.setImageDrawable(null);        mRefreshViewProgress.setVisibility(View.VISIBLE);// 设置文字mRefreshViewText.setText("加载中...");        mRefreshState = REFRESHING;    }    public void onRefresh() {        if (mOnRefreshListener != null) {            mOnRefreshListener.onRefresh();        }    }    /** * 重置listview为普通的listview,该方法设置最后更新时间 *  * @param lastUpdated *            Last updated at. */    public void onRefreshComplete(CharSequence lastUpdated) {        setLastUpdated(lastUpdated);        onRefreshComplete();    }    /** * 重置listview为普通的listview,不设置最后更新时间 */    public void onRefreshComplete() {                resetHeader();// 如果refreshview在加载结束后可见,下滑到下一个条目        if (mRefreshView.getBottom() > 0) {            invalidateViews();            setSelection(1);        }    }    /** * 刷新监听器接口 */    public interface OnRefreshListener {        /** * list需要被刷新时调用 */        public void onRefresh();    }}

相信我注释已经写的比较详细了,主要注意onTouchEvent和onScroll方法,在这里面计算头部边距,从而通过用户的手势实现“下拉刷新”到“松开加载”以及“加载”三个状态的切换。其中还有一系列和header有关的方法,用来设置header的显示以及取得header的边距。于此同时,代码留出了接口以供调用。

那么现在写一个测试Activity来试验下效果:

package com.notice.pullrefresh;import java.util.Arrays;import java.util.LinkedList;import android.app.ListActivity;import android.os.AsyncTask;import android.os.Bundle;import android.widget.ArrayAdapter;import com.notice.pullrefresh.PullToRefreshListView.OnRefreshListener;public class PullrefreshActivity extends ListActivity {private LinkedList<String> mListItems;ArrayAdapter<String> adapter;/** Called when the activity is first created. */    @Override    public void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);setContentView(R.layout.pull_to_refresh);// list需要刷新时调用((PullToRefreshListView) getListView()).setOnRefreshListener(new OnRefreshListener() {@Overridepublic void onRefresh() {// 在这执行后台工作new GetDataTask().execute();}});mListItems = new LinkedList<String>();mListItems.addAll(Arrays.asList(mStrings));adapter = new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1, mListItems);setListAdapter(adapter);}private class GetDataTask extends AsyncTask<Void, Void, String[]> {@Overrideprotected String[] doInBackground(Void... params) {// 在这里可以做一些后台工作try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}return mStrings;}@Overrideprotected void onPostExecute(String[] result) {// 下拉后增加的内容mListItems.addFirst("Added after refresh...");// 刷新完成调用该方法复位((PullToRefreshListView) getListView()).onRefreshComplete();super.onPostExecute(result);}}private String[] mStrings = { "normal data1", "normal data2","nomal data3", "normal data4", "norma data5", "normal data6" };}

代码通过asyncTask实现一个异步操作,并通过设置onRefreshListener监听器调用onRefresh方法实现下拉时刷新,并在刷新完成后调用onRefreshComplete做复位处理。

今天就和大家分享这些,有问题欢迎留言交流。







原创粉丝点击