Anroid ListView分组和悬浮Header实现方法

之前在使用iOS时,看到过一种分组的View,每一组都有一个Header,在上下滑动的时候,会有一个悬浮的Header,这种体验觉得很不错,请看下图:

上图中标红的1,2,3,4四张图中,当向上滑动时,仔细观察灰色条的Header变化,当第二组向上滑动时,会把第一组的悬浮Header挤上去。

这种效果在Android是没有的,iOS的SDK就自带这种效果。这篇文章就介绍如何在Android实现这种效果。

1、悬浮Header的实现

其实Android自带的联系人的App中就有这样的效果,我也是把他的类直接拿过来的,实现了PinnedHeaderListView这么一个类,扩展于ListView,核心原理就是在ListView的最顶部绘制一个调用者设置的Header View,在滑动的时候,根据一些状态来决定是否向上或向下移动Header View(其实就是调用其layout方法,理论上在绘制那里作一些平移也是可以的)。下面说一下具体的实现:

1.1、PinnedHeaderAdapter接口

这个接口需要ListView的Adapter来实现,它定义了两个方法,一个是让Adapter告诉ListView当前指定的position的数据的状态,比如指定position的数据可能是组的header;另一个方法就是设置Header View,比如设置Header View的文本,图片等,这个方法是由调用者去实现的。

