編輯:關於Android編程
ListView:一個可以垂直滑動的列表視圖。
setEmptyView()接口繼承至ListView的父類AdapterView。可想而知,ListView為空時,才會顯示EmptyView,這與ListView的數據適配器有間接的聯系。
List使用非常廣泛,用於具有相同數據類型的數據模型顯示,也可以自定義List以符合實際的需求。
本文主要介紹List.setEmptyView()接口。使用場景為,當客戶端當前顯示窗口中顯示一個ListView,ListView需要通過Adapter將數據關聯到列表上顯示出來。這就會出現一個場景,就是當未設置Adapter或Adapter裡面的數據為空時,如果給用戶一個友好的提示,提升用戶體驗。
Android官方源碼中已經提供了這樣的一個接口,通過這個接口,可以在使用ListView的過程中,利用內在的邏輯幫我們實現這個功能,減少代碼,讓並且是代碼更加的清晰,易懂。
實現方式:
EmptyView的添加包括兩種方式,一種是在創建布局的時候將EmptyView直接寫進去,這種方式的缺點在於缺乏靈活性。另一種是通過代碼將創建EmptyView,這種方式相對布局來添加更加具有靈活性,可以自定義EmptyView,動態的修改。當然即便通過布局的方式添加了EmptyView,也可以再次通過代碼添加。
1.布局文件實現EmptyView
…
2.代碼中實現
TextView emptyView = new TextView(context);
emptyView.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
emptyView.setText(R.string.tip_of_empty);
emptyView.setVisibility(View.GONE);
((ViewGroup)list.getParent()).addView(emptyView);
list.setEmptyView(emptyView);
注:添加EmptyView有一個前提條件,所添加的EmptyView必須存在於ListView的父級容器中,或者說同一個視圖樹中,才能生效,這涉及到List.setEmptyView()的原理。
EmptyView顯示的原理如上圖所示,當Adapter為null,或Adapter的數據為空時,即ListView沒有數據進行顯示時,ListVewi會被設置為View.GONE,而EmptyView被設置為View.VISABLE;當數據不為空時,邏輯相反。只有當EmptyView在當前的布局層級中,才能有這樣的效果,如上圖所示。
ListView初始化:
ListActivity.java中的源碼實現:作為官網的例子,可以看出其具體的使用方法與邏輯,包括在何時進行EmptyView的添加,以及EmptyView添加的流程關系。
ListActivity創建的布局層級:ListActivity,顧名思義,該Activity為使用者維護了一個ListView,但當創建ListActivity時,並沒有在布局層級中出現。
因此,去查看ListActivity的源碼,源碼中介紹,在使用ListActivity時,需調用setContentView()/setListAdapter ()進行初始化。
從源碼中可以看出,在setListAdapter方法調用了ensureList()方法。
在ensureList()方法中會對ListActivity維護的ListView進行判斷,如果為null,會調用setContentView,否則返回繼續執行。接著看setContentView。
setContentView()方法調用完後,發現並沒有我們想要的結果,也沒有對listView進行初始化等等,陷入僵局。但是我們仔細看源碼,發現setContentView()中調用了Window. setContentView()。通過各種方式,最後發現,Activity這個類實現了Window.Callback接口,當Activity調用setContentView()後,會回調onContentChanged()方法。
在onContentChanged()中,回去初始化ListView,EmptyView。
通過ListActivity,EmptyView添加的具體流程為:
接下來看ListView.setEmptyView()的實現源碼。
源碼分析:
/**
* Sets the view to show if the adapter is empty
*/
@android.view.RemotableViewMethod
public void setEmptyView(View emptyView) {
mEmptyView = emptyView;
// If not explicitly specified this view is important for accessibility.
if (emptyView != null
&& emptyView.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
emptyView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
}
final T adapter = getAdapter();
final boolean empty = ((adapter == null) || adapter.isEmpty());
updateEmptyStatus(empty);
}
在setEmptyView中,可以看到兩點,即ListView中的mEmptyView對傳入的View對象是一個引用關系,第二點就是:對empty的定義,當adapter==null,或者adapter.isEmpty(),然後傳入updateEmptyStatus()。
/**
* Update the status of the list based on the empty parameter. If empty is true and
* we have an empty view, display it. In all the other cases, make sure that the listview
* is VISIBLE and that the empty view is GONE (if it's not null).
*/
private void updateEmptyStatus(boolean empty) {
if (isInFilterMode()) {
empty = false;
}
if (empty) {
if (mEmptyView != null) {
mEmptyView.setVisibility(View.VISIBLE);
setVisibility(View.GONE);
} else {
// If the caller just removed our empty view, make sure the list view is visible
setVisibility(View.VISIBLE);
}
// We are now GONE, so pending layouts will not be dispatched.
// Force one here to make sure that the state of the list matches
// the state of the adapter.
if (mDataChanged) {
this.onLayout(false, mLeft, mTop, mRight, mBottom);
}
} else {
if (mEmptyView != null) mEmptyView.setVisibility(View.GONE);
setVisibility(View.VISIBLE);
}
}
updateEmptyStatus()方法的原理就相當於前面繪制的原理圖,通過設置View的visibility屬性,實現EmptyView的邏輯。然而,setEmpty只是在添加的時候進行一個界面更新,當有數據之後,Adapter必須通知ListView,再去更新當前的visibility屬性,所以去看下和Adapter相關的兩個數據更新方法。
/**
* Sets the data behind this ListView.
*
* The adapter passed to this method may be wrapped by a {@link WrapperListAdapter},
* depending on the ListView features currently in use. For instance, adding
* headers and/or footers will cause the adapter to be wrapped.
*
* @param adapter The ListAdapter which is responsible for maintaining the
* data backing this list and for producing a view to represent an
* item in that data set.
*
* @see #getAdapter()
*/
@Override
public void setAdapter(ListAdapter adapter) {
if (mAdapter != null && mDataSetObserver != null) {
mAdapter.unregisterDataSetObserver(mDataSetObserver);
}
resetList();
mRecycler.clear();
if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {
mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);
} else {
mAdapter = adapter;
}
mOldSelectedPosition = INVALID_POSITION;
mOldSelectedRowId = INVALID_ROW_ID;
// AbsListView#setAdapter will update choice mode states.
super.setAdapter(adapter);
if (mAdapter != null) {
mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();
mOldItemCount = mItemCount;
mItemCount = mAdapter.getCount();
checkFocus();
mDataSetObserver = new AdapterDataSetObserver();
mAdapter.registerDataSetObserver(mDataSetObserver);
mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());
int position;
if (mStackFromBottom) {
position = lookForSelectablePosition(mItemCount - 1, false);
} else {
position = lookForSelectablePosition(0, true);
}
setSelectedPositionInt(position);
setNextSelectedPositionInt(position);
if (mItemCount == 0) {
// Nothing selected
checkSelectionChanged();
}
} else {
mAreAllItemsSelectable = true;
checkFocus();
// Nothing selected
checkSelectionChanged();
}
requestLayout();
}
SetAdapter()方法中,有兩點,一個是checkFocus,另一個是為Adapter注冊了一個數據觀察者,後面源碼會介紹到,當adapter數據發送變化時,會回調觀察者的onChanged()方法。
checkFocus()源碼:
void checkFocus() {
final T adapter = getAdapter();
final boolean empty = adapter == null || adapter.getCount() == 0;
final boolean focusable = !empty || isInFilterMode();
// The order in which we set focusable in touch mode/focusable may matter
// for the client, see View.setFocusableInTouchMode() comments for more
// details
super.setFocusableInTouchMode(focusable && mDesiredFocusableInTouchModeState);
super.setFocusable(focusable && mDesiredFocusableState);
if (mEmptyView != null) {
updateEmptyStatus((adapter == null) || adapter.isEmpty());
}
}
可以看出,checkFocus()方法中,調用了updateEmptyStatus(),即在設置數據適配器的時候,會對EmptyView進行更新。
接下來看注冊registerDataSetObserver數據觀察者源碼:
public void registerDataSetObserver(DataSetObserver observer) {
mDataSetObservable.registerObserver(observer);
}
到此,setAdapter()方法的邏輯就結束了,然後setEmptyView()和setAdapter()方法只會在數據初始化的時候調用一次,當數據發送變化的時候,需要手動去更新Adapter調用notifyDataSetChanged()。
notifyDataSetChanged()源碼:
/**
* Notifies the attached observers that the underlying data has been changed
* and any View reflecting the data set should refresh itself.
*/
public void notifyDataSetChanged() {
mDataSetObservable.notifyChanged();
}
Adapter調用notifyDataSetChanged()方法,實質上是調用mDataSetObservable. notifyChanged()方法。繼續跟蹤下去。
/**
* Invokes {@link DataSetObserver#onChanged} on each observer.
* Called when the contents of the data set have changed. The recipient
* will obtain the new contents the next time it queries the data set.
*/
public void notifyChanged() {
synchronized(mObservers) {
// since onChanged() is implemented by the app, it could do anything, including
// removing itself from {@link mObservers} - and that could cause problems if
// an iterator is used on the ArrayList {@link mObservers}.
// to avoid such problems, just march thru the list in the reverse order.
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onChanged();
}
}
}
在notifyChanged()方法中,會遍歷所有注冊的數據觀察者,並回調觀察者的onChanged()方法,通過源碼可以看到,在Adapter源碼中,創建了一個內部類AdapterDataSetObserver,並重寫了onChanged()方法。
class AdapterDataSetObserver extends DataSetObserver {
private Parcelable mInstanceState = null;
@Override
public void onChanged() {
mDataChanged = true;
mOldItemCount = mItemCount;
mItemCount = getAdapter().getCount();
// Detect the case where a cursor that was previously invalidated has
// been repopulated with new data.
if (AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null
&& mOldItemCount == 0 && mItemCount > 0) {
AdapterView.this.onRestoreInstanceState(mInstanceState);
mInstanceState = null;
} else {
rememberSyncState();
}
checkFocus();
requestLayout();
}
}
onChanged()中有兩點,一點是mDataChanged,在updateEmptyStatus()方法中會去判斷該變量的狀態,當為true時,會更新ListView布局大小,而在onChanged()方法中會將mDataChanged置為true,通知布局更新,另一點是調用了checkFocus()方法,間接調用updateEmptyStatus()進行EmptyView的更新。
至此,EmptyView的設置基本的邏輯已經很清晰了,總結下。EmptyView的更新主要在updateEmptyStatus()中進行,在初始化ListView的Adapter以及數據更新後回調Adapter.notifyDataSetChanged()方法,其實質也是回調notifyDataSetChanged()方法。
導論 本文著重講解Android3.0後推出的屬性動畫框架Property Animation——Animator。
本文主要介紹Android4.4默認Home應用Launcher3的啟動過程和Launcher3的數據加載過程。Launcher的啟動是開機時,ActivityManag
很多Android手機隨機都預裝了很多無法卸載的第三方APP,這些APP既浪費資源還有偷跑流量的隱患。那麼,在不Root系統的前提下如何將它們“
現在很多Android市場中都能找到關於美女的應用,比如 撕開美女衣服、吹裙子等。 這些應用的下載量挺大的,作為Android的開發人員或者一名技術人員我們不能只局限在欣