欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

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

程序员文章站 2024-03-02 10:25:04
之前在使用ios时,看到过一种分组的view,每一组都有一个header,在上下滑动的时候,会有一个悬浮的header,这种体验觉得很不错,请看下图: 上图中标红...

之前在使用ios时,看到过一种分组的view,每一组都有一个header,在上下滑动的时候,会有一个悬浮的header,这种体验觉得很不错,请看下图:
Anroid ListView分组和悬浮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,通过数据来控制其显示或隐藏,实现原理如下图:Anroid ListView分组和悬浮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); 
        } 
      } 
    } 
  } 
} 

最后来一张截图:
Anroid ListView分组和悬浮Header实现方法以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。