/**
 * 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);
}

1.2、如何绘制Header View

这是在dispatchDraw方法中绘制的:

@Override
protected void dispatchDraw(Canvas canvas) {
  super.dispatchDraw(canvas);
  if (mHeaderViewVisible) {
    drawChild(canvas, mHeaderView, getDrawingTime());
  }
} 

1.3、配置Header View

核心就是根据不同的状态值来控制Header View的状态,比如PINNED_HEADER_GONE(隐藏)的情况,可能需要设置一个flag标记,不绘制Header View,那么就达到隐藏的效果。当PINNED_HEADER_PUSHED_UP状态时,可能需要根据不同的位移来计算Header View的移动位移。下面是具体的实现:

public void configureHeaderView(int position) {
  if (mHeaderView == null || null == mAdapter) {
    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;
    }
  }
}

1.4、onLayout和onMeasure

在这两个方法中,控制Header View的位置及大小

@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());
  }
}

好了,到这里,悬浮Header View就完了,各位可能看不到完整的代码,只要明白这几个核心的方法,自己写出来,也差不多了。

2、ListView Section实现

有两种方法实现ListView Section效果:

方法一:

每一个ItemView中包含Header,通过数据来控制其显示或隐藏,实现原理如下图:

优点:

1,实现简单,在Adapter.getView的实现中,只需要根据数据来判断是否是header,不是的话,隐藏Item view中的header部分,否则显示。

2,Adapter.getItem(int n)始终返回的数据是在数据列表中对应的第n个数据,这样容易理解。

3,控制header的点击事件更加容易

缺点:
1、使用更多的内存,第一个Item view中都包含一个header view,这样会费更多的内存,多数时候都可能header都是隐藏的。

方法二:

使用不同类型的View:重写getItemViewType(int)和getViewTypeCount()方法。

优点:

1,允许多个不同类型的item

2,理解更加简单

缺点:

1,实现比较复杂

2,得到指定位置的数据变得复杂一些

到这里,我的实现方式是选择第二种方案,尽管它的实现方式要复杂一些,但优点比较明显。

3、Adapter的实现

这里主要就是说一下getPinnedHeaderState和configurePinnedHeader这两个方法的实现

private class ListViewAdapter extends BaseAdapter implements PinnedHeaderAdapter { 

  private ArrayList<Contact> mDatas;
  private static final int TYPE_CATEGORY_ITEM = 0;
  private static final int TYPE_ITEM = 1;  

  public ListViewAdapter(ArrayList<Contact> datas) {
    mDatas = datas;
  } 

  @Override
  public boolean areAllItemsEnabled() {
    return false;
  } 

  @Override
  public boolean isEnabled(int position) {
    // 异常情况处理
    if (null == mDatas || position < 0|| position > getCount()) {
      return true;
    }  

    Contact item = mDatas.get(position);
    if (item.isSection) {
      return false;
    } 

    return true;
  } 

  @Override
  public int getCount() {
    return mDatas.size();
  } 

  @Override
  public int getItemViewType(int position) {
    // 异常情况处理
    if (null == mDatas || position < 0|| position > getCount()) {
      return TYPE_ITEM;
    }  

    Contact item = mDatas.get(position);
    if (item.isSection) {
      return TYPE_CATEGORY_ITEM;
    } 

    return TYPE_ITEM;
  } 

  @Override
  public int getViewTypeCount() {
    return 2;
  } 

  @Override
  public Object getItem(int position) {
    return (position >= 0 && position < mDatas.size()) ? mDatas.get(position) : 0;
  } 

  @Override
  public long getItemId(int position) {
    return 0;
  } 

  @Override
  public View getView(int position, View convertView, ViewGroup parent) {
    int itemViewType = getItemViewType(position);
    Contact data = (Contact) getItem(position);
    TextView itemView; 

    switch (itemViewType) {
    case TYPE_ITEM:
      if (null == convertView) {
        itemView = new TextView(SectionListView.this);
        itemView.setLayoutParams(new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
            mItemHeight));
        itemView.setTextSize(16);
        itemView.setPadding(10, 0, 0, 0);
        itemView.setGravity(Gravity.CENTER_VERTICAL);
        //itemView.setBackgroundColor(Color.argb(255, 20, 20, 20));
        convertView = itemView;
      } 

      itemView = (TextView) convertView;
      itemView.setText(data.toString());
      break; 

    case TYPE_CATEGORY_ITEM:
      if (null == convertView) {
        convertView = getHeaderView();
      }
      itemView = (TextView) convertView;
      itemView.setText(data.toString());
      break;
    } 

    return convertView;
  } 

  @Override
  public int getPinnedHeaderState(int position) {
    if (position < 0) {
      return PINNED_HEADER_GONE;
    } 

    Contact item = (Contact) getItem(position);
    Contact itemNext = (Contact) getItem(position + 1);
    boolean isSection = item.isSection;
    boolean isNextSection = (null != itemNext) ? itemNext.isSection : false;
    if (!isSection && isNextSection) {
      return PINNED_HEADER_PUSHED_UP;
    } 

    return PINNED_HEADER_VISIBLE;
  } 

  @Override
  public void configurePinnedHeader(View header, int position, int alpha) {
    Contact item = (Contact) getItem(position);
    if (null != item) {
      if (header instanceof TextView) {
        ((TextView) header).setText(item.sectionStr);
      }
    }
  }
}

在getPinnedHeaderState方法中,如果第一个item不是section,第二个item是section的话,就返回状态PINNED_HEADER_PUSHED_UP,否则返回PINNED_HEADER_VISIBLE。
在configurePinnedHeader方法中,就是将item的section字符串设置到header view上面去。

【重要说明】

Adapter中的数据里面已经包含了section(header)的数据,数据结构中有一个方法来标识它是否是section。那么,在点击事件就要注意了,通过position可能返回的是section数据结构。

数据结构Contact的定义如下:

public class Contact {
  int id;
  String name;
  String pinyin;
  String sortLetter = "#";
  String sectionStr;
  String phoneNumber;
  boolean isSection;
  static CharacterParser sParser = CharacterParser.getInstance(); 

  Contact() { 

  } 

  Contact(int id, String name) {
    this.id = id;
    this.name = name;
    this.pinyin = sParser.getSpelling(name);
    if (!TextUtils.isEmpty(pinyin)) {
      String sortString = this.pinyin.substring(0, 1).toUpperCase();
      if (sortString.matches("[A-Z]")) {
        this.sortLetter = sortString.toUpperCase();
      } else {
        this.sortLetter = "#";
      }
    }
  } 

  @Override
  public String toString() {
    if (isSection) {
      return name;
    } else {
      //return name + " (" + sortLetter + ", " + pinyin + ")";
      return name + " (" + phoneNumber + ")";
    }
  }
}

完整的代码

package com.lee.sdk.test.section; 

import java.util.ArrayList; 

import android.graphics.Color;
import android.os.Bundle;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.BaseAdapter;
import android.widget.TextView;
import android.widget.Toast; 

import com.lee.sdk.test.GABaseActivity;
import com.lee.sdk.test.R;
import com.lee.sdk.widget.PinnedHeaderListView;
import com.lee.sdk.widget.PinnedHeaderListView.PinnedHeaderAdapter; 

public class SectionListView extends GABaseActivity { 

  private int mItemHeight = 55;
  private int mSecHeight = 25; 

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main); 

    float density = getResources().getDisplayMetrics().density;
    mItemHeight = (int) (density * mItemHeight);
    mSecHeight = (int) (density * mSecHeight); 

    PinnedHeaderListView mListView = new PinnedHeaderListView(this);
    mListView.setAdapter(new ListViewAdapter(ContactLoader.getInstance().getContacts(this)));
    mListView.setPinnedHeaderView(getHeaderView());
    mListView.setBackgroundColor(Color.argb(255, 20, 20, 20));
    mListView.setOnItemClickListener(new OnItemClickListener() {
      @Override
      public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        ListViewAdapter adapter = ((ListViewAdapter) parent.getAdapter());
        Contact data = (Contact) adapter.getItem(position);
        Toast.makeText(SectionListView.this, data.toString(), Toast.LENGTH_SHORT).show();
      }
    }); 

    setContentView(mListView);
  } 

  private View getHeaderView() {
    TextView itemView = new TextView(SectionListView.this);
    itemView.setLayoutParams(new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
        mSecHeight));
    itemView.setGravity(Gravity.CENTER_VERTICAL);
    itemView.setBackgroundColor(Color.WHITE);
    itemView.setTextSize(20);
    itemView.setTextColor(Color.GRAY);
    itemView.setBackgroundResource(R.drawable.section_listview_header_bg);
    itemView.setPadding(10, 0, 0, itemView.getPaddingBottom()); 

    return itemView;
  } 

  private class ListViewAdapter extends BaseAdapter implements PinnedHeaderAdapter { 

    private ArrayList<Contact> mDatas;
    private static final int TYPE_CATEGORY_ITEM = 0;
    private static final int TYPE_ITEM = 1;  

    public ListViewAdapter(ArrayList<Contact> datas) {
      mDatas = datas;
    } 

    @Override
    public boolean areAllItemsEnabled() {
      return false;
    } 

    @Override
    public boolean isEnabled(int position) {
      // 异常情况处理
      if (null == mDatas || position < 0|| position > getCount()) {
        return true;
      }  

      Contact item = mDatas.get(position);
      if (item.isSection) {
        return false;
      } 

      return true;
    } 

    @Override
    public int getCount() {
      return mDatas.size();
    } 

    @Override
    public int getItemViewType(int position) {
      // 异常情况处理
      if (null == mDatas || position < 0|| position > getCount()) {
        return TYPE_ITEM;
      }  

      Contact item = mDatas.get(position);
      if (item.isSection) {
        return TYPE_CATEGORY_ITEM;
      } 

      return TYPE_ITEM;
    } 

    @Override
    public int getViewTypeCount() {
      return 2;
    } 

    @Override
    public Object getItem(int position) {
      return (position >= 0 && position < mDatas.size()) ? mDatas.get(position) : 0;
    } 

    @Override
    public long getItemId(int position) {
      return 0;
    } 

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
      int itemViewType = getItemViewType(position);
      Contact data = (Contact) getItem(position);
      TextView itemView; 

      switch (itemViewType) {
      case TYPE_ITEM:
        if (null == convertView) {
          itemView = new TextView(SectionListView.this);
          itemView.setLayoutParams(new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
              mItemHeight));
          itemView.setTextSize(16);
          itemView.setPadding(10, 0, 0, 0);
          itemView.setGravity(Gravity.CENTER_VERTICAL);
          //itemView.setBackgroundColor(Color.argb(255, 20, 20, 20));
          convertView = itemView;
        } 

        itemView = (TextView) convertView;
        itemView.setText(data.toString());
        break; 

      case TYPE_CATEGORY_ITEM:
        if (null == convertView) {
          convertView = getHeaderView();
        }
        itemView = (TextView) convertView;
        itemView.setText(data.toString());
        break;
      } 

      return convertView;
    } 

    @Override
    public int getPinnedHeaderState(int position) {
      if (position < 0) {
        return PINNED_HEADER_GONE;
      } 

      Contact item = (Contact) getItem(position);
      Contact itemNext = (Contact) getItem(position + 1);
      boolean isSection = item.isSection;
      boolean isNextSection = (null != itemNext) ? itemNext.isSection : false;
      if (!isSection && isNextSection) {
        return PINNED_HEADER_PUSHED_UP;
      } 

      return PINNED_HEADER_VISIBLE;
    } 

    @Override
    public void configurePinnedHeader(View header, int position, int alpha) {
      Contact item = (Contact) getItem(position);
      if (null != item) {
        if (header instanceof TextView) {
          ((TextView) header).setText(item.sectionStr);
        }
      }
    }
  }
}

最后来一张截图:
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Android ListView中动态显示和隐藏Header&Footer的方法

    ListView的模板写法 ListView模板写法的完整代码: •android代码优化----ListView中自定义adapter的封装(ListView的模板写法) 以后每写一个ListView,就这么做:直接导入ViewHolder.java和ListViewAdapter,然后写一个自定义adapter继承自ListViewAdapter就行了. ListView中动态显示和隐藏Header&Footer 如果需要动态的显示和隐藏footer的话,按照惯例,误以为直接通过setVis

  • Android之联系人PinnedHeaderListView使用介绍

    Android联系人中的ListView是做得比较独特的,但是源码写得比较复制,当我们想使用他的时候再从源码中提取,实属不易啊,而且容易出错,这几天,我把他提取出来了,写成一个简单的例子,一是给自己备忘,而是跟大家分享一下,好了,先来看看效果图:  首先是封装好的带头部的PinnedHeaderListView: 复制代码 代码如下: public class PinnedHeaderListView extends ListView { public interface PinnedHeade

  • 当ListView有Header时 onItemClick里的position不正确的原因

    当ListView实例addheaderView()或者addFooterView后,再通过setAdapter来添加适配器,此时在ListView实例变量里保存的适配器变量与我们传入的适配器变量不是同一个了.具体可参考源码: 因此,当有headerView或footerView的时候,要通过getAdapter()获取到适配器,而不是直接使用调用setAdapter是传入的入参. 以上所述是小编给大家介绍的当ListView有Header时 onItemClick里的position不正确的原

  • Anroid ListView分组和悬浮Header实现方法

    之前在使用iOS时,看到过一种分组的View,每一组都有一个Header,在上下滑动的时候,会有一个悬浮的Header,这种体验觉得很不错,请看下图: 上图中标红的1,2,3,4四张图中,当向上滑动时,仔细观察灰色条的Header变化,当第二组向上滑动时,会把第一组的悬浮Header挤上去. 这种效果在Android是没有的,iOS的SDK就自带这种效果.这篇文章就介绍如何在Android实现这种效果. 1.悬浮Header的实现 其实Android自带的联系人的App中就有这样的效果,我也是把

  • Android实现的ListView分组布局改进示例

    本文实例讲述了Android实现的ListView分组布局改进方法.分享给大家供大家参考,具体如下: 由于是在网上转载的一篇文章,在这里就不多说废话了,首先看一下最终的效果图: 然后是实现该ListView布局的主要代码: 1.程序主界面 SeparateListView.java package whu.iss.wuxianglong; import java.util.ArrayList; import java.util.List; import android.app.Activity;

  • js+cookies实现悬浮购物车的方法

    本文实例讲述了js+cookies实现悬浮购物车的方法.分享给大家供大家参考.具体分析如下: 在 "商品列表展示页"做上 "悬浮的"与"DataList"结合的 "无刷新购物车",只需计算出总价,不必去单独页面结算.找了些资料修改了一下,整理示例如下: gwc.js文件如下: // JavaScript Document //计算单个小计 function EveryCount() { var index=window.eve

  • Android中ListView下拉刷新的实现方法实例分析

    本文实例讲述了Android中ListView下拉刷新的实现方法.分享给大家供大家参考,具体如下: ListView中的下拉刷新是非常常见的,也是经常使用的,看到有很多同学想要,那我就整理一下,供大家参考.那我就不解释,直接上代码了. 这里需要自己重写一下ListView,重写代码如下: package net.loonggg.listview; import java.util.Date; import android.content.Context; import android.util.

  • Android实现ListView异步加载图片的方法

    本文实例讲述了Android实现ListView异步加载图片的方法.分享给大家供大家参考.具体如下: ListView异步加载图片是非常实用的方法,凡是是要通过网络获取图片资源一般使用这种方法比较好,用户体验好,不用让用户等待下去,下面就说实现方法,先贴上主方法的代码: package cn.wangmeng.test; import java.io.IOException; import java.io.InputStream; import java.lang.ref.SoftReferen

  • go语言在请求http时加入自定义http header的方法

    本文实例讲述了go语言在请求http时加入自定义http header的方法.分享给大家供大家参考.具体实现方法如下: 复制代码 代码如下: client := &http.Client{] req, err := http.NewRequest("POST", "http://example.com", bytes.NewReader(postData)) req.Header.Add("User-Agent", "myCli

  • Android开发中Listview动态加载数据的方法示例

    本文实例讲述了Android开发中Listview动态加载数据的方法.分享给大家供大家参考,具体如下: 最近在研究网络数据加载的问题,比如我有几百,甚至上千条数据,这些数据如果一次性全部加载到arraylist,然后再加载到Listview中.我们必然会去单独开线程来做,这样造成的结果就是会出现等待时间很长,用户体验非常不好.我的想法是动态加载数据,第一次加载十条,然后往下面滑动的时候再追加十条,再往下面滑动的时候再去追加,这样大大减少了用户等待的时间,同时给处理数据留下了时间.网上看到了这样一

  • Android编程实现为ListView创建上下文菜单(ContextMenu)的方法

    本文实例讲述了Android编程实现为ListView创建上下文菜单(ContextMenu)的方法.分享给大家供大家参考,具体如下: ContextMenu称为上下文菜单,一般在控件上长按时弹出.今天我们学习ContextMenu的用法,这里与listview相结合,先在ListView显示几个Item,然后在Item上长按,弹出一个菜单(就是ContextMenu),点击菜单上的项目,提示刚才长按的Item的Position. main.xml文件 <?xml version="1.0

  • jQuery实现页面下拉100像素出现悬浮窗口的方法

    本文实例讲述了jQuery实现页面下拉100像素出现悬浮窗口的方法.分享给大家供大家参考,具体如下: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"&

  • Android ListView与ScrollView冲突的解决方法总结

    Android ListView与ScrollView冲突的解决方法总结 众所周知ListView与ScrollView都具有滚动能力,对于这样的View控件,当ScrollView与ListView相互嵌套会成为一种问题:  问题一:ScrollView与ListView嵌套导致ListView显示不全面  问题二:ScrollView不能正常滑动 解决方式一: ScrollView+LinearLayout+ListView可以换成ScrollView+LinearLayout+Linear

随机推荐