ListView下拉刷新

来源:互联网 发布:python爬虫爬取知乎 编辑:程序博客网 时间:2024/05/21 06:58

这是一个下拉刷新的ListView,它不是一个复合控件,而是继承了ListView然后进行了改写.

这里要说明一点,其实用复合控件去实现下拉刷新是最简单的方式,但由于复合控件继承的通常是layout,所以并不是真正意义上的ListView.而且多个相同的复合控件在同一个fragment页面里是会出问题的.

所以说,直接继承ListView去制作一个下拉控件,是相对比较复杂,但比较安全的方式.它的重用性更强.


现在大部分下拉刷新控件是不隔离下拉操作和列表滚动操作的,也就是说当列表拉到顶部之后,如果继续拉就直接刷新了.

我个人不是很喜欢这样的操作,我希望滚到顶的时候能停住,就像一个普通的ListView一样.只有松开手指再次下拉,才能刷新.这也是我写这个控件的初衷.


源码及Demo在这里:

http://download.csdn.net/detail/qyc898/7556559

源码里"加载更多"功能没有更新进去,但下面的代码是更新了的.可以直接复制下面ListPullDown类的代码,然后替换掉Demo里的代码.


 

头部ITEM的XML布局,注意,这个布局一定要LinearLayout,用相对布局在拖动时会被自动排版,失去效果

pulldownlist_top.xml

<?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="wrap_content" >    <RelativeLayout        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:layout_marginBottom="5dp"        android:layout_marginTop="5dp" >        <TextView            android:id="@+id/textView1"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_centerHorizontal="true"            android:text="下拉刷新"            android:textColor="#000000"            android:textSize="14sp" />        <TextView            android:id="@+id/textView2"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_below="@+id/textView1"            android:layout_centerHorizontal="true"            android:text="上一次更新时间:未更新"            android:textSize="12sp" />        <ImageView            android:id="@+id/imageView1"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_centerVertical="true"            android:layout_marginLeft="70dp"            android:src="@drawable/refresh_down" />        <ProgressBar            android:id="@+id/progressBar1"            style="?android:attr/progressBarStyleSmall"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_centerVertical="true"            android:layout_marginLeft="70dp" />    </RelativeLayout></LinearLayout>


底部ITEM布局

pulldownlist_bottom.xml

<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="wrap_content"    android:background="@drawable/st_pulldownlist_click"    android:paddingBottom="6dp"    android:paddingTop="6dp" >    <TextView        android:id="@+id/textView1"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_centerInParent="true"        android:textColor="#FF000000"        android:textSize="16sp" />    <ProgressBar        android:id="@+id/progressBar1"        style="?android:attr/progressBarStyleSmall"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_centerVertical="true"        android:layout_marginLeft="70dp" /></RelativeLayout>


 

自定义属性attrs.xml

    <declare-styleable name="PullDownList">        <attr name="loadingMoreTextColor" format="color" />        <attr name="loadingMoreTextSize" format="dimension" />        <attr name="loadingMoreBKColor" format="reference|color" />        <attr name="refreshTextColor" format="color" />        <attr name="refreshTextSize" format="dimension" />        <attr name="refreshTimeColor" format="color" />        <attr name="refreshTimeSize" format="dimension" />        <attr name="refreshArrowUp" format="reference" />        <attr name="refreshArrowDown" format="reference" />    </declare-styleable>


代码:

