Android:複雜滾動佈局的一種適配思路
App 的首頁向來是個寸土寸金的地方,如何在首頁吸引使用者是 PM 的工作,而對於我們開發者而言要做的就是適配。想必大家一定遇到過類似如下的設計稿:
在設計稿上我們可以看到一些重點:
- 頁面可以上下滾動
- 元素比較繁多
- 不同頁之間有分割線或者間隙
基於上面的幾點我們的思路無非分為兩條,第一條思路是:
使用ScrollView包裹LinearLayout並動態add多個佈局
這種方法的是可以解決問題的,各個板塊可以寫成子佈局,之間的風格線和間隙可以通過新增黑色的高度為 1 畫素的 View 和設定 MarginTop 來解決,重複的元素使用動態修改高度的 ListView 來適配。
確實簡單粗暴,但缺點是無法複用 View 比較浪費記憶體。
第二條思路,也就是本篇博文將要論述的思路:
將包括頂部的輪播圖、中間的活動版面甚至是分割線和間隙在內的東西都當成 Item 佈局,並使用單個ListView適配
較之前者,該方法不但解決了複用 View 的問題而且檢視的層次更少。
使用 RecyclerView 也是可以的不過思路其實和 ListView 一樣,這裡不再贅述。具體實現同樣也在給出的原始碼中,請自行查閱。
之後的章節筆者會詳細地闡述實現和程式碼,篇幅略長。如果你想直接看原始碼的話可以直接跳轉到文末。
BaseAdapter 的初步封裝
考慮到原有的 BaseAdapter 功能太弱,在正式開始工作之前有必要對之進行初步的封裝。首先我們在 BaseAdapter 中實現類似 RecyclerView 的 ViewHolder 機制,這裡先定義一個 AbsViewHolder 的基類,如下:
public static abstract class AbsViewHolder<Item> {
// 這裡使用一個泛型來引用該ViewHolder對應的資料項
private Item item;
private int position;
private final View view;
// 這裡省略get和set方法,完整的程式碼參見文末GitHub
}
之後我們定義 BaseAdapter 的基類程式碼如下:
// 每個Adapter都要確定自己能適配的資料型別和使用的ViewHolder
public abstract class CoreAdapter<Item, Holder extends AbsViewHolder<Item>> extends BaseAdapter {
// 將資料來源封裝進Adapter中方便後續的開發
private final List<Item> data = new ArrayList<>();
/* 繼承 */
// inflater的單例
private LayoutInflater inflater;
@Override
public final int getCount () {
// 由於資料來源在內部並且是final的,這裡可以寫死
return data.size();
}
@Override
public final Item getItem (int position) {
// 同上,寫死
return data.get(position);
}
@Override
public long getItemId (int position) {
return position;
}
@Override
public final View getView (int position, View convertView, ViewGroup parent) {
// 獲取inflater的單例
if (inflater == null) {
//當getView方法被呼叫時必定已經被set進ListView了
//此時parent.getContext()不會空指標
inflater = LayoutInflater.from(parent.getContext());
}
//獲取position對應的資料和viewType
int viewType = getItemViewType(position);
Item data = getItem(position);
//實現ViewHolder機制
Holder holder;
if (convertView == null) {
holder = createViewHolder(inflater, parent, viewType);
convertView = holder.getView();
convertView.setTag(holder);// 繫結ViewHolder
} else {
holder = (Holder) convertView.getTag();
}
// 將資料和position繫結到ViewHolder上
holder.setPosition(position);
holder.setItem(data);
bindViewData(holder, position, data, viewType);
return convertView;
}
/* 回撥 */
//建立ViewHolder例項
//如果你使用過RecyclerView的Adapter的話這裡你會發現這裡的思想與之基本一致
protected abstract Holder createViewHolder (LayoutInflater inflater, ViewGroup parent, int viewType);
//繫結資料到ViewHolder例項上
//也是RecyclerView.Adapter的思想
protected abstract void bindViewData (Holder holder, int position, Item data, int viewType);
/* 資料展示 */
//從外部新增資料項到Adapter內部的資料來源,並重新整理檢視
public final void display (Item... items) {
this.data.clear();
Collections.addAll(this.data, items);
notifyDataSetChanged();
}
public final void display (Collection<? extends Item> items) {
this.data.clear();
this.data.addAll(items);
notifyDataSetChanged();
}
//後續還可以拓展上add,remove之類的方法,這裡略去。
//完整程式碼請參閱GitHub
}
經過以上的修改我們已經將 ViewHolder 機制封裝進了 Adapter 之中,為後續的開發剩下了大量的程式碼。
具體實現
由於我們將所有的控制元件都當成Item佈局不用想也知道這裡會有數不清的 if-else 要寫,這顯然也是不夠優雅的。要解決這個問題我們就需要用到策略模式和策略工廠。
我們首先將每個 Item 的操作邏輯封裝成對應的策略,一個策略處理一種 Item 的例項化、繫結資料和事件回撥等邏輯。如下:
/*單個Item佈局的策略*/
public abstract class AbsItemOperator<Item, Holder extends AbsViewHolder<Item>> {
//該策略支援的資料型別的單例
private Type type;
//判斷該策略是否能夠適配obj的例項
public final boolean canHandleObject (Object obj) {
if (type == null) {
//獲取實現類的第一個泛型引數作為該策略的可適配型別
//ClassUtil請參見GitHub原始碼
Type types[] = ClassUtil.getGenericParametersType(getClass());
type = types[0];
}
if (obj instanceof Collection) {
//出於安全和可維護性考慮這裡禁止直接適配Collection的子類。
throw new IllegalStateException("不允許直接適配Collection或者其子類");
}
return type == obj.getClass();
}
/*回撥*/
//建立ViewHolder
public abstract Holder createViewHolder (LayoutInflater inflater, ViewGroup parent);
//繫結資料到ViewHolder
public abstract void bindViewData (Holder holder, Item data);
}
我們可以在這個基類中看到兩個抽象方法:createViewHolder()
和 bindViewData()
,只要在這兩個方法裡面實現對 Item 的處理即可。
之後我們需要將 Adapter 封裝一個策略工廠,負責策略的匹配和排程。程式碼如下:
public final class FlexibleAdapter extends CoreAdapter<Object, AbsViewHolder<Object>> {
//使用被final修飾的List來儲存所有支援的佈局策略
private final List<AbsItemOperator> operators = new ArrayList<>();
//在構造的時候必須指明所有支援的策略
public FlexibleAdapter (AbsItemOperator<?, ?>... operators) {
Collections.addAll(this.operators, operators);
}
/* 繼承 */
@Override
protected AbsViewHolder<Object> createViewHolder (LayoutInflater inflater, ViewGroup parent, int viewType) {
//createViewHolder方法是在之前的CoreAdapter之中定義的
//其中viewType引數來自getItemViewType(),其值也是目標策略在策略列表operators中的序數
//取出策略,直接排程
return (AbsViewHolder<Object>) operators.get(viewType).createViewHolder(inflater, parent);
}
@Override
protected void bindViewData (CoreAdapter.AbsViewHolder<Object> viewHolder, int position, Object data, int viewType) {
//同上,根據viewType取出策略並排程
operators.get(viewType).bindViewData(viewHolder, data);
}
@Override
public int getItemViewType (int position) {
//獲取position對應的資料例項
//並通過AbsItemOperator.canHandleObject()方法逐一匹配合適的策略
Object obj = getItem(position);
for (int i = 0, count = operators.size(); i < count; i++) {
if (operators.get(i).canHandleObject(obj)) {
return i;// 可處理
}
}
// 沒有策略能夠適配該資料
throw new IllegalStateException("指定資料型別不存在可用的operator");
}
@Override
public int getViewTypeCount () {
//策略列表的大小就是佈局型別數
return operators.size();
}
}
至此整體的框架就已經搭建完畢,往後如果有新增的Item佈局只需要新增一個獨立的策略實現類即可,十分靈活。
實現一個策略
這裡我們寫一個策略以加深對這種思路的理解。佈局檔案很簡單:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="48dp"
android:background="@color/core_white">
<View
android:layout_width="24dp"
android:layout_height="16dp"
android:layout_gravity="center_vertical"
android:background="@color/core_holo_green_light"/>
<TextView
android:id="@+id/textView_listView_tip"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"/>
</FrameLayout>
有點經驗的開發者應該立馬就能想象到它例項化後的樣子,主體只是一個用來顯示文字的 TextView 而已。接下來是它對應的策略:
public class TipOperator extends AbsItemOperator<Tip, TipViewHolder> implements View.OnClickListener {
//寫一個AbsViewHolder的基類,ViewHolder本身其實還可以深挖不少東西,這裡篇幅有限就不再贅述
public static class TipViewHolder extends AbsViewHolder<Tip> {
public TipViewHolder(View v) {
super(v);
}
}
/*繼承*/
@Override
public TipViewHolder createViewHolder(LayoutInflater inflater, ViewGroup parent) {
//例項化佈局
View view = inflater.inflate(R.layout.activity_listview_tip, parent, false);
//繫結事件
view.setOnClickListener(this);
return new TipViewHolder(textView);
}
@Override
public void bindViewData(TipViewHolder holder, Tip data) {
//繫結資料
TextView textView = (TextView) holder.getView().findViewById(R.id.textView_listView_tip);
textView.setText(data.tip);
}
/*回撥*/
@Override
public void onClick(View v) {
//因為CoreAdapter中已經實現了ViewHolder機制因而itemView.getTag返回的必定是對應的ViewHolder,在這裡就是TipViewHolder
//在設計上ViewHolder和ItemView的例項是繫結在一起的,只是攜帶者的position和data一直在變化
//因此只要獲得ViewHolder就能獲得當前的position和data
TipViewHolder holder = (TipViewHolder) v.getTag();
if(onTipClickListener != null){
//回撥監聽者
onTipClickListener.onTipClick(v,holder.getPosition(),holder.getItem());
}
}
/*介面*/
//監聽者介面
public interface OnTipClickListener{
void onTipClick(View v,int position,Tip tip);
}
private OnTipClickListener onTipClickListener;
public TipOperator setOnTipClickListener(OnTipClickListener onTipClickListener) {
this.onTipClickListener = onTipClickListener;
return this;
}
}
實際使用時如下:
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ListView listView = new ListView(this);
listView.setDivider(null);
listView.setDividerHeight(0);
setContentView(listView);
setTitle(getClass().getSimpleName());
//例項話FlexibleAdapter並填入所有的佈局策略
FlexibleAdapter adapter = new FlexibleAdapter(new RotateOperator(), new TipOperator(), new PanelOperator(), new MsgOperator(), new DividerOperator(), new SpanOperator());
listView.setAdapter(adapter);
//逐一新增每個資料項
List<Object> list = new ArrayList<>();
Rotate rotate = new Rotate(R.layout.activity_listview_rotate_1, R.layout.activity_listview_rotate_2, R.layout.activity_listview_rotate_3);
list.add(rotate);
list.add(new Divider());
list.add(new Span());
int times = 5;
while (times-- > 0) {
list.add(new Divider());
Tip tip = new Tip("這是一個Tip");
list.add(tip);
list.add(new Divider());
Panel panel = new Panel("這個是內容,我就隨便輸入一些什麼東西");
list.add(panel);
list.add(new Divider());
list.add(new Span());
}
list.add(new Divider());
times = 20;
while (times-- > 0) {
list.add(new Msg("這是文字35435435453545354"));
}
//重新整理到介面上
adapter.display(list);
}
以上程式碼執行起來後效果如下:
之後新增一個佈局只需要新增一個對應的策略即可。
最後附上原始碼地址:
相關推薦
Android:複雜滾動佈局的一種適配思路
App 的首頁向來是個寸土寸金的地方,如何在首頁吸引使用者是 PM 的工作,而對於我們開發者而言要做的就是適配。想必大家一定遇到過類似如下的設計稿: 在設計稿上我們可以看到一些重點: 頁面可以上下滾動 元素比較繁多 不同頁之間有分割線或者間隙
表現層響應式:響應式佈局和螢幕適配佈局
1.響應式佈局: 響應式佈局是根據瀏覽器寬度,解析度,橫屏,豎屏等情況來自動改變元素展示的一種佈局方式,一般可以使用柵格方式來實現,一般有兩種思路,一種是桌面端瀏覽器優先,擴充套件到移動端瀏覽器適配,另一種是以移動端優先,擴充套件到桌面瀏覽器適配。由於移動端網路和計算
Android項目實戰(十二):解決OOM的一種偷懶又有效的辦法
建議 是什麽 cat 解決 blog www. android項目 roi acm 原文:Android項目實戰(十二):解決OOM的一種偷懶又有效的辦法在程序的manifest文件的application節點加入android:largeHeap=“true&
C++從零開始區塊鏈:main函式的一種實現
前面已經把各種業務邏輯都寫好了,main函式怎麼呼叫就隨便了,這裡只是其中一種實現方法 int main(int argc, char **argv) { if (argc < 2) { std::cout << "argc error!
作業系統,核心定時器:使用“訊號”建立一種使用者空間機制來測量一個多執行緒程式的執行時間。
核心是一個作業系統的核心。它負責管理系統的程序、記憶體、裝置驅動程式、檔案和網路系統,決定著系統的效能和穩定性。 定時器是Linux提供的一種定時服務的機制,它在某個特定的時間喚醒某個程序來進行工作。核心在時鐘中斷髮生後檢測各定時器是否到期,在li
android Collections.sort排序的一種使用
//按照javabean的時間排序 Collections.sort(infosList, new Comparator<Dzbp_Info>() { @Override public int compare(Dzbp_Info lhs, Dzbp_Info rhs)
VS程式設計,WPF中兩個滾動條 ScrollViewer 同步滾動的一種方法
這裡以兩個ScrollViewer控制元件之間的同步滾動為例。 當滑鼠拖動其中一個滾動條時,另一個滾動條跟著一起調整到相應的位置。 1、前臺建立兩個ScrollViewer控制元件,並分別給兩個滾動條控制元件命名。 <Stack
Table標題行凍結,資料行滾動的一種方式
這段時間在做Table標題行凍結,資料行滾動,雖然能實現,但也遇到一些問題,記錄下來。 首先說說實現,實現其實不難,估計很多人都能想象出來,那就是標題行與內容行分離。我是這麼做的,用兩個表格,一個
Android:控制元件佈局(相對佈局)RelativeLayout RelativeLayout是相對佈局控制元件:以控制元件之間相對位置或相對父容器位置進行排列。 相對佈局常用屬性: 子類控制元件相對子
RelativeLayout是相對佈局控制元件:以控制元件之間相對位置或相對父容器位置進行排列。 相對佈局常用屬性: 子類控制元件相對子類控制元件:值是另外一個控制元件的id android:layout_above----------位於給定DI控制元件之上 android:layout_below -
在Android Studio下的一種模組化思路
隨著專案功能的增多,我們在Android Studio下的module也越來越多,或者說本身我們就想將功能按module劃分。比如,你想做個 “微信” 一樣的應用,有聊天模組、朋友圈模組,購物模組、遊戲模組等等,你想把這些模組以module劃分,通常情況下是將這些module打包成一個微信a
Android之ScrollView滾動佈局控制元件使用以及顯示新聞網頁
ScrollView滾動佈局使用原理: ①滾動產生的條件是,裡面的內容大於物理尺寸 ②ScrollView裡面只有一個子元素,這個子元素就是一個線性佈局LinearLayout,我們可以線上性佈局中新增我們需要的內容,所以ScrollView中得包裹一層,並且線性佈局中設計
關於處理if和複雜邏輯的一種思路及…
折騰了好久的qbs的解決方案今天終於有眉目了,這個設計思路還是很巧妙的,對於處理多路if else等有非常好的效果,提高了程式碼的靈活性和設計的可重用性,程式碼更加簡潔,邏輯更加清晰,真的得好好的研究一番,明天開始正式把程式碼敲出來,慢慢的就會成為自己的東西,真正的
Android 6.0+ 動態許可權 一種清爽的封裝過程(以及多個許可權的處理)
Android 6.0 之前我們申請許可權直接在配置檔案中配置一下即可,但是6.0之後,谷歌官方將許可權分為普通許可權和危險許可權。對於危險許可權來說,我們就需要進行動態設定了。本文主要講解為什麼要進行Android 6.0 動態許可權的設定、動態許可權的使用、
基於 android 資料備份恢復的一種實現
引言 隨著 3G 時代的到來,移動網際網路的發展,手機的功能越來越強大,手機裡的資料對每個使用者來說都非常的重要,特別是通訊錄、日程、簡訊息、郵件等資料,一旦手機丟失、誤刪或其他意外使得資料無法正常使用,會給使用者帶來麻煩,資料備份與恢復這個應用可以幫助使用者解決這個問題。
android 讀取本地檔案的一種方式
try {String fileUri = "layout.xml";InputStream is =MainApplication.getInstance().getAssets().open(fileUri);int size = is.available();//
Android:wpa_supplicant決定選擇哪種驅動
1, main 函式的入口: external/wpa_supplicant_8/wpa_supplicant/main.c 2, init.rc中通過引數指定要載入哪個驅動 3, wpa_supplicant中載入驅動的原理 main.c: main() --&
android listview & toolbar形成的一種炫酷效果(外加一個圓形圖片的實現)
介面的效果用到的技術都很基本, 但實現的思路很新穎, 程式碼和佈局充滿各種技巧, 值得學習和借鑑. 效果圖: 原理圖: 佈局程式碼: <RelativeLayout xmlns:android="http://schemas.android.com/apk/res
Android下基於Iptables的一種app網路訪問控制方案(一)
1.什麼是Iptable? 百度百科對於Iptables有詳細的介紹。簡單地說,Iptables是Linux核心提供的一套IP資訊包過濾系統,對外由Iptables命令提供設定過濾規則的入口。 Android是基於Linux的作業系統,支援Iptables。執行Iptabl
Android Studio中使用自定義Android.jar缺少方法的一種解決方案
最近在Android Studio專案中ListView的scrollListBy方法,這個方法在SDK 19及其以上版本才有的,而專案使用的自定義Android.jar中的ListView並不包含這個方法。 如果為了使用這個方法,而重新編譯Android.jar,代價太大
android:wifi通訊(一)
上傳了原始碼,自己下載看吧:http://download.csdn.net/detail/bigtree_mfc/9528424 關於wifi操作: 1、開啟wifi 2、關閉wifi 3、獲取wifi網絡卡狀態 activity_main.xml中新增三個按鈕 pu