简单模仿StickyListHeaders,给listview内容设置header,随着滚动,header有推出,推入的效果。
先上StickyListHeaders的效果图:
这是StickyListHeaders 的效果图,有点小问题哎,顶部的headerview显示不全。
这是我接下来要弄的仿制品的效果图
效果还行的吧
实现原理分析:
listview通过adpater(重写getView())来给内容设置两种类型的item,一类item是蓝色这种"移动标题",一类是白色这种内容性item。为了区分,我简单做了数据结构的处理,adapter传入的不仅是list<String>,还要传入indexHeaderView[]用来跟踪"移动标题"的位置。 其次是要实现一个固定在listview最上面的headerview即可了,这个headerview需要和"移动标题"一样的布局。因为每当"移动标题"移动到headerview下面或者上面的时候,那么headerview只要跟着一起移动,然后在移出的时候再显示出来,并修改headerview的文字,这样就覆盖在"移动标题"的上面,给人感觉好像被推出去了一样,其实显示在最上方的还是headerview,大致思路是这样,接下来说说代码。(都有很多注释了的,我再简单说下)
我的实现方式没有StickyListHeaders方式复杂,但是复用性很差。我稍微看了下StickyListHeaders的实现源码,继承frameLayout。
/**
* Even though this is a FrameLayout subclass we still consider it a ListView.
* This is because of 2 reasons:
* 1. It acts like as ListView.
* 2. It used to be a ListView subclass and refactoring the name would cause compatibility errors.
*
* @author Emil Sjölander
*/
public class StickyListHeadersListView extends FrameLayout {
public interface OnHeaderClickListener {
void onHeaderClick(StickyListHeadersListView l, View header,
int itemPosition, long headerId, boolean currentlySticky);
}
我的是通过在PinnedHeaderListView的基础上修改而来,那么先来解释下PinnedHeaderListView这个东西。这个listview是一个头部固定带有headerview,并已经实现headerview的移出,移入的代码。
/**
* A ListView that maintains a header pinned at the top of the list. The
* pinned header can be pushed up and dissolved as needed.
*/
public class PinnedHeaderListView extends ListView {
/**
* Adapter interface. The list adapter must implement this interface.
*/
public interface PinnedHeaderAdapter {
/**
* Pinned header state: don't show the header.
*/
public static final int PINNED_HEADER_GONE = 0;
/**
* Pinned header state: show the header at the top of the list.
*/
public static final int PINNED_HEADER_VISIBLE = 1;
/**
* Pinned header state: show the header. If the header extends beyond
* the bottom of the first shown element, push it up and clip.
*/
public static final int PINNED_HEADER_PUSHED_UP = 2;
/**
* Computes the desired state of the pinned header for the given
* position of the first visible list item. Allowed return values are
* {@link #PINNED_HEADER_GONE}, {@link #PINNED_HEADER_VISIBLE} or
* {@link #PINNED_HEADER_PUSHED_UP}.
*/
int getPinnedHeaderState(int position);
/**
* Configures the pinned header view to match the first visible list item.
*
* @param header pinned header view.
* @param position position of the first visible list item.
* @param alpha fading of the header view, between 0 and 255.
*/
void configurePinnedHeader(View header, int position, int alpha);
}
private static final int MAX_ALPHA = 255;
private PinnedHeaderAdapter mAdapter;
private View mHeaderView;
private boolean mHeaderViewVisible;
private int mHeaderViewWidth;
private int mHeaderViewHeight;
public PinnedHeaderListView(Context context) {
super(context);
}
public PinnedHeaderListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public PinnedHeaderListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public void setPinnedHeaderView(View view) {
mHeaderView = view;
// Disable vertical fading when the pinned header is present
// TODO change ListView to allow separate measures for top and bottom fading edge;
// in this particular case we would like to disable the top, but not the bottom edge.
if (mHeaderView != null) {
setFadingEdgeLength(0);
}
requestLayout();
}
@Override
public void setAdapter(ListAdapter adapter) {
super.setAdapter(adapter);
mAdapter = (PinnedHeaderAdapter) adapter;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (mHeaderView != null) {
measureChild(mHeaderView, widthMeasureSpec, heightMeasureSpec);
mHeaderViewWidth = mHeaderView.getMeasuredWidth();
mHeaderViewHeight = mHeaderView.getMeasuredHeight();
}
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (mHeaderView != null) {
mHeaderView.layout(0, 0, mHeaderViewWidth, mHeaderViewHeight);
configureHeaderView(getFirstVisiblePosition());
}
}
/**
* 函数名叫"配置headerview"
* 其实就是根据三个状态对headerview(最上面的那个东西)做显示,消失,移动的处理
* @param position listview最上面item的position
*/
public void configureHeaderView(int position) {
if (mHeaderView == null) {
return;
}
int state = mAdapter.getPinnedHeaderState(position);
switch (state) {
case PinnedHeaderAdapter.PINNED_HEADER_GONE: {
mHeaderViewVisible = false;
break;
}
case PinnedHeaderAdapter.PINNED_HEADER_VISIBLE: {
mAdapter.configurePinnedHeader(mHeaderView, position, MAX_ALPHA);
if (mHeaderView.getTop() != 0) {
mHeaderView.layout(0, 0, mHeaderViewWidth, mHeaderViewHeight);
}
mHeaderViewVisible = true;
break;
}
case PinnedHeaderAdapter.PINNED_HEADER_PUSHED_UP: {
View firstView = getChildAt(0);
int bottom = firstView.getBottom();
int itemHeight = firstView.getHeight();
int headerHeight = mHeaderView.getHeight();
int y;
int alpha;
if (bottom < headerHeight) {
y = (bottom - headerHeight);
alpha = MAX_ALPHA * (headerHeight + y) / headerHeight;
} else {
y = 0;
alpha = MAX_ALPHA;
}
mAdapter.configurePinnedHeader(mHeaderView, position, alpha);
if (mHeaderView.getTop() != y) {
mHeaderView.layout(0, y, mHeaderViewWidth, mHeaderViewHeight + y);
}
mHeaderViewVisible = true;
break;
}
}
}
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
if (mHeaderViewVisible) {
drawChild(canvas, mHeaderView, getDrawingTime());
}
}
}
其中解释两个方法的作用:
1.setPinnedHeaderView(View view),使用来将你写的headerview布局传入进来,所以初始化后就需要设置该方法,否则headerview为null,那么就不会显示了
2.configureHeaderView(int position) 这个方法是用来改变headerview的状态的,在onScroll监听方法里调用该方法,传入的position是listview的firstVisibleItem
之前说过,我们需要对adapter做处理,让他显示不同的item,接下来看下adapter的代码:
public class MyAdapter extends BaseAdapter implements
PinnedHeaderListView.PinnedHeaderAdapter, AbsListView.OnScrollListener{
private Context context;
private List<String>list; //所有内容(包括移动标题的内容)都在这里
private int indexSubHeader[]; //移动标题的position
private View subHeaderView;
private View contentView;
public MyAdapter(Context context, List<String> list, int[] indexSubHeader){
this.context = context;
this.list = list;
this.indexSubHeader = indexSubHeader;
}
@Override
public int getCount() {
return list.size();
}
@Override
public Object getItem(int position) {
return list.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ContentViewHolder contentViewHolder = null;
SubHeaderViewHolder subHeaderViewHolder = null;
ViewHolder viewHolder = null;
if(contentView != null){
viewHolder = (ViewHolder)contentView.getTag();
}
if(isSubHeaderViewPosition(position)){
//为了防止两种类型的布局互相复用,导致错乱,在这里加个类型判断。
if(convertView == null || !(viewHolder instanceof SubHeaderViewHolder)) {
subHeaderViewHolder = new SubHeaderViewHolder();
convertView = LayoutInflater.from(context).inflate(R.layout.listview_title_item, parent, false);
subHeaderViewHolder.tvTitle = (TextView) convertView.findViewById(R.id.listview_item_tv_title);
convertView.setTag(subHeaderViewHolder);
} else {
subHeaderViewHolder = (SubHeaderViewHolder)convertView.getTag();
}
subHeaderViewHolder.tvTitle.setText(list.get(position));
} else {
if(convertView == null || !(viewHolder instanceof ContentViewHolder)) {
contentViewHolder = new ContentViewHolder();
convertView = LayoutInflater.from(context).inflate(R.layout.listview_item, parent, false);
contentViewHolder.tvContent = (TextView) convertView.findViewById(R.id.listview_item_tv_content);
convertView.setTag(contentViewHolder);
} else {
contentViewHolder = (ContentViewHolder)convertView.getTag();
}
contentViewHolder.tvContent.setText(list.get(position));
}
return convertView;
}
/**
* 判断该position是否是标题的position
* @param position
* @return
*/
public boolean isSubHeaderViewPosition(int position){
for(int i=0; i<indexSubHeader.length; i++){
if(position == indexSubHeader[i]) {
return true;
}
}
return false;
}
//通过position判断该position属于哪个组
private int getCurrentSubHeaderIndex(int position){
for(int i=indexSubHeader.length-1; i>=0; i--){
if(position >= indexSubHeader[i])
return i;
}
return -1;
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (view instanceof PinnedHeaderListView) {
((PinnedHeaderListView) view).configureHeaderView(firstVisibleItem);
}
}
//通过position,来判断headerview的状态
@Override
public int getPinnedHeaderState(int position) {
if (position < 0) {
return PINNED_HEADER_GONE;
}
int currentSubHeaderIndex = getCurrentSubHeaderIndex(position);
int nextSubHeaderIndex = currentSubHeaderIndex + 1;
if (nextSubHeaderIndex >= 0
&& nextSubHeaderIndex<indexSubHeader.length
&& indexSubHeader[nextSubHeaderIndex]-1 == position) {
return PINNED_HEADER_PUSHED_UP;
}
return PINNED_HEADER_VISIBLE;
}
//改变headerview的内容信息
@Override
public void configurePinnedHeader(View header, int position, int alpha) {
int subHeaderIndex = getCurrentSubHeaderIndex(position);
if(subHeaderIndex >= 0 && subHeaderIndex < indexSubHeader.length) {
System.out.println(list.get(indexSubHeader[subHeaderIndex]));
((TextView) header.findViewById(R.id.listview_item_tv_title)).setText(list.get(indexSubHeader[subHeaderIndex]));
}
}
class ViewHolder{
}
class ContentViewHolder extends ViewHolder{
TextView tvContent;
}
class SubHeaderViewHolder extends ViewHolder{
TextView tvTitle;
}
}
代码不多,注释也有,不多费口舌了
接下来看下Acitivity里面调用
private PinnedHeaderListView pListView;
private MyAdapter myAdapter;
private int subHeaderIndex[];
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initListView();
}
private void initListView() {
pListView = (PinnedHeaderListView)super.findViewById(R.id.activity_main_plistview);
subHeaderIndex = new int[]{0, 3, 8, 17, 28};
List<String> list = getList(subHeaderIndex);
myAdapter = new MyAdapter(this, list, subHeaderIndex);
View headerView = LayoutInflater.from(this).inflate(R.layout.listview_title_item, pListView, false);
pListView.setPinnedHeaderView(headerView);
pListView.setAdapter(myAdapter);
pListView.setOnScrollListener(myAdapter);
pListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (!myAdapter.isSubHeaderViewPosition(position)){
Toast.makeText(MainActivity.this, "this position = " + (position+1), Toast.LENGTH_SHORT).show();
}
}
});
}
private List<String> getList(int subHeaderIndex[]) {
final int size = 40;
List<String>list = new ArrayList<String>();
int count = 0;
for(int i=0; i<size; i++){
if(count<subHeaderIndex.length && i == subHeaderIndex[count]){
if(count == subHeaderIndex.length-1)
list.add("there have " + (size - subHeaderIndex[count]) + " items of listview");
else
list.add("there have " + (subHeaderIndex[count + 1] - subHeaderIndex[count]-1) + " items of listview");
count++;
} else {
list.add("position = " + (i+1));
}
}
return list;
}
好啦,完成。收工回家...................