Android View 之 addView與listView

前言

關(guān)于view的使用以及列表式加載view的簡單介紹隐圾。

  • View與ViewGroup:

View作為一個(gè)視圖類的基類牢酵,給人的直觀感受就是一個(gè)視圖,即一個(gè)控件或者一個(gè)Layout形成的范圍區(qū)域默蚌。View的層次結(jié)構(gòu)類似于一個(gè)完整的數(shù)據(jù)結(jié)構(gòu)體系母剥,從根部至ViewGroup一直向下遍歷,在其遞歸的過程中茵烈,會發(fā)生對View的測量百匆、布局和繪制的過程,即View的三個(gè)基本方法onMeasure( )呜投,onLayout( )加匈,onDraw( )存璃,這在自定義View中會經(jīng)常用到。而其中又包含了多種回調(diào)監(jiān)聽方法雕拼,如點(diǎn)擊纵东,觸摸,滑動啥寇,位置偎球,尺寸等狀態(tài)變化。而其中的一點(diǎn)細(xì)節(jié)辑甜,比如繪制的過程衰絮, 重繪時(shí)的invalidate()方法,以及ViewGroup與其子View的關(guān)系等在這不做詳細(xì)說明栈戳。
</br>

  • 布局填充器LayoutInflater的使用說明:

LayoutInflater可以獲得布局文件的對象并且實(shí)例化岂傲,常見于一些Adapter、自定義View中子檀,其實(shí)現(xiàn)方法有別于簡單的findViewById( )镊掖,是將初始化具體子View的實(shí)現(xiàn)變?yōu)榱藢?shí)例化具體子布局的實(shí)現(xiàn)。通常見于一些沒有被載入的界面褂痰,即不在Activity中且需要調(diào)用布局文件的情景亩进,因?yàn)锳ctivity的setContentView( )方法通過getWindow( )的實(shí)現(xiàn)已經(jīng)完成了對Layout的展示:
setContentView(R.layout.activity_main);
  針對于LayoutInflater的實(shí)現(xiàn)方法,其本質(zhì)都是一致的缩歪,即通過resoure id和ViewGroup使用getSystemService( )得到該布局的對象归薛,以下大致介紹其三種實(shí)現(xiàn)方法:
LayoutInflater inflater = getLayoutInflater();
LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
LayoutInflater inflater = LayoutInflater.from(context);

  • ListView的基本使用

ListView是包含多個(gè)子Item的列表式View,通過與Adapter關(guān)聯(lián)的適配器模式從而實(shí)現(xiàn)了經(jīng)典的MVC模式匪蝙,而其通過在Model中自定義實(shí)現(xiàn)方法來操作數(shù)據(jù)源主籍,可關(guān)聯(lián)于基本的View-Model的概念。
  針對于List的數(shù)據(jù)結(jié)構(gòu)逛球,ListView提供了幾種常用的基本適配器千元,如ArrayAdapter,SimpleAdapter和BaseAdapter颤绕,其中的一些重寫方法getCount( )幸海,getItem( ),getItemId( )在使用時(shí)很方便去調(diào)用奥务,而針對于getView( )物独,在對ListView優(yōu)化時(shí),通過視圖的緩存機(jī)制氯葬,為了避免多次的findViewById而使用了ViewHolder挡篓,具體細(xì)節(jié)在這里也不做詳細(xì)說明。
</br>

自定義View & addView

關(guān)于自定義View的簡單實(shí)現(xiàn)帚称,以及在實(shí)例中對該View調(diào)用時(shí)的一些細(xì)節(jié)處理瞻凤。

  • 簡單定義的Item

有時(shí)可能由于需求的不同憨攒,如果單純的使用ListView實(shí)現(xiàn)多種樣式的Item時(shí)需要使用ViewHolderType處理,而ListView會對高度有一個(gè)默認(rèn)設(shè)置阀参,即在內(nèi)部實(shí)現(xiàn)滾動效果肝集,容易與外部布局的ScrollView的滑動事件沖突,在根據(jù)實(shí)際情況可自行選擇蛛壳。在這里介紹一下通過addView方法實(shí)現(xiàn)的類似ListView的列表式加載杏瞻,且適用于處理ScrollView的滑動事件。請看代碼:
public class CampaignListItem extends LinearLayout {

