BaseAdapter的封裝來實現模組化開發
- 引子
要在一個手掌大的手機上展示各種豐富的資訊,橫向滑動與上下滑動已經成為了手機應用不可缺少的一部分。對於上下滑動,有一個被小看的神器ListView配合Adapter。為什麼被小看了呢?對比ScrollView來說,ListView不僅能夠實現不同介面的分離,便於開發與維護,而且其中的自動回收機制可以給使用者提供更好的流暢性。下面隨便截了幾個能夠通過ListView輕鬆實現的介面
看完本篇部落格後你就可以用最簡單的程式碼與最簡單的邏輯實現上面的介面了,甚至如果你已經理解了其中的原理那麼市面上95%以上的複雜帶滑動的介面你都可以通過ListView來實現。
- adapter的簡單介紹
ListView能有這麼強大的功能缺少不了Adapter的貢獻。對於ListView的封裝一般都是通過自定義ViewGroup來實現自動滑動回收佈局來實現類似瀑布流的功能。這兒主要介紹對adapter的封裝。
首先簡單介紹下adaper,首先google官方api(中國地區直接可用)-
https://developer.android.google.cn/reference/android/widget/BaseAdapter.html
簡單介紹下registerDataSetObserver、enable與getViewTypeCount,因為後面會利用這兩個方法進行設計
getViewTypeCount是放回這個佈局有多少種type-注意:這個方法只有第一次會呼叫,之後notifyDataSetChanged不會再次呼叫這個方法了
而registerDataSetObserver其中用到了觀察者模式,用處就是在notifyDataSetChanged的時候會去通知其中的觀察者。
enable是否可以進入,返回true就可以點選,返回false就沒有點選效果
- 對BaseAdapter中控制元件複用進行封裝
我們在使用baseadapter時一般都需要手動在getView中使用getTag,setTag方法來使其中的holder得到重用來減少findviewbyid從而提高效能。那麼使用以下SimpleListAdapter後這個工作就不需要你來手動進行了。
/**
* Created by Sober_philer on 2017/3/8 10:31
* adapter的基類,實現了holder的自定義封裝和一些基本方法的封裝
*/
public abstract class SimpleListAdapter<H,X extends SimpleListAdapter.SimpleHolder> extends BaseAdapter {
private List<H> dates = new ArrayList<>();//資料來源
protected Context context;
/**
* 需要更新資料是呼叫這個方法,如重新整理介面
* @param dates 新的資料
*/
public void replaceAll(List<H> dates){
this.dates.clear();
this.dates.addAll(dates);
notifyDataSetChanged();
}
//構造方法
public SimpleListAdapter(Context context) {
this.context = context;
}
private List<H> getDates(){
return dates;
}
@Override
public int getCount() {
return dates.size();
}
@Override
public H getItem(int position) {
return dates.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
X holder;
if(convertView == null){
convertView = createView(convertView, parent);
holder = createHolder(convertView);
convertView.setTag(holder);
}else {
holder = (X) convertView.getTag();
}
setContent(dates.get(position), convertView, holder);
return convertView;
}
/**
* 獲取當前adapter繫結的holder
*/
public abstract X createHolder(View parent);
/**
* 當前adapter的item的view
*/
public abstract View createView(View convertView, ViewGroup parent);
/**
* 繫結資料
*/
public abstract void setContent(H date, View convertView, X holder);
public static class SimpleHolder{}
}
使用方法如下
public class TestBaseAdapter extends Activity {
private ListView lv;
private List<String> dates = new ArrayList<>();
private InnerAdapter ad;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test_base_adapter);
lv = (ListView) findViewById(R.id.lv);
ad = new InnerAdapter(this);
lv.setAdapter(ad);
for(int i=0;i<100;i++)
dates.add("date : "+i);
ad.replaceAll(dates);
}
private static class InnerAdapter extends SimpleListAdapter<String, InnerAdapter.InnerHolder>{
public InnerAdapter(Context context) {
super(context);
}
@Override
public InnerHolder createHolder(View parent) {
return new InnerHolder(parent);
}
@Override
public View createView(View convertView, ViewGroup parent) {
return View.inflate(context, android.R.layout.simple_list_item_1, null);
}
@Override
public void setContent(String date, View convertView, InnerHolder holder) {
holder.tv1.setText(date);
}
public class InnerHolder extends SimpleListAdapter.SimpleHolder{
private TextView tv1;
InnerHolder(View v){
tv1 = (TextView) v.findViewById(android.R.id.text1);
}
}
}
}
- 上面的adapter只是對holder的簡單封裝,下面介紹實現上面3張圖的效果的一個關鍵adapter.其主要思想是通過itemtype來區分開不能的型別,當然這些都不需要我們來做。
/**
* Created by Sober_philer on 2017/3/8 09:23
* 可以放置多個adapter來實現不同的佈局
*/
public class SectionAdapter extends BaseAdapter {
private boolean isInited;//是否已經初始化
/**
* 是否可以點選,這兒返回false不讓點選
*/
@Override
public boolean isEnabled(int position) {
return false;
}
/**
* 各種adapter儲存在這兒
*/
private List<BaseAdapter> lists = new ArrayList<>();
/**
* 新增adapter
*/
public void addWrapper(BaseAdapter wrapper) {
if (isInited)//如果已經初始化了則丟擲異常,否則介面可能會顯示異常
throw new RuntimeException("cannot addWrapper after init");
lists.add(wrapper);
}
//計算所以的adapter中item的總數量
@Override
public int getCount() {
int count = 0;
for (BaseAdapter tempAd : lists) {
count += tempAd.getCount();
}
return count;
}
//計算需要的總type
@Override
public int getViewTypeCount() {
isInited = true;
int typeCount = 0;
for (BaseAdapter tempAd : lists) {
typeCount += tempAd.getViewTypeCount();
}
if(typeCount == 0)
return 1;
return typeCount;
}
//計算當前的type
@Override
public int getItemViewType(int position) {
int nowType = 0;
for (int i = 0; i < lists.size(); i++) {
BaseAdapter tempAd = lists.get(i);
if (position < tempAd.getCount()) {
return nowType + tempAd.getItemViewType(position);
} else {
nowType += tempAd.getViewTypeCount();
position -= tempAd.getCount();
}
}
throw new RuntimeException("error in Wraperadapter getitemView type");
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return 0;
}
//新增觀察者,否則notifydatesetchanged時子adapter不會生效
@Override
public void registerDataSetObserver(DataSetObserver observer) {
for(BaseAdapter tempAd : lists){
tempAd.registerDataSetObserver(observer);
}
}
//取消註冊觀察者,否則記憶體可能溢位
@Override
public void unregisterDataSetObserver(DataSetObserver observer) {
for(BaseAdapter tempAd : lists){
tempAd.unregisterDataSetObserver(observer);
}
}
//具體獲取那個adapter的view;
@Override
public View getView(int position, View convertView, ViewGroup parent) {
for (int i = 0; i < lists.size(); i++) {
BaseAdapter tempAd = lists.get(i);
if (position < tempAd.getCount()) {
return tempAd.getView(position, convertView, parent);
} else {
position -= tempAd.getCount();
}
}
return null;
}
}
上面的scetionadapter實現了一個模組一個模組的新增進來,各個佈局直接沒有耦合關係。比如引子中第三章網易雲的那張截圖,可以分為4個模組來實現。banner是一個模組、私人FM是一個模組、推薦歌單是一個模組、下面的內容是一個模組.
當然這兒只是介紹了一些理論,當你真正開始使用後你才會發現這樣使用的好處。