package qyc.library.control.list_pulldown;import java.text.SimpleDateFormat;import java.util.Calendar;import java.util.Locale;import qyc.library.R;import android.content.Context;import android.content.res.TypedArray;import android.graphics.Color;import android.util.AttributeSet;import android.util.TypedValue;import android.view.LayoutInflater;import android.view.MotionEvent;import android.view.View;import android.view.ViewGroup;import android.widget.AbsListView;import android.widget.ArrayAdapter;import android.widget.ImageView;import android.widget.ListView;import android.widget.ProgressBar;import android.widget.TextView;/** * <p> * 作者:QYC * <p> * 邮件:68452263@qq.com * <p> * */public class ListPullDown extends ListView {public ListPullDown(Context context) {super(context);if (isInEditMode()) {inEditMode();} else {init(null);}}public ListPullDown(Context context, AttributeSet attrs) {super(context, attrs);if (isInEditMode()) {inEditMode();} else {init(attrs);}}public ListPullDown(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);if (isInEditMode()) {inEditMode();} else {init(attrs);}}// 在可视化编辑器里面显示的内容private void inEditMode() {if (isInEditMode()) {setBackgroundColor(Color.rgb(134, 154, 190));ArrayAdapter<String> adapter = new ArrayAdapter<String>(getContext(), android.R.layout.simple_list_item_1);adapter.add("PullDownList");adapter.add("作者:QYC");adapter.add("邮箱:68452263@qq.com");setAdapter(adapter);return;}}/* * ==================列表顶部的控件集合================== */private ProgressBar progressBar_top = null;private ImageView imageView_top = null;private TextView txtTime_top, txtRefresh_top;/** 列表顶部的视图 */private View topViewItem = null;/** 列表顶部视图的高 */private int topViewItemHeight = 0;/** 刷新向下箭头 */private int imgRefreshDown = R.drawable.png_listpulldown_refreshdown;/** 刷新向上箭头 */private int imgRefreshUp = R.drawable.png_listpulldown_refreshup;/* * ===================================================== *//* * ==================列表底部的控件集合================== */private View bottomViewItem = null;private ProgressBar progressBar_bottom = null;private TextView txtBottom = null;/* * ===================================================== *//* * ==================用于逻辑判断的4个变量================== *//** 第一行是否在屏幕上 */private boolean isFirstItemShow = false;// 第一行即加载行,只要把列表滚动到顶,不管加载行被压缩了还是被拉开了,都会认为显示在了屏幕上/** 是否有加载意图,即加载第一行的意图 */private boolean isWantToExtrude = true;/** 是否已经拉开 */private boolean isExtruded = false;/** 是否已经在加载 */private boolean isLoading = false;/* * ===================================================== *//** 时间格式化 */private static final SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss", Locale.CHINA);/* * ===================================================== *//** 初始化 */private void init(AttributeSet attrs) {setOnScrollListener(onScrollListener);LayoutInflater layoutInflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);// 初始化列表底部工具条bottomViewItem = layoutInflater.inflate(R.layout.listpulldown_bottom,this, false);progressBar_bottom = (ProgressBar) bottomViewItem.findViewById(R.id.progressBar1);txtBottom = (TextView) bottomViewItem.findViewById(R.id.textView1);bottomViewItem.setOnClickListener(onClickListener);// 设置点击监听bottomViewItem.setVisibility(View.GONE);// 默认状态下不显示加载更多的动作条// 加入底部工具条(加载更多)addFooterView(bottomViewItem);// 初始化列表顶部工具条topViewItem = layoutInflater.inflate(R.layout.listpulldown_top, this,false);topViewItem.setOnClickListener(null);// 使它不能作为list的一个item来点击progressBar_top = (ProgressBar) topViewItem.findViewById(R.id.progressBar1);progressBar_top.setVisibility(View.INVISIBLE);imageView_top = (ImageView) topViewItem.findViewById(R.id.imageView1);txtTime_top = (TextView) topViewItem.findViewById(R.id.textView2);txtTime_top.setText("上一次更新时间:无");txtRefresh_top = (TextView) topViewItem.findViewById(R.id.textView1);// 根据兹定于属性,对控件样式进行调整setResValue(attrs);/* * 3步隐藏该topViewItem: 1.测出其加入屏幕后的长和宽,因为现在它没加入屏幕,直接获取其长宽是无效的 2.得到测出的高度 * 3.将上边距设置为负高度,相当于把高度变成了0,达到隐藏的效果(顺带一提:topViewItem即使通过padding设置其高度为0 * ,在列表中,它仍然会占据一行的像素,列表仍然认为它是第0行) */// 第一步measureView(topViewItem);// 第二步topViewItemHeight = topViewItem.getMeasuredHeight();// 第三步topViewItem.setPadding(0, 0 - topViewItemHeight, 0, 0);// 加入到列表中(点击刷新)addHeaderView(topViewItem);}/** 根据自定义属性,对控件样式进行调整 */private void setResValue(AttributeSet attrs) {// 获取控件自定义属性值TypedArray a = getContext().obtainStyledAttributes(attrs,R.styleable.ListPullDown);int N = a.getIndexCount();// 自定义属性被使用的数量for (int i = 0; i < N; i++) {int attr = a.getIndex(i);switch (attr) {case R.styleable.ListPullDown_loadingMoreBKColor:// 底部工具条背景色int loadingMoreBKColorID = a.getResourceId(attr, 0);if (loadingMoreBKColorID != 0) {bottomViewItem.setBackgroundResource(loadingMoreBKColorID);} else {int loadingMoreBKColor = a.getColor(attr, 0);if (loadingMoreBKColor != 0) {bottomViewItem.setBackgroundColor(loadingMoreBKColor);}}break;case R.styleable.ListPullDown_loadingMoreTextColor:// 底部工具条文字颜色int loadingMoreTextColor = a.getColor(attr, 0);if (loadingMoreTextColor != 0) {txtBottom.setTextColor(loadingMoreTextColor);}break;case R.styleable.ListPullDown_loadingMoreTextSize:// 底部工具条文字大小float loadingMoreTextSize = a.getDimension(attr, 0);if (loadingMoreTextSize != 0) {txtBottom.setTextSize(TypedValue.COMPLEX_UNIT_PX,loadingMoreTextSize);}break;case R.styleable.ListPullDown_refreshTextColor:// 顶部工具条提示文字颜色int refreshTextColor = a.getColor(attr, 0);if (refreshTextColor != 0) {txtRefresh_top.setTextColor(refreshTextColor);}break;case R.styleable.ListPullDown_refreshTextSize:// 顶部工具条提示文字大小float refreshTextSize = a.getDimension(attr, 0);if (refreshTextSize != 0) {txtRefresh_top.setTextSize(TypedValue.COMPLEX_UNIT_PX,refreshTextSize);}break;case R.styleable.ListPullDown_refreshTimeColor:// 顶部工具条时间文字颜色int refreshTimeColor = a.getColor(attr, 0);if (refreshTimeColor != 0) {txtTime_top.setTextColor(refreshTimeColor);}break;case R.styleable.ListPullDown_refreshTimeSize:// 顶部工具条时间文字大小float refreshTimeSize = a.getDimension(attr, 0);if (refreshTimeSize != 0) {txtTime_top.setTextSize(TypedValue.COMPLEX_UNIT_PX,refreshTimeSize);}break;case R.styleable.ListPullDown_refreshArrowUp:// 顶部工具条向上箭头int refreshArrowUp = a.getResourceId(attr, 0);if (refreshArrowUp != 0) {this.imgRefreshUp = refreshArrowUp;}break;case R.styleable.ListPullDown_refreshArrowDown:// 顶部工具条向下箭头int refreshArrowDown = a.getResourceId(attr, 0);if (refreshArrowDown != 0) {this.imgRefreshDown = refreshArrowDown;}break;default:break;}}a.recycle();}// 列表滚动事件private android.widget.AbsListView.OnScrollListener onScrollListener = new OnScrollListener() {/* * 当列表以任何形式(自动滚或者手拖着滚)停止滚动后,如果第一行已经出现了,那么认为已经有了加载的意图 * 如果第一行没有出现,那么认为没有加载的意图 */@Overridepublic void onScrollStateChanged(AbsListView view, int scrollState) {if (isLoading)// 已经在加载则不作任何处理{return;}// 当列表以任何形式(自动滚或者手拖着滚)停止滚动时if (scrollState == SCROLL_STATE_IDLE) {if (isFirstItemShow) {isWantToExtrude = true;// System.out.println("意图被onScrollStateChanged置为true");} else {isWantToExtrude = false;// System.out.println("意图被onScrollStateChanged置为false");}}}/* * 在列表滚动过程中,根据屏幕最顶行是不是第0行,来判断:第0行是否出现在屏幕上,是否存在加载的意图 */@Overridepublic void onScroll(AbsListView view, int firstVisibleItem,int visibleItemCount, int totalItemCount) {if (isLoading)// 已经在加载则不作任何处理{return;}if (firstVisibleItem == 0) {isFirstItemShow = true;// System.out.println("isFirstItemShow被onScroll置为true");} else {isFirstItemShow = false;isWantToExtrude = false;// System.out.println("意图被onScroll置为false");}}};/** 上一次手指触摸屏幕的Y轴坐标 */private float lastActionDown_Y = 0;// 触摸事件@Overridepublic boolean onTouchEvent(MotionEvent ev) {if (isLoading)// 已经在加载则不作任何处理{return super.onTouchEvent(ev);}int action = ev.getAction();if (action == MotionEvent.ACTION_DOWN) {// 按下手指时,获得按下y轴位置,用于在拖动时计算手指拖动距离lastActionDown_Y = ev.getY();} else if (action == MotionEvent.ACTION_MOVE) {/* * 1.如果有加载的意图,则根据触摸屏Y轴的移动来判断. * 2.Y轴移动值为正说明在有拉伸意图的情况下,操作者进行了拉伸,所以应该根据拉伸的幅度来拉伸第一行,并且屏蔽其他控件的触摸屏事件。 * 3.Y轴移动值变成负,说明操作者已经顶回去,第一行也已经被顶没..这时不再屏蔽触摸屏事件,直到ListView将加载意图至为负值。 * 或操作者再次拉伸. 4.当不再有加载的意图,则有两种可能。一种是平常的触摸,不用作任何处理。 * 另一种是当时已经拉开了,用户又不想拉了,所以又向上推了回去 推回去后,加载意图被onScroll函数制为false。 * 在这种情况下,应该把第一行重新压缩回原状。 */// 手指滑动的距离float gapValue_y = ev.getY() - lastActionDown_Y;gapValue_y = gapValue_y * 2 / 3;// 减慢下拉速度,比真实的手指滑动速度略慢if (isWantToExtrude) {if (gapValue_y >= 0) {isExtruded = true;// 新的PaddingTop应该是滑动的距离减去总高,才会使加载行有慢慢拉宽的效果int newPaddingTop = (int) (gapValue_y - topViewItemHeight);topViewItem.setPadding(0, newPaddingTop, 0, 0);// 如果新的PaddingTop大于等于20,说明已经正常显示的情况下还多拉了20像素,这就足够了,提示用户松开的话是可以更新的if (newPaddingTop >= 20) {setTopViewControl(2);} else {setTopViewControl(1);}// 屏蔽return false;}} else {// 用户此刻已经不想加载更多// 可能顶部工具条已经消失不见,也可能拖到一半if (isExtruded) {// 拖到一半,复原isExtruded = false;topViewItem.setPadding(0, 0 - topViewItemHeight, 0, 0);}}} else if (action == MotionEvent.ACTION_UP) {if (isExtruded) {if (topViewItem.getPaddingTop() >= 20) {if (this.onPDListListener != null) {isLoading = true;topViewItem.setPadding(0, 0, 0, 0);setTopViewControl(3);this.onPDListListener.onRefresh();} else {topViewItem.setPadding(0, 0 - topViewItemHeight, 0, 0);isExtruded = false;}} else {topViewItem.setPadding(0, 0 - topViewItemHeight, 0, 0);isExtruded = false;}}}return super.onTouchEvent(ev);}// 提升性能,防止无意义的重复赋值private int controlState = -1;/** * 用户是否正在往下拉 *  * @return */public boolean isExtruded() {return isExtruded;}/* * ==================顶部工具条部分的私有函数================= *//** * 设置顶部工具条控件的显示内容 *  * @param state *            0表示被用户重置,1表示下拉刷新,2表示松开刷新,3表示正在刷新 */private void setTopViewControl(int state) {if (controlState != state) {System.out.println("state=" + state);controlState = state;if (state == 0) {imageView_top.setVisibility(View.VISIBLE);progressBar_top.setVisibility(View.INVISIBLE);} else if (state == 1) {txtRefresh_top.setText("下拉更新");imageView_top.setImageResource(imgRefreshDown);} else if (state == 2) {txtRefresh_top.setText("释放立即更新");imageView_top.setImageResource(imgRefreshUp);} else if (state == 3) {imageView_top.setVisibility(View.INVISIBLE);progressBar_top.setVisibility(View.VISIBLE);txtRefresh_top.setText("正在更新...");}}}/* * ==============底部工具条部分的私有函数================= */// View的点击事件private OnClickListener onClickListener = new OnClickListener() {@Overridepublic void onClick(View v) {if (v.equals(bottomViewItem)) {if (onPDListListener != null) {onPDListListener.onloadMore();loadingMoreView_State(1);}}}};/** * <p> * 设置加载更多工具条的状态 * <p> * -1=隐藏 0=显示并还原为待点击状态 1=处于加载中 * */private void loadingMoreView_State(int state) {if (state == -1) {bottomViewItem.setVisibility(View.GONE);} else if (state == 0) {bottomViewItem.setVisibility(View.VISIBLE);progressBar_bottom.setVisibility(View.INVISIBLE);txtBottom.setText("点击加载更多");bottomViewItem.setEnabled(true);} else if (state == 2) {bottomViewItem.setVisibility(View.VISIBLE);progressBar_bottom.setVisibility(View.VISIBLE);txtBottom.setText("正在努力加载...");bottomViewItem.setEnabled(false);}}/* =======================监听部分的公开函数====================== *//** 设置刷新监听 */public void setOnPDListen(OnPDListListener onPDListListener) {this.onPDListListener = onPDListListener;}/** 移除刷新监听 */public void removePDListen() {this.onPDListListener = null;}/* ===============底部工具条部分的公开函数============= *//** 是否显示加载更多 */public void loadingMoreView_IsEnabled(boolean isEnabled) {if (isEnabled) {loadingMoreView_State(0);} else {loadingMoreView_State(-1);}}/* ====================顶部工具条部分的公开函数==================== *//** 弹出刷新条 并触发onRefresh接口 */public void startRefresh() {if (!isLoading && this.onPDListListener != null) {isLoading = true;topViewItem.setPadding(0, 0, 0, 0);setTopViewControl(3);setSelection(0);// 选中首行this.onPDListListener.onRefresh();}}/** * 结束刷新 *  * @param isUpdateTime *            是否更新时间,刷新失败可以不更新 */public void stopRefresh(boolean isUpdateTime) {// 还原所有变量,首行重置回初始值,选中首行if (isLoading) {// 重置所有变量isFirstItemShow = false;isWantToExtrude = true;isExtruded = false;isLoading = false;// 重置下拉行控件的内容setTopViewControl(0);// 更新时间if (isUpdateTime) {txtTime_top.setText("上一次更新时间:"+ sdf.format(Calendar.getInstance().getTime()));}topViewItem.setPadding(0, 0 - topViewItemHeight, 0, 0);setSelection(0);}}/* ================测量长宽========================== *//** 为View测量长宽 因为View在加入到界面之前,直接去获取长宽是无效的,跟在Activity的onCreat时无法获取控件长宽一个道理 */private static void measureView(View child) {ViewGroup.LayoutParams p = child.getLayoutParams();if (p == null) {p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_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 OnPDListListener onPDListListener = null;public interface OnPDListListener {/** 下拉刷新 */public void onRefresh();/** 加载更多 */public void onloadMore();}}





原创粉丝点击