標籤:
能夠使頭部懸停的listview在項目中是經常用到的,例如qq的好友名單或者地區選擇。
先不多說,看:(懶得上gif圖了)
這裡借鑒了別人的核心代碼,我做了一些分析。
主要是使用PinnedSectionListView來替換listview。
這裡的PinnedSectionListView是別人的。我們主要看如何使用這個PinnedSectionListView以及如何適配資料進去。
博文的最後我會上傳demo,裡面有PinnedSectionListView和完整項目。可以根據這篇部落格來看看是如何適配資料進去的。
先看項目結構圖:
其中:PinnedSectionListView是原封不動借鑒的別人的
其他的則是適配listview和資料的類
1,activity
activity裡面很簡單了
public class IndexActivity extends AppCompatActivity { private PinnedSectionListView pinned_section_list; private IndexAdapter indexAdapter; private List<CityBean> data; @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //執行個體化擁有懸停頭的控制項 pinned_section_list = (PinnedSectionListView) findViewById(R.id.pinned_section_list); //類比資料 data = new TestData().initData(); //初始化適配器 indexAdapter = new IndexAdapter(this, data); //添加適配器 pinned_section_list.setAdapter(indexAdapter); }}
2,xml布局
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <com.android.list.PinnedSectionListView android:id="@+id/pinned_section_list" android:layout_width="match_parent" android:layout_height="match_parent" android:divider="@null" android:footerDividersEnabled="false" android:headerDividersEnabled="false" /></RelativeLayout>
3,城市實體類
public class CityBean { private int cityId;//城市id private String cityName;//城市名 private int superiorId;//上級城市id private List<CityBean> subordinateList;//下級城市集合 //忽略get/set方法 }
4,測試資料,類比城市資料
public class TestData { private List<CityBean> data; public List<CityBean> initData() { data = new ArrayList<>(); for (int j = 1; j < 11; j++) { List<CityBean> beijingList = new ArrayList<>(); for (int i = 1; i < 11; i++) { CityBean cityBean = new CityBean(); cityBean.setCityId(i + 10); cityBean.setCityName("北京" + i + "區"); beijingList.add(cityBean); } CityBean cityBean = new CityBean(); cityBean.setCityId(1); cityBean.setCityName("北京" + j); cityBean.setSubordinateList(beijingList); data.add(cityBean); List<CityBean> shanghaiList = new ArrayList<>(); for (int i = 1; i < 11; i++) { CityBean cityBean1 = new CityBean(); cityBean1.setCityId(i + 20); cityBean1.setCityName("上海" + i + "區"); shanghaiList.add(cityBean1); } CityBean cityBean1 = new CityBean(); cityBean1.setCityId(2); cityBean1.setCityName("上海" + j); cityBean1.setSubordinateList(shanghaiList); data.add(cityBean1); List<CityBean> shenzhenList = new ArrayList<>(); for (int i = 1; i < 11; i++) { CityBean cityBean2 = new CityBean(); cityBean2.setCityId(i + 20); cityBean2.setCityName("深圳" + i + "區"); shenzhenList.add(cityBean2); } CityBean cityBean2 = new CityBean(); cityBean2.setCityId(3); cityBean2.setCityName("深圳" + j); cityBean2.setSubordinateList(shenzhenList); data.add(cityBean2); } return data; }}
5,Item類,真正在listview中顯示的對象
public class Item { public static final int ITEM = 0;//判斷是否是普通item public static final int SECTION = 1;//判斷是否是需要置頂懸停的item public final int type;//外部傳入的類型,ITEM或者SECTION public final CityBean cityBean;//外部傳入的資料,這裡我們將它寫成城市實體類,可以任意更換 public int sectionPosition;//頭標記,一般用父資料的id標記 public int listPosition;//集合標記,一般用自身的id標記 public Item(int type, CityBean cityBean) { this.type = type; this.cityBean = cityBean; } //獲得其中儲存的資料 public CityBean getCityBean() { return cityBean; }}
Item類用來儲存外部傳進來的資料和設定該資料的類型是屬於懸停頭還是普通item。
6,最重要的,adapter
懸停listview的適配器必須實現PinnedSectionListView中的介面:PinnedSectionListAdapter和android原生介面:SectionIndexer
public class IndexAdapter extends BaseAdapter implements PinnedSectionListView.PinnedSectionListAdapter, SectionIndexer { //為了區分頭部的背景顏色,也可換成其他方式。例如:配置樣式 private static final int[] COLORS = new int[]{ R.color.green_light, R.color.orange_light, R.color.blue_light, R.color.red_light}; private List<CityBean> data;//外部傳進來的未經處理資料 private List<Item> items;//這個才是真正顯示的list private Context context; private Item[] sections;//頭標記數組 public IndexAdapter(Context context, List<CityBean> data) { this.data = data; this.context = context; initSection(); } //初始化顯示資料 private void initSection() { items = new ArrayList<>(); sections = new Item[data.size()]; //資料準備 for (int i = 0; i < data.size(); i++) { //添加頭資訊,將頭標記和資料傳入 Item section = new Item(Item.SECTION, data.get(i)); section.sectionPosition = data.get(i).getCityId();//將父類城市id作為頭id傳入,因為父類id沒有上級城市了,所以傳自身id section.listPosition = data.get(i).getCityId();//傳入自身id,將當前城市id作為普通id傳入 //頭標記組中城市id的標記相對應放入該城市的Item執行個體 sections[section.sectionPosition] = section; items.add(section); //當前城市的下級城市 for (int j = 0; j < data.get(i).getSubordinateList().size(); j++) { //下級城市為普通item,所以傳入Item.ITEM Item item = new Item(Item.ITEM, data.get(i).getSubordinateList().get(j)); item.sectionPosition = data.get(i).getCityId();//將父類城市id作為頭id傳入,證明該普通id下的城市屬於哪個父類城市 item.listPosition = data.get(i).getSubordinateList().get(j).getCityId();//傳入自身id,將當前城市id作為普通id傳入 items.add(item); } } } /************************* * 以下是PinnedSectionListView的重寫方法 *************************************************/ //當前view是否屬於固定的item @Override public boolean isItemViewTypePinned(int viewType) { return viewType == Item.SECTION; } /************************* * 以下是adapter的重寫方法 *************************************************/ //傳入檢視類型的個數,表示有幾種檢視類型 //PinnedSectionListView中如果發現adapter的getViewTypeCount<2會拋出異常 @Override public int getViewTypeCount() { return 2; } //返回每一個視圖的類型 @Override public int getItemViewType(int position) { return getItem(position).type; } @Override public int getCount() { return items.size(); } @Override public Item getItem(int position) { return items.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View view, ViewGroup parent) { View v; ViewHolder vh; Item item = items.get(position);// 從集合中擷取當前行的資料 if (view == null) { // 說明當前這一行不是重用的 // 載入行布局檔案,產生具體的一行 v = View.inflate(context, R.layout.item, null); // 建立儲存一行控制項的對象 vh = new ViewHolder(); // 將該行的控制項全部儲存到vh中 vh.tvName = (TextView) v.findViewById(R.id.text_text); v.setTag(vh);// 將vh儲存到行的Tag中 } else { v = view; // 取出隱藏在行中的Tag--取出隱藏在這一行中的vh控制項緩衝對象 vh = (ViewHolder) view.getTag(); } // 從ViewHolder緩衝的控制項中改變控制項的值 // 這裡主要是避免多次強制轉化目標對象而造成的資源浪費 vh.tvName.setText(item.getCityBean().getCityName()); if (item.type == Item.SECTION) { v.setBackgroundColor(parent.getResources().getColor(COLORS[item.sectionPosition % COLORS.length])); } return v; } // 儲存一行中的控制項(緩衝作用)---避免多次強轉每行的控制項 class ViewHolder { TextView tvName; } /************************ * 以下是SectionIndexer介面的重寫方法 ******************************************/ //返回一個對象數組代表列表的部分,用於來顯示頭 //這裡返回的Item[]是裝的頭的部分 @Override public Item[] getSections() { return sections; } //給定的索引部分數組內的部分對象,返回在適配器部分的起始位置。 @Override public int getPositionForSection(int sectionIndex) { //以免拋出異常 if (sectionIndex >= sections.length) { sectionIndex = sections.length - 1; } //返回當前頭集合的頭id return sections[sectionIndex].listPosition; } //給定一個適配器內的位置,返回相應的索引部分數組內的部分對象。 @Override public int getSectionForPosition(int position) { if (position >= getCount()) { position = getCount() - 1; } //返回當前item中儲存的頭id return getItem(position).sectionPosition; }}
整個項目的代碼就是這些,有興趣的可以看看PinnedSectionListView中是如何?的。我看了一部分,因為時間關係沒有繼續研究了。
demo下載
android UI——分組+懸停 listview