        private ImageView mIconIV;    
        private ImageView mAttendIV;    
        private ImageView mNewIV;    
        private TextView mTitleTV;    

        public CampaignListItem(Context context, AttributeSet attrs) {        
              super(context, attrs);
              LayoutInflater.from(context).inflate(R.layout.include_campaignlist, this);        
              mIconIV = (ImageView) findViewById(R.id.iv_attendcampaignitemicon);        
              mAttendIV = (ImageView) findViewById(R.id.iv_attendcampaignitemstate);        
              mNewIV = (ImageView) findViewById(R.id.iv_attendcampaignitemnew);        
              mTitleTV = (TextView) findViewById(R.id.tv_attendcampaignitemtitle);    
        }    

        public void setCampaignIcon(int resource) {        
              mIconIV.setImageResource(resource);        
              mIconIV.setVisibility(View.VISIBLE);    
        }    

        public void setCampaignAttend(int resource) {        
              mAttendIV.setImageResource(resource);        
              mAttendIV.setVisibility(View.VISIBLE);    
        }    

        public void setCampaignNew(int resource) {        
              mNewIV.setImageResource(resource);        
              mNewIV.setVisibility(View.VISIBLE);    
        }    

        public void setCampaignTitle(String title) {        
              mTitleTV.setText(title);    
        }
  }

CampaignListItem這個(gè)簡單的View間接地繼承了View衙荐,其中沒有對其繪制做處理捞挥,如有感興趣的同學(xué)可以重寫其onMeasure( ),onLayout( )忧吟,onDraw( )進(jìn)行重繪砌函。邏輯簡單的話實(shí)現(xiàn)起來并不復(fù)雜,主要涉及到一些子View和父Layout的Height/Weight的計(jì)算和關(guān)系處理溜族,以及一些回調(diào)方法的使用讹俊。

  • 項(xiàng)目中的實(shí)踐

通過addView實(shí)現(xiàn)統(tǒng)一化處理的效果,類似于一個(gè)自定義的include.xml煌抒,和ListView仍劈,GridView等都有相似之處,當(dāng)然也可以對每次add的View進(jìn)行差異化處理寡壮,完全可以手動實(shí)現(xiàn)贩疙。
  在實(shí)際項(xiàng)目中,對View的處理除了初始化時(shí)况既,一般都是由網(wǎng)絡(luò)請求后返回的data對其設(shè)置屬性这溅。舉個(gè)例子,onResponse( )在成功時(shí)返回一個(gè)list棒仍,這時(shí)我們?nèi)粝雽⑦@個(gè)list的每個(gè)元素依次添加到該自定義View中【在list.size( )較小時(shí)較為適用】芍躏,可以先對list進(jìn)行遍歷,然后依次將CampaignListItem進(jìn)行實(shí)例化降狠,再通過自定義View中定義的這些方法對list的子元素屬性進(jìn)行設(shè)置,從而達(dá)到了類似于ListView的getView中通過ViewHolder對象對Item進(jìn)行設(shè)置的效果庇楞。
  思路大致是這樣榜配,下面從代碼層面深入解析一下:
  1、調(diào)用該自定義View的構(gòu)造方法CampaignListItem(Context context, AttributeSet attrs)吕晌,傳入Context對象和定義的屬性變量(attrs在此未作處理)蛋褥,new CampaignListItem( )。
CampaignListItem campaignListItem = new CampaignListItem(getContext(), null);
  2睛驳、通過其對象campaignListItem調(diào)用自定義的方法對其設(shè)置烙心。
campaignListItem.setCampaignIcon(R.drawable.ic_launcher);
campaignListItem.setCampaignAttend(R.drawable.ic_launcher);
campaignListItem.setCampaignNew(R.drawable.ic_launcher);
campaignListItem.setCampaignTitle(R.string.text);
  3膜廊、也可以對其設(shè)置監(jiān)聽,如對點(diǎn)擊事件實(shí)現(xiàn)onClick( )的處理淫茵。
campaignListPlatformItem.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// TODO
}
});
  4爪瓜、這里可能會遇到一個(gè)問題,即在依次循環(huán)中匙瘪,campaignListItem會自動變成最后遍歷出的那個(gè)View铆铆,而此時(shí)如在onClick( )中處理campaignListItem,則會統(tǒng)一為對最后View的處理丹喻。針對這個(gè)問題薄货,解決方法有多種,如定義一個(gè)回調(diào)的Listener碍论,通過實(shí)現(xiàn)該接口做出處理等谅猾,這里介紹一種簡單的系統(tǒng)方法:setId( )/getId( ),setTag( )/getTag( )鳍悠。
  setId( )實(shí)現(xiàn)較為簡單税娜,參數(shù)設(shè)為一個(gè)int型的變量即可,如循環(huán)次數(shù) i贼涩。而setTag( )的實(shí)現(xiàn)需在資源文件中進(jìn)行預(yù)定義巧涧,res/values/ids.xml中:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item type="id" name="campaignlist_type"></item>
<item type="id" name="campaignlist_state"></item>
</resources>
  這樣便可在代碼中進(jìn)行設(shè)置,并可在該匿名內(nèi)部類中調(diào)用遥倦,從而實(shí)現(xiàn)對每個(gè)Item獨(dú)立setTag( )谤绳,依次實(shí)現(xiàn)監(jiān)聽:
private String mType;
private String mState;

  // TODO......      

  campaignListItem.setId(i);
  campaignListItem.setTag(R.id.campaignlist_type, list.getType());
  campaignListItem.setTag(R.id.campaignlist_state, list.getState());
  campaignListPlatformItem.setOnClickListener(new View.OnClickListener() {   
        @Override   
        public void onClick(View v) {      
              mType = campaignListPlatformItem.getTag(R.id.campaignlist_type).toString();
              mState = campaignListPlatformItem.getTag(R.id.campaignlist_state).toString();
        }
  });

5、循環(huán)內(nèi)袒哥,父View(rootView)對循環(huán)遍歷的子Item進(jìn)行addView( )缩筛。
rootView.addView(campaignListPlatformItem);
  6、刷新問題的處理堡称,結(jié)合removeView( )的實(shí)現(xiàn)瞎抛。因?yàn)橥ㄟ^addView( )添加的子Item不像Adapter那樣可以動態(tài)刷新:notifyDataSetChanged( );當(dāng)然,你可以自己寫出一個(gè)動態(tài)刷新的方法却紧,或者更簡單一點(diǎn)桐臊,刷新時(shí)請求完網(wǎng)絡(luò),判斷出哪條數(shù)據(jù)需要變化晓殊,則對其手動重設(shè)即可断凶,這時(shí)便可調(diào)用getId( )來實(shí)現(xiàn),需要注意的是將某條Item移除后巫俺,還得重新將這條Item添加到同一位置认烁。
rootView.removeViewAt(campaignListItem.getId());
// TODO
rootView.addView(campaignListItem, campaignListItem.getId());
  當(dāng)然,如果在數(shù)據(jù)量不大的情況下,即不影響性能時(shí)却嗡,可考慮全部重新加載的方法舶沛,避免了錯(cuò)位等問題:即先removeAll,然后再重新addAll窗价。
private int mSize;

  // TODO......

  private void removeCampaign() {
        this.mSize = rootView.getChildCount();      
        for( int i = 0; i < mSize; i++) {
              // 每次remove()后如庭,下一條Item都會前移一位,所以每次都removeViewAt(0)即可舌镶。         
              this.rootView.removeViewAt(0);     
        }   
  }

  // 重新addView();

</br>

關(guān)于addView( )和removeView( )的實(shí)現(xiàn)原理

這里只從源碼的角度下找出幾個(gè)重點(diǎn)柱彻,有興趣的同學(xué)可以自行深入研究下,畢竟關(guān)于View這塊的水還是很深的餐胀。

  • addView( )
    /**
     * @param child the child view to add
     */
    public void addView(View child) {    
          addView(child, -1);
    }
    

由注釋中可大致理解這里只是將這個(gè)View進(jìn)行add哟楷,還得繼續(xù)Ctrl + Click:
/**
* @param child the child view to add
* @param index the position at which to add the child
/
public void addView(View child, int index) {
LayoutParams params = child.getLayoutParams();
if (params == null) {
params = generateDefaultLayoutParams();
if (params == null) {
throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null");
}
}
addView(child, index, params);
}
  由上可見,這里是將待添加的View調(diào)用getLayoutParams( )方法初始化一個(gè)LayoutParams對象否灾,從而可在添加進(jìn)行父布局時(shí)對布局的參數(shù)(比例卖擅、位置、大小等)進(jìn)行控制墨技,如通過index進(jìn)行添加時(shí)的定位惩阶。繼續(xù)Ctrl + Click:
/
*
* @param child the child view to add
* @param index the position at which to add the child
* @param params the layout parameters to set on the child
*/
public void addView(View child, int index, LayoutParams params) {
if (DBG) {
System.out.println(this + " addView");
}
// addViewInner() will call child.requestLayout() when setting the new LayoutParams
// therefore, we call requestLayout() on ourselves before, so that the child's request
// will be blocked at our level
requestLayout();
invalidate(true);
addViewInner(child, index, params, false);
}
  通過扒了下源代碼和查找了網(wǎng)上的一些資料,這里簡單說一下invalidate( )方法的實(shí)現(xiàn)原理扣汪。invalidate( )的主要作用是請求View樹進(jìn)行重繪断楷。其原理比較復(fù)雜,大致是通過一個(gè)循環(huán)將子視圖的dirty區(qū)域與父視圖做運(yùn)算來確定最終要重繪的dirty區(qū)域崭别,直至ViewRoot的parent為null時(shí)為止冬筒,并保存在ViewRoot中的mDirty變量中,最后調(diào)用scheduleTraversals發(fā)起一個(gè)異步的重繪請求消息茅主,從而最終調(diào)用performTraversals( )執(zhí)行重繪舞痰。(附此段落參考鏈接
  由于下面的源碼較多,這里就不一一復(fù)述诀姚,值得一提的是requestLayout( )和invalidate(true)都是對View進(jìn)行一個(gè)重繪的方法响牛,invalidate( )是View本身調(diào)用迫使view重畫,類似一個(gè)強(qiáng)制的調(diào)用赫段,而requestLayout則是當(dāng)view確定自身已經(jīng)不再適合現(xiàn)有的區(qū)域時(shí)呀打,該view本身調(diào)用這個(gè)方法要求parent view重新調(diào)用他的onMeasure( ),onLayout( )來對重新設(shè)置自己位置糯笙,是一個(gè)自適應(yīng)的調(diào)用方法贬丛,如LayoutParams已經(jīng)變化但還沒有更新View時(shí)調(diào)用。

  • removeView( )

了解完addView( )的原理后再來看removeView( )就簡單了炬丸,其實(shí)現(xiàn)方法大體類似,就不在此詳述了。查看源碼可看到其幾種實(shí)現(xiàn)方法:
/**
* do not invoke this method from
* draw(android.graphics.Canvas)
* onDraw(android.graphics.Canvas)
* dispatchDraw(android.graphics.Canvas) or any related method
*/
public void removeView(View view) {
removeViewInternal(view);
requestLayout();
invalidate(true);
}

  /** 
   * Removes a view during layout. 
   * This is useful if in your onLayout() method, you need to remove more views
   * @param view the view to remove from the group
   */
  public void removeViewInLayout(View view) {    
        removeViewInternal(view);
  }

  /** 
   * Removes a range of views during layout. 
   * This is useful if in your onLayout() method, you need to remove more views
   * @param start the index of the first view to remove from the group 
   * @param count the number of views to remove from the group 
   */
  public void removeViewsInLayout(int start, int count) {    
        removeViewsInternal(start, count);
  }

  /** 
   * Removes the view at the specified position in the group
   * @param index the position in the group of the view to remove 
   */
  public void removeViewAt(int index) {    
        removeViewInternal(index, getChildAt(index));    
        requestLayout();    
        invalidate(true);
  }

  /** 
   * Removes the specified range of views from the group
   * @param start the first position in the group of the range of views to remove 
   * @param count the number of views to remove 
   */
  public void removeViews(int start, int count) {    
        removeViewsInternal(start, count);    
        requestLayout();    
        invalidate(true);
  }

  private void removeViewInternal(View view) {    
        final int index = indexOfChild(view);    
        if (index >= 0) {        
              removeViewInternal(index, view);    
        }
  }

</br>

尾聲

再來說兩句......

  • 注意事項(xiàng)

1稠炬、關(guān)于調(diào)用getChildCount( )這里需結(jié)合實(shí)際情況對父View是否為空或者是否不含子View進(jìn)行判斷焕阿,在ListView中調(diào)用這個(gè)方法經(jīng)常會出現(xiàn)為0的情況,可以稍留心一下首启。
2暮屡、關(guān)于View的繪制,有一些回調(diào)的監(jiān)聽方法毅桃,可以反饋出繪制是否完成褒纲,繪制的位置等,結(jié)合第幾條Item的id钥飞,params等可以自己實(shí)現(xiàn)一個(gè)動態(tài)的更新方法莺掠。
3、關(guān)于ListView優(yōu)化時(shí)使用的ViewHolder读宙,也是通過convertView的setTag( )/getTag( )進(jìn)行處理彻秆,可以參考結(jié)合ListView較為成熟的處理方法。
4结闸、關(guān)于invalidate( )的底層代碼有興趣的可以深入研究唇兑。但是在實(shí)際繪制時(shí)經(jīng)常會出現(xiàn)一些View沒有對應(yīng)上的問題,即在遍歷時(shí)的addView( )沒有處理好桦锄。
5扎附、關(guān)于自定義View的實(shí)現(xiàn),不僅限于本文中提到的對原Item進(jìn)行屬性設(shè)置的方法结耀,可以多元化的自定義View留夜,喔,你的能量超乎你想像饼记。

  • Github地址:

https://github.com/Ivorfason

  • 雜談一下:

后續(xù)會不定期更新一點(diǎn)實(shí)踐中的心得香伴,可能敘述的不夠完善,希望大家可以一起交流~~~
</br>

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末具则,一起剝皮案震驚了整個(gè)濱河市即纲,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌博肋,老刑警劉巖低斋,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異匪凡,居然都是意外死亡膊畴,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進(jìn)店門病游,熙熙樓的掌柜王于貴愁眉苦臉地迎上來唇跨,“玉大人稠通,你說我怎么就攤上這事÷虿” “怎么了改橘?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵,是天一觀的道長玉控。 經(jīng)常有香客問我飞主,道長,這世上最難降的妖魔是什么高诺? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任碌识,我火速辦了婚禮,結(jié)果婚禮上虱而,老公的妹妹穿的比我還像新娘筏餐。我一直安慰自己,他們只是感情好薛窥,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布胖烛。 她就那樣靜靜地躺著,像睡著了一般诅迷。 火紅的嫁衣襯著肌膚如雪佩番。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天罢杉,我揣著相機(jī)與錄音趟畏,去河邊找鬼。 笑死滩租,一個(gè)胖子當(dāng)著我的面吹牛赋秀,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播律想,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼猎莲,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了技即?” 一聲冷哼從身側(cè)響起著洼,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎而叼,沒想到半個(gè)月后身笤,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡葵陵,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年液荸,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片脱篙。...
    茶點(diǎn)故事閱讀 38,605評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡娇钱,死狀恐怖伤柄,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情文搂,我是刑警寧澤响迂,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站细疚,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏川梅。R本人自食惡果不足惜疯兼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望贫途。 院中可真熱鬧吧彪,春花似錦、人聲如沸丢早。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽怨酝。三九已至傀缩,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間农猬,已是汗流浹背赡艰。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留斤葱,地道東北人慷垮。 一個(gè)月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像揍堕,于是被迫代替她去往敵國和親料身。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評論 2 348

推薦閱讀更多精彩內(nèi)容