Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> ListView注意的問題和源碼解析(上)

ListView注意的問題和源碼解析(上)

編輯:關於Android編程

設置沒有數據時顯示的默認布局

setEmptyView這個方法傳入的是一個view,

TextView emptytext=new TextView(this);
emptytext.setText("the list is empty");
listview.setEmptyView(emptytext);
上面的代碼有什麼問題呢?當listview為空時,emptytext能正常顯示嗎?
運行測試之後會發現,emptytext沒有顯示,是因為沒有設置大小嗎?debug代碼,
查看當前的root布局,子布局中怎麼也找不到emptytext這個view。這個布局根本就沒有加載,更別說繪制了。
通過源碼可以發現setEmptyView方法只是把AdapterView的屬性mEmptyView設置為emptyView

 

 

    @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);
    }

在更新數據時,在更新數據時控制mEmptyView和listVeiw的顯示狀態

 

 

   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);
        }
    }

 

ListView添加刪除更新header和footer

對footer和header的操作相同,這裡以header為例。其實是完全相同的,最終都被封裝為FixedViewInfo,只是顯示的位置不同
ListView可以添加多個header

 

TextView header = new TextView(this);
header.setText("this is header 1");
listview.addHeaderView(header);
header = new TextView(this);
header.setText("this is header 2");
listview.addHeaderView(header);
header和footer連同adapter被包裝在了HeaderViewListAdapter中,
header和footer相關view保存在了下面兩個方法中

 

 

ArrayList mHeaderViewInfos;
ArrayList mFooterViewInfos;
HeaderViewListAdapter並沒有提供獲取FixedViewInfo的方法,因此無法獲取FixedViewInfo的實例。
如果外部沒有header的引用,就無法獲取這個header了。這個時候要更新header就需要先移除,然後重新創建,重新添加。
重新創建是很浪費內存的,如果header的數據要更新,還是在外部持有引用直接更新比較好。
header並不參與View復用,外部持有引用可直接修改,不需要通知adapter刷新數據。

 

listView中多種類型布局使用

BaseAdapter中默認是一種類型布局,支持多種類型布局需要在自定義的Adapter中重寫這兩個方法

 

    public int getViewTypeCount() {
        return 1;
    }
       public int getItemViewType(int position) {
        return 0;
    }

getViewTypeCount返回布局類型個數。getItemViewType返回某個位置的布局類型。
這個地方容易出現數組越界的問題。getItemViewType的返回值是不可以隨便寫的。它的最大值不超過TypeCount-1,

 

否則就會數組越界。
這是因為在復用多種布局時,TypeCount是布局類型最大個數,而ItemViewType是一個下標,

這個下標的最大值就是TypeCount-1。

 

        /**
         * Unsorted views that can be used by the adapter as a convert view.
         */
        private ArrayList[] mScrapViews;

        public void setViewTypeCount(int viewTypeCount) {
            if (viewTypeCount < 1) {
                throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
            }
            //noinspection unchecked
            ArrayList[] scrapViews = new ArrayList[viewTypeCount];
            for (int i = 0; i < viewTypeCount; i++) {
                scrapViews[i] = new ArrayList();
            }
            mViewTypeCount = viewTypeCount;
            mCurrentScrap = scrapViews[0];
            mScrapViews = scrapViews;
        }

 

 View getScrapView(int position) {
            final int whichScrap = mAdapter.getItemViewType(position);
            if (whichScrap < 0) {
                return null;
            }
            if (mViewTypeCount == 1) {
                return retrieveFromScrap(mCurrentScrap, position);
            } else if (whichScrap < mScrapViews.length) {
                return retrieveFromScrap(mScrapViews[whichScrap], position);
            }
            return null;
        }

 

OnItemClickListener中獲取獲取點擊的item數據

public void onItemClick(AdapterViewparent, View view, int position, long id)
這個方法的postiton並不是真實的item數據位置,當沒有header時,positon與item位置對應
當有header時,positon=headercount+item位置,也就是說
item位置=positon-headercount
listview.getHeaderViewsCount()可以獲取headercount
還有一種方法是通過包裝過的adapter直接獲取
parent.getAdapter().getItem(position);

異步加載圖片混亂

當滑動ListView的時候,圖片自動變來變去,圖片顯示的位置也不正確。
所有的主流圖片加載庫都解決了這個問題。這個問題的原因是listview的item復用。
解決這個問題的思路都是建立當前imageview與加載的url的對應關系,使當前看到的item始終加載的是當前對應數據。

Picasso通過建立ImageView與ImageViewAction的的對應關系來加載正確圖片

 

 void enqueueAndSubmit(Action action) {
    Object target = action.getTarget();
    if (target != null && targetToAction.get(target) != action) {
      // This will also check we are on the main thread.
      cancelExistingRequest(target);
      targetToAction.put(target, action);
    }
    submit(action);
  }

 

RecycleBin機制

View復用方式

 

    public static final class ViewHolder {
        public ImageView img;
        public TextView name;

        public ViewHolder(View convertView) {
            img = (ImageView) convertView.findViewById(R.id.img);
            name = (TextView) convertView.findViewById(R.id.name);
        }
    }

  @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder viewHolder;
            if (convertView == null) {
                convertView = LayoutInflater.from(ListViewActivity.this).inflate(R.layout.item_list, parent, false);
                viewHolder = new ViewHolder(convertView);
                convertView.setTag(viewHolder);
            } else {
                viewHolder = (ViewHolder) convertView.getTag();
            }
            viewHolder.name.setText(data.get(position));
            return convertView;
        }

 

源碼解析

單獨再做分析吧

 

ListView中的設計模式

ListView與Adapter的應用就是典型的適配器模式

ListView與Adapter都實現了接口ListAdapter,同時ListView又包裹了Adapter,從而實現適配,是典型的對象適配。

裝飾模式

HeaderViewListAdapter是典型的裝飾模式
對Adapter進行裝飾,為Adapter擴展了footer和header

數據刷新觀察者模式

在設置Adapter時會構建一個AdapterDataSetObserver,這就是創建觀察者
adapter中包含一個數據集可觀察者DataSetObservable,這就是可觀察者
adapter的notifyDataSetChanged就是可觀察者通知觀察者數據發生了變化。

其它常見小問題:

點擊item沒反應

原因是tem子控件獲取了焦點,在Item布局的根布局加上android:descendantFocusability=“blocksDescendants”

的屬性就可以了。
descendantFocusability值含義:
beforeDescendants:viewgroup會優先其子類控件而獲取到焦點
afterDescendants:viewgroup只有當其子類控件不需要獲取焦點時才獲取焦點
blocksDescendants:viewgroup會覆蓋子類控件而直接獲得焦點

給ListView加上背景圖片,或者背景顏色時,滾動時listView會黑掉

原因是,滾動時,列表裡面的view重繪時,用的依舊是系統默認的透明色,顏色值為#FF191919,
要改變這種情況,只需要調用listView的setCacheColorHint(0),顏色值設置為0
或者xml文件中listView的屬性 Android:cacheColorHint="#00000000"即可。

ListView設置item高度無效

在item的layout文件中,用android:layout_height設置item的高度。運行,高度設置無效。
解決辦法:給item設定minHeight,即可。

設置虛線分割線

 

  android:divider="@drawable/dash_line"
dash_line為drawable


    
    


 

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved