package com.example.hunxiao; import java.text.SimpleDateFormat; import java.util.Calendar; 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.widget.AbsListView; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ListView; import android.widget.ProgressBar; import android.widget.TextView; /** * @date 2013-7-26 */ public class DropListivew extends ListView implements android.widget.AbsListView.OnScrollListener { public DropListivew(Context context) { super(context); init(); } public DropListivew(Context context, AttributeSet attrs) { super(context, attrs); init(); } public DropListivew(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(); } /* * ==================列表顶部的控件集合================== */ private ProgressBar progressBar_top = null; private ImageView imageView_top = null; private TextView txtTime_top, txtRefresh_top; /** 列表顶部的视图 */ private LinearLayout topViewItem = null; /** 列表顶部视图的高 */ private int topViewItemHeight = 0; /* * ===================================================== */ /* * ==================列表底部的控件集合================== */ private LinearLayout 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 SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss"); /** 初始化 */ private void init() { setOnScrollListener(this); LayoutInflater layoutInflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); // 初始化列表底部工具条 bottomViewItem = (LinearLayout) layoutInflater.inflate(R.layout.base_list_item_bottom, this, false); progressBar_bottom = (ProgressBar) bottomViewItem.findViewById(R.id.progressBar1); txtBottom = (TextView) bottomViewItem.findViewById(R.id.textView1); // 初始化列表顶部工具条 topViewItem = (LinearLayout) layoutInflater.inflate(R.layout.base_list_item_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); txtRefresh_top = (TextView) topViewItem.findViewById(R.id.textView1); /* * 3步隐藏该topViewItem: 1.测出其加入屏幕后的长和宽,因为现在它没加入屏幕,直接获取其长宽是无效的 2.得到测出的高度 * 3.将上边距设置为负高度,相当于把高度变成了0,达到隐藏的效果(顺带一提:topViewItem即使通过padding设置其高度为0 * ,在列表中,它仍然会占据一行的像素,列表仍然认为它是第0行) */ // 第一步 measureView(topViewItem); // 第二步 topViewItemHeight = topViewItem.getMeasuredHeight(); // 第三步 topViewItem.setPadding(topViewItem.getPaddingLeft(), -1 * topViewItemHeight, topViewItem.getPaddingRight(), topViewItem.getPaddingBottom()); // 加入到列表中 addHeaderView(topViewItem); } /* * 当列表以任何形式(自动滚或者手拖着滚)停止滚动后,如果第一行已经出现了,那么认为已经有了加载的意图 如果第一行没有出现,那么认为没有加载的意图 */ @Override public 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行是否出现在屏幕上,是否存在加载的意图 */ @Override public 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; @Override public boolean onTouchEvent(MotionEvent ev) { boolean result = super.onTouchEvent(ev); if (isLoading)// 已经在加载则不作任何处理 { return result; } switch (ev.getAction()) { // 按下手指时,获得按下y轴位置,用于在拖动时计算手指拖动距离 case MotionEvent.ACTION_DOWN: lastActionDown_Y = ev.getY(); break; /* * 1.如果有加载的意图,则拉伸第一行。但加载意图只是说他可能要拉,有加载意图的同时还需要手指往下滑动才算真的要加载. * 当手指往上滑动时,用户并不想拉开,滑动Y轴的值为负(刚开始接近0,后面绝对值逐渐增大).但加载意图在那个一瞬间可能 * 还没有立即被onScroll重置为false,在那一瞬间可能就会把拉伸条的paddingTop置为非常接近0的负值, * 很明显就会把拉伸条显示出来.因此要判断大于0才去做这个动作 2.如果没有加载的意图,则有两种可能。一种是平常的触摸,不用作任何处理。 * 另一种是当时已经拉开了,用户又不想拉了,所以又向上推了回去(大家应该深有体会)。推回去后,加载意图被onScroll函数制为false。 * 在这种情况下,应该把第一行重新压缩回原状。 */ case MotionEvent.ACTION_MOVE: // 手指滑动的距离 float gapValue_y = ev.getY() - lastActionDown_Y; if (isWantToExtrude) { if (gapValue_y > 0) {// 超过5个像素再执行,这是为了提升用户体验,免得一碰就抖,跟本身的逻辑没关系 isExtruded = true; // 新的PaddingTop应该是滑动的距离减去总高,才会使加载行有慢慢拉宽的效果 int newPaddingTop = (int) gapValue_y - topViewItemHeight; topViewItem.setPadding(0, newPaddingTop, 0, 0); // 如果新的PaddingTop大于等于0,说明已经完全被拉开,设置控件相关动作 if (newPaddingTop >= 0) { setControl(2); } else { setControl(1); } } else { // System.out.println("小于0,往上推,什么都不干,坐等意图被重置"); } } else { if (isExtruded) { // 又推回去了 isExtruded = false; topViewItem.setPadding(0, -1 * topViewItemHeight, 0, 0); // System.out.println("重置"); } } break; // 拿起手指时,平常状态下什么都不用做。 // 拿起手指时,如果已经完全被拉开,则开始加载 case MotionEvent.ACTION_UP: if (isExtruded) { // System.out.println("paddingTop:" + // topViewItem.getPaddingTop()); // System.out.println("topViewItemHeight:" + topViewItemHeight); if (topViewItem.getPaddingTop() >= 0) { if (this.onPDListListener != null) { isLoading = true; topViewItem.setPadding(0, 0, 0, 0); setControl(3); this.onPDListListener.onRefresh(); } else { topViewItem.setPadding(0, -1 * topViewItemHeight, 0, 0); isExtruded = false; setSelection(0);// 退回首行 } } else { topViewItem.setPadding(0, -1 * topViewItemHeight, 0, 0); isExtruded = false; setSelection(0);// 退回首行 } } break; default: break; } return result; } // 提升性能,防止无意义的重复赋值 private int controlState = -1; /** * 设置控件的显示内容 * * @param state 0表示被用户重置,1表示下拉刷新,2表示松开刷新,3表示正在刷新 */ private void setControl(int state) { if (controlState != 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(R.drawable.ic_down); } else if (state == 2) { txtRefresh_top.setText("释放立即更新"); imageView_top.setImageResource(R.drawable.ic_up); } else if (state == 3) { imageView_top.setVisibility(View.INVISIBLE); progressBar_top.setVisibility(View.VISIBLE); txtRefresh_top.setText("正在更新..."); } } } /* =======================监听部分的公开函数====================== */ /** 设置刷新监听 */ public void setOnPDListen(OnPDListListener onPDListListener) { this.onPDListListener = onPDListListener; } /** 移除刷新监听 */ public void removePDListen() { this.onPDListListener = null; } /* ==================================================== */ /* ===============加载更多部分的公开函数============= */ private boolean isLoadingMoreViewEnabled = true; /** 初始化加载更多的工具条,并且添加到列表底部 */ public void loadingMoreView_init() { // 初始化列表底部的控件 addFooterView(bottomViewItem); bottomViewItem.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (onPDListListener != null && isLoadingMoreViewEnabled) { progressBar_bottom.setVisibility(View.VISIBLE); txtBottom.setText("正在加载..."); onPDListListener.onloadMore(); } } }); } /** 完成加载更多 */ public void loadingMore_finish() { progressBar_bottom.setVisibility(View.INVISIBLE); txtBottom.setText("点击加载更多"); } /** 设置是否启用 */ public void loadingMore_enabled(boolean enabled) { progressBar_bottom.setVisibility(View.INVISIBLE); isLoadingMoreViewEnabled = enabled; if (enabled) { txtBottom.setText("点击加载更多"); } else { txtBottom.setText("已经到达最后一条"); } } /* ==================================================== */ /* ====================刷新部分的公开函数==================== */ /** 弹出刷新条 并触发onRefresh接口 */ public void startRefresh() { if (!isLoading && this.onPDListListener != null) { isLoading = true; topViewItem.setPadding(0, 0, 0, 0); setControl(3); setSelection(0);// 选中首行 this.onPDListListener.onRefresh(); } } /** * 结束刷新 * * @param isUpdateTime 是否更新时间,刷新失败可以不更新 */ public void stopRefresh(boolean isUpdateTime) { // 还原所有变量,首行重置回初始值,选中首行 if (isLoading) { // 重置所有变量 isFirstItemShow = false; isWantToExtrude = true; isExtruded = false; isLoading = false; // 重置下拉行控件的内容 setControl(0); // 更新时间 if (isUpdateTime) { txtTime_top.setText("上一次更新时间:" + sdf.format(Calendar.getInstance().getTime())); } topViewItem.setPadding(0, -1 * 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(); } }base_list_item_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="15dp" android:layout_marginTop="30dp" > <TextView android:id="@+id/textView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:text="下拉刷新" 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/drop_down_list_arrow" /> <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>base_list_item_bottom.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="fill_parent" android:gravity="center_horizontal" android:orientation="horizontal" android:padding="8dip" > <ProgressBar android:id="@+id/progressBar1" style="?android:attr/progressBarStyleSmall" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:id="@+id/textView1" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout>