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>