編輯:關於Android編程
接著上篇文章,現在在通過Android實際開發和源碼再探觀察者模式,listview是我們日常開發中必用的控件,雖然之前就有listview的替代品(recyclerview),現在不去深究兩個控件到底誰好誰差,但有一點需要強調下,它們之間有一大相同點,都是通過觀察者模式去實現數據更新。
首先,我們先去看一個簡單的例子
/**
*
* created by zero on2016-6-1
*
* 通過listview再探觀察者模式
*
*/
public class MainActivity extends Activity
{
private ListView lv_simple;
private ArrayAdapter adapter;
private Button btn_add;
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main_listview);
lv_simple = (ListView) findViewById(R.id.lv_simple);
btn_add = (Button) findViewById(R.id.btn_add);
btn_add.setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
adapter.add("will");
adapter.add("go");
adapter.add("on");
adapter.notifyDataSetChanged();
}
});
adapter = new ArrayAdapter(this,
android.R.layout.simple_expandable_list_item_1);
adapter.add("my");
adapter.add("heart");
lv_simple.setAdapter(adapter);
}
}
布局文件就一個listview加上一個button,沒必要貼代碼了,現在直奔主題,核心分析listview和ArrayAdapter之間的關系,我們都知道listview添加adapter通過setAdapter(adapter)方法,當刷新的時候,需要調用adapter.notifyDataSetChanged()方法,首先,我們先看下ArrayAdapter父類BaseAdapter的源碼<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwcmUgY2xhc3M9"brush:java;">
public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter {
private final DataSetObservable mDataSetObservable = new DataSetObservable();
public boolean hasStableIds() {
return false;
}
public void registerDataSetObserver(DataSetObserver observer) {
mDataSetObservable.registerObserver(observer);
}
public void unregisterDataSetObserver(DataSetObserver observer) {
mDataSetObservable.unregisterObserver(observer);
}
/**
* 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();
}
/**
* Notifies the attached observers that the underlying data is no longer valid
* or available. Once invoked this adapter is no longer valid and should
* not report further data set changes.
*/
public void notifyDataSetInvalidated() {
mDataSetObservable.notifyInvalidated();
}
public boolean areAllItemsEnabled() {
return true;
}
public boolean isEnabled(int position) {
return true;
}
public View getDropDownView(int position, View convertView, ViewGroup parent) {
return getView(position, convertView, parent);
}
public int getItemViewType(int position) {
return 0;
}
public int getViewTypeCount() {
return 1;
}
public boolean isEmpty() {
return getCount() == 0;
}
}
之前都是繼承BaseAdapter,卻很少了解它的源碼,裡面部分方法,在最初工作的兩年裡面,壓根沒看到過,先對一些方法作出解釋,如下:
int getItemViewType(int position)
int getViewTypeCount()
如果ListView需要顯示多種類型的內容,就需要有不同的緩存拿來使用。比如一個listview裡面有好幾種類型的item,就像基數是一種item,偶數是一種item,或者更復雜的布局,這時候這兩個方法就可以實現我們需要的頁面。
long getItemId(int position)
boolean hasStableIds()
getItemId是干嘛用的?在調用 invalidateView()時,ListView會刷新顯示內容。如果內容的id是有效的,系統會跟據id來確定當前顯示哪條內容,也就是firstVisibleChild的位置。id是否有效通過hasStableIds()確定。
boolean areAllItemsEnabled()
boolean isEnabled(int position)
在我們點擊item的時候,系統會有一個默認的顏色,如果不希望Item點擊時出現背景顏色,就使用這兩個方法。它們分別針對所有和單個View實現。這個確實有點惡心,之前一直在listview裡面找方法,最後沒轍了,就在listview裡面設置背景色透明,我相信一定會有好多小伙伴和我一樣這麼做的,哈哈。
registerDataSetObserver(DataSetObserver observer)
unregisterDataSetObserver(DataSetObserver observer)
對於這兩個方法,這裡作為重點講述,通過上一篇博客,看到observer是不是瞬間很親切,根據字義我們應該就明白這是一個注冊和取消注冊的操作,之前也講述過,observer和observable通過注冊相關聯,當observable發生改變的時候,observer會和observable保持同步操作,這裡DataSetObservable的是干嘛用的?看到名字就知道是和Observable相關,我們在回憶一下,上文提到observable中一個很重要的條件就是必須要有一個容器,DataSetObservable會不會和容器相關呢?我們繼續追查下去。
/**
* A specialization of {@link Observable} for {@link DataSetObserver}
* that provides methods for sending notifications to a list of
* {@link DataSetObserver} objects.
*/
public class DataSetObservable extends Observable {
/**
* 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();
}
}
}
/**
* Invokes {@link DataSetObserver#onInvalidated} on each observer.
* Called when the data set is no longer valid and cannot be queried again,
* such as when the data set has been closed.
*/
public void notifyInvalidated() {
synchronized (mObservers) {
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onInvalidated();
}
}
}
}
先看到這裡,通過上述的注釋可知道,當observable發生改變的時候,便會通知DataSetObserver對象的列表發生對應的改變。現在,好像明白了點什麼,但是我們沒有找到容器,我們繼續追查下去,接下來,我們再看下DataSetObserver的父類,如下:
public abstract class Observable {
/**
* The list of observers. An observer can be in the list at most
* once and will never be null.
*/
protected final ArrayList mObservers = new ArrayList();
/**
* Adds an observer to the list. The observer cannot be null and it must not already
* be registered.
* @param observer the observer to register
* @throws IllegalArgumentException the observer is null
* @throws IllegalStateException the observer is already registered
*/
public void registerObserver(T observer) {
if (observer == null) {
throw new IllegalArgumentException("The observer is null.");
}
synchronized(mObservers) {
if (mObservers.contains(observer)) {
throw new IllegalStateException("Observer " + observer + " is already registered.");
}
mObservers.add(observer);
}
}
/**
* Removes a previously registered observer. The observer must not be null and it
* must already have been registered.
* @param observer the observer to unregister
* @throws IllegalArgumentException the observer is null
* @throws IllegalStateException the observer is not yet registered
*/
public void unregisterObserver(T observer) {
if (observer == null) {
throw new IllegalArgumentException("The observer is null.");
}
synchronized(mObservers) {
int index = mObservers.indexOf(observer);
if (index == -1) {
throw new IllegalStateException("Observer " + observer + " was not registered.");
}
mObservers.remove(index);
}
}
/**
* Remove all registered observers.
*/
public void unregisterAll() {
synchronized(mObservers) {
mObservers.clear();
}
}
}
我們在理一下,重新提取核心代碼,如下:
public class DataSetObservable extends Observable
public abstract class Observable {
protected final ArrayList mObservers = new ArrayList();
}
現在一目了然了,我們找到了容器了,Observable類裡面就是注冊observer、取消特定某個observer的注冊,還有取消容器中所有observer的注冊,DataSetObservable裡面提供了notifyChanged()方法,遍歷mObservers,通知所有observer發生對應的改變,另外,還提供了notifyInvalidated()方法,在數據源失效的時候會調用這個方法。
現在,我們在分析DataSetObserver,毫無疑問,這個肯定就是和Observer相關喽。先看源碼,如下:
public abstract class DataSetObserver {
/**
* This method is called when the entire data set has changed,
* most likely through a call to {@link Cursor#requery()} on a {@link Cursor}.
*/
public void onChanged() {
// Do nothing
}
/**
* This method is called when the entire data becomes invalid,
* most likely through a call to {@link Cursor#deactivate()} or {@link Cursor#close()} on a
* {@link Cursor}.
*/
public void onInvalidated() {
// Do nothing
}
對應DataSetObservable中的兩個遍歷,一切都是那麼的清晰,O(∩_∩)O哈哈~
listview和adapter關聯是通過setAdapter(adapter)源碼如下:
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();
}
listview有4000行左右的代碼,此處就拷貝一些必要代碼,通過以上代碼可知,當mAdapter和mDataSetObserver同時不為null的時候,便會把mDataSetObserver取消注冊,當mAdapter不為null的時候,便會在mAdapter裡面進行注冊了一個觀察者。s當數據集發生改變的時候,我們通過adapter.notifyDataSetChanged()方法去改變數據,點進去後,追查到的代碼如下:
@Override
public void notifyDataSetChanged() {
super.notifyDataSetChanged();
mNotifyOnChange = true;
}
這是ArrayAdapter裡面的代碼,我們接著追查它的父類BaseAdapter
public void notifyDataSetChanged() {
mDataSetObservable.notifyChanged();
}
最後還是指向了DataSetObservable的notifyChanged()方法,一個輪回後,又重新回到了原點,遍歷通知發生改變。
現在還有一個最後問題,是怎樣更新界面的,onchanged()方法裡面什麼都沒操作,到底是在哪裡更新的呢?好吧,我們現在再把目光朝前放一放,回到listview源碼中,剛剛提到如果adapter不為null的時候,便會注冊一個observer到adapter中,關鍵的兩行代碼提取,如下:
mDataSetObserver = new AdapterDataSetObserver(); mAdapter.registerDataSetObserver(mDataSetObserver);
當我們一路追蹤AdapterDataSetObserver的時候,發現AdapterDataSetObserver是AdapterView的內部類,代碼如下:
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();
}
@Override
public void onInvalidated() {
mDataChanged = true;
if (AdapterView.this.getAdapter().hasStableIds()) {
// Remember the current state for the case where our hosting activity is being
// stopped and later restarted
mInstanceState = AdapterView.this.onSaveInstanceState();
}
// Data is invalid so we should reset our state
mOldItemCount = mItemCount;
mItemCount = 0;
mSelectedPosition = INVALID_POSITION;
mSelectedRowId = INVALID_ROW_ID;
mNextSelectedPosition = INVALID_POSITION;
mNextSelectedRowId = INVALID_ROW_ID;
mNeedSync = false;
checkFocus();
requestLayout();
}
public void clearSavedState() {
mInstanceState = null;
}
}
在adapter.notifyDataSetChanged()時,便會調用裡面的onchangd()方法,通過mItemCount = getAdapter().getCount()獲取adapter中數據的數量,通過requestLayout()重新布局刷新界面。
剛准備到此為止,忽然想起有件事還沒做完,現在,我們再簡單看下recyclerview與觀察者模式,不多說,直接撸代碼:
public static abstract class Adapter
{
private final RecyclerView.AdapterDataObservable mObservable;
//部分代碼省略
//接下來的代碼會瞬間讓你變得熟悉
public final boolean hasObservers()
{
return this.mObservable.hasObservers();
}
public void registerAdapterDataObserver(RecyclerView.AdapterDataObserver observer)
{
this.mObservable.registerObserver(observer);
}
public void unregisterAdapterDataObserver(RecyclerView.AdapterDataObserver observer)
{
this.mObservable.unregisterObserver(observer);
}
public final void notifyDataSetChanged()
{
this.mObservable.notifyChanged();
}
//recyclerview可以單獨刷新item,原因就在此
public final void notifyItemChanged(int position)
{
this.mObservable.notifyItemRangeChanged(position, 1);
}
public final void notifyItemRangeChanged(int positionStart, int itemCount)
{
this.mObservable.notifyItemRangeChanged(positionStart, itemCount);
}
public final void notifyItemInserted(int position)
{
this.mObservable.notifyItemRangeInserted(position, 1);
}
public final void notifyItemMoved(int fromPosition, int toPosition)
{
this.mObservable.notifyItemMoved(fromPosition, toPosition);
}
public final void notifyItemRangeInserted(int positionStart, int itemCount)
{
this.mObservable.notifyItemRangeInserted(positionStart, itemCount);
}
public final void notifyItemRemoved(int position)
{
this.mObservable.notifyItemRangeRemoved(position, 1);
}
public final void notifyItemRangeRemoved(int positionStart, int itemCount)
{
this.mObservable.notifyItemRangeRemoved(positionStart, itemCount);
}
}
recyclerview可以指定范圍刷新、插入刷新、移動刷新…一個控件,多套布局,多種玩法,這TM是不是有點違背了單一職責原則。。。
AdapterDataObservable
static class AdapterDataObservable extends Observable
{
public boolean hasObservers()
{
return !this.mObservers.isEmpty();
}
public void notifyChanged()
{
for (int i = this.mObservers.size() - 1; i >= 0; --i)
((RecyclerView.AdapterDataObserver)this.mObservers.get(i)).onChanged();
}
public void notifyItemRangeChanged(int positionStart, int itemCount)
{
for (int i = this.mObservers.size() - 1; i >= 0; --i)
((RecyclerView.AdapterDataObserver)this.mObservers.get(i)).onItemRangeChanged(positionStart, itemCount);
}
public void notifyItemRangeInserted(int positionStart, int itemCount)
{
for (int i = this.mObservers.size() - 1; i >= 0; --i)
((RecyclerView.AdapterDataObserver)this.mObservers.get(i)).onItemRangeInserted(positionStart, itemCount);
}
public void notifyItemRangeRemoved(int positionStart, int itemCount)
{
for (int i = this.mObservers.size() - 1; i >= 0; --i)
((RecyclerView.AdapterDataObserver)this.mObservers.get(i)).onItemRangeRemoved(positionStart, itemCount);
}
public void notifyItemMoved(int fromPosition, int toPosition)
{
for (int i = this.mObservers.size() - 1; i >= 0; --i)
((RecyclerView.AdapterDataObserver)this.mObservers.get(i)).onItemRangeMoved(fromPosition, toPosition, 1);
}
}
AdapterDataObserver
public static abstract class AdapterDataObserver
{
public void onChanged()
{
}
public void onItemRangeChanged(int positionStart, int itemCount)
{
}
public void onItemRangeInserted(int positionStart, int itemCount)
{
}
public void onItemRangeRemoved(int positionStart, int itemCount)
{
}
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount)
{
}
}
不做多余解釋,今天到此為止。
Android輸入法框架(Input Method Framework,IMF)是Android中非常重要的模塊,它分布於三個部分(確切的說,是三個進程), 包含編輯框
簡介好久沒有寫博客了,最近公司產品剛發布了,終於有一點休閒的時間了,趁這個空閒的時間,今天給大家寫一個數字動畫的效果,可能在以後的時間,或許大家會用的到,值得大家收藏,直
一、Socket通信簡介Android與服務器的通信方式主要有兩種,一是Http通信,一是Socket通信。兩者的最大差異在於,http連接使用的是“請求&m
Android事件驅動編程-基於EventBus(一) 雖然在Android開發具有某些事件驅動的特性,但它還遠不是純粹的事件驅動架構。這算是好事還是壞事呢