该组件的功能有三个:
(1) 任意加头去头,加脚去脚。
我们知道ListView在setAdapter之后再调用addHeader方法会抛出异常,而加脚有时管用,有时不管用。Android开发文档中也明确指出ListView在setAdapter之后不应该再进行setHeader和setFooter方法。这明显不能满足我们的实际需求。
我的解决方案是:在setAdapter之前给ListView先加上一个空头布局和空脚布局,布局高度设为wrap_content,这样当头或脚布局中没有任何组件时,头和脚是看不到的。当需要显示头和脚时,直接向头和脚布局中添加要显示的组件即可。
(2) 可任意添加或删除ListView中的数据。
有过实际开发经验的同学可能会有所体会,我们每次改变数据后都要调一次adapter.notifyDataSetChanged(),而且当ListView添加或删除数据的操作未完成时,再来一次添加或删除数据操作的话会报异常。比如用ListView显示网络获取的数据时,不断的刷新数据就会出现这种情况。这时候“同步“获取数据的线程是无法避免这个问题的,因为当线程获取网络数据特别快时还是会出现Listview添加数据操作的同时发生。
我的解决办法是:在该组件中自己实现添加和删除数据的方法并用一个变量来标记当前ListView是否在进行添加或删除数据操作,是的话则不允许在进行数据操作。
(3) 实现了向下滑动出现一个显示正在加载的头布局,手离开屏幕时头布局自动隐藏的功能。如YiBo微博首页的下拉刷新效果。效果图如下:
拉动前图片
按下拉动后图片
代码:
package diy.ts.wader.widget;
import java.util.Collection;
import java.util.List;
import android.content.Context;
import android.graphics.Color;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.widget.BaseAdapter;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
/******************************************************************
* 文件名称 : MyListView.java
* 作者 : wader
* 创建时间 : 2011-7-20上午10:40:05
* 文件描述 :
* 实现了下拉显示组件头、任意添加头脚、添加删除数据功能的ListView组件
******************************************************************/
public class MyListView extends ListView {
private BaseAdapter adapter;
private List<Object> dataList;
private Context context;
private LinearLayout header;
private LinearLayout footer;
/*
* 在touch时间后的move动作时显示的头部动画
*/
private LinearLayout headerContent;
ProgressBar headerProgressBar;
TextView headertextView;
private OnHeadShowOrHideAcion onHeadShowOrHideAcion = null;
/*
* 在init时是否加头布局,在init方法前设置
*/
private boolean addHeader = false;
/*
* 在init时是否加脚布局,在init方法前设置
*/
private boolean addFooter = false;
/*
* 在touch时间后的move动作时是否显示头部动画
*/
private boolean showAnimationHead = false;
/*
* 在显示头部动画时是否执行HeadShowOrHideAcion的doActionOnShowHead方法
*/
private boolean doActionOnShowHead = true;
/*
* 在显示头部动画时是否执行HeadShowOrHideAcion的doActionOnHideHead方法
*/
private boolean doActionOnHideHead = true;
/*
* 头布局中是否已有内容(headerContent!=null)并已显示出来
*/
private boolean haveHead = false;
/*
* 是否可以对list进行添加或删除数据操作,保证list数据同步
*/
private boolean ableOperateData = true;
private float initY = 0;
public MyListView(Context context) {
super(context);
}
public MyListView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MyListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
/*
* 设置在init时是否加头布局,在init方法前设置
*/
public void setAddHeader(boolean addHeader) {
this.addHeader = addHeader;
}
/*
* 设置在init时是否加脚布局,在init方法前设置
*/
public void setAddFooter(boolean addFooter) {
this.addFooter = addFooter;
}
/*
* 设置在touch时间后的move动作时是否显示头部动画
*/
public void setShowAnimationHead(boolean param) {
this.showAnimationHead = param;
}
/*
* 设置在显示头部动画时是否执行HeadShowOrHideAcion的doActionOnShowHead方法
*/
public void setDoActionOnShowHead(boolean param) {
this.doActionOnShowHead = param;
}
/*
* 设置在隐藏头部动画时是否执行HeadShowOrHideAcion的doActionOnHideHead方法
*/
public void setDoActionOnHideHead(boolean param) {
this.doActionOnHideHead = param;
}
/*
* 对listview进行初始化,在使用该组件前必须先执行这个方法
*/
public void init(Context context) {
this.context = context;
this.addHeaderAndFooterLayout();
}
/*
* 加头布局和脚布局
*/
private void addHeaderAndFooterLayout() {
if (addHeader) {
header = new LinearLayout(context);
addHeaderView(header);
header.setVisibility(View.GONE);
}
if (addFooter) {
footer = new LinearLayout(context);
addFooterView(footer);
footer.setVisibility(View.GONE);
}
}
/*
* 显示指定的头内容
*/
public void showHeader(View view) {
if (view.getLayoutParams() == null) {
view.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT));
}
header.setVisibility(View.VISIBLE);
if (header.getChildCount() <= 0)
header.addView(view);
this.postInvalidate();
}
/*
* 显示指定的头动画
*/
public void showAnimationHeader() {
if (!haveHead && addHeader) {
if (headerContent == null) {
headerContent = new LinearLayout(context);
headerContent.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.FILL_PARENT,
LinearLayout.LayoutParams.FILL_PARENT));
headerContent.setBackgroundColor(Color.LTGRAY);
headerContent.setGravity(Gravity.CENTER);
headerContent.setOrientation(LinearLayout.HORIZONTAL);
} else
headerContent.removeAllViews();
if (headertextView == null) {
headertextView = new TextView(context);
headertextView.setTextColor(Color.BLACK);
headertextView.setTextSize(20);
headertextView.setText("松开即可更新");
}
headerProgressBar = new ProgressBar(context);
headerProgressBar.setPadding(0, 3, 20, 3);
headerContent.addView(headerProgressBar);
headerContent.addView(headertextView);
this.showHeader(headerContent);
haveHead = true;
if (onHeadShowOrHideAcion != null && doActionOnShowHead) {
doActionOnShowHead = false;
onHeadShowOrHideAcion.doActionOnShowHead(this);
}
}
}
/*
* 删除头内容
*/
public void hideHeader() {
if (addHeader) {
header.removeAllViews();
haveHead = false;
header.setVisibility(View.GONE);
this.postInvalidate();
if (onHeadShowOrHideAcion != null && doActionOnHideHead)
onHeadShowOrHideAcion.doActionOnHideHead(this);
}
}
/*
* 显示指定的脚内容
*/
public void showFooter(View view) {
if (addFooter) {
if (view.getLayoutParams() == null) {
view.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT));
}
footer.setVisibility(View.VISIBLE);
if (footer.getChildCount() <= 0)
footer.addView(view);
this.postInvalidate();
this.postInvalidate();
}
}
/*
* 删除脚内容
*/
public void hideFooter(View view) {
if (addFooter) {
footer.removeAllViews();
footer.setVisibility(View.GONE);
this.postInvalidate();
}
}
/*
* 为listview设置adapter和与该adapter相关的list,应该在init方法之后执行
*/
@SuppressWarnings("unchecked")
public void setData(BaseAdapter adapter, List<? extends Object> dataList) {
this.adapter = adapter;
this.dataList = (List<Object>) dataList;
setAdapter(adapter);
}
/*
* 在list最后添加新数据
*/
public void addData(Collection<? extends Object> c) {
if (ableOperateData) {
ableOperateData = false;
if (dataList == null || adapter == null)
throw new NullPointerException(
"You do not call the setData() method");
dataList.addAll(c);
adapter.notifyDataSetChanged();
}
ableOperateData = true;
}
/*
* 在list的指定位置添加新数据
*/
public void addData(int index, Collection<? extends Object> c) {
if (ableOperateData) {
ableOperateData = false;
if (dataList == null || adapter == null)
throw new NullPointerException(
"You don not call the setData() method or the Adapter or List param in setData() is null");
dataList.addAll(index, c);
adapter.notifyDataSetChanged();
}
ableOperateData = true;
}
/*
* 删除list中指定位置的数据
*/
public void deleteData(int index) {
if (ableOperateData) {
ableOperateData = false;
dataList.remove(index);
adapter.notifyDataSetChanged();
}
ableOperateData = true;
}
/*
* 删除list中的全部数据
*/
public void deleteAll() {
if (ableOperateData) {
ableOperateData = false;
dataList.clear();
adapter.notifyDataSetChanged();
}
ableOperateData = true;
}
/*
* 触屏事件,主要是为了显示头动画
*/
public void touchEventAction(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_MOVE:
if (this.getFirstVisiblePosition() == 0 && (ev.getY() - initY > 30)
&& !haveHead) {// 当向下滑动30个单位时显示头部动画
showAnimationHeader();
}
break;
case MotionEvent.ACTION_UP:
hideHeader();
break;
case MotionEvent.ACTION_DOWN:
initY = ev.getY();
break;
}
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (showAnimationHead)
this.touchEventAction(ev);
super.dispatchTouchEvent(ev);
return true;
}
/*
* 设置在显示头部动画时要执行HeadShowOrHideAcion
*/
public void setHeadShowOrHideAcion(
OnHeadShowOrHideAcion paramOnHeadShowOrHideAcion) {
this.onHeadShowOrHideAcion = paramOnHeadShowOrHideAcion;
}
/*
* 自定义内部接口
*/
public abstract interface OnHeadShowOrHideAcion {
public void doActionOnShowHead(ListView paramListView);
public void doActionOnHideHead(ListView paramListView);
}
}
注:
(1)OnHeadShowOrHideAcion接口是为了实现当下拉刷新时要进行的操作,如启动线程获取网络数据。
(2)ListView加头布局后item下标是从1开始而不是从0开始。
(3)该组件用起来可能比较麻烦,大家可以根据自身需要对它进行瘦身和改造。