【轉】Android開發的那些坑和小技巧

本文轉自:?LeoLiang的Blog

ps: 排版還有問題, 將就下, 實在看不慣就去原文瞅瞅吧~ ? 其實有非常多的坑和小技巧可供分享, 在此轉載, 同時未來可能會結合自身開發中的坑, 加上之前有心人總結的帖子,一并在此更新一下, 鏈接未來添加, 今天事情還不少, 就暫且占個蘿卜坑先 ~

1、android:clipToPadding


意思是控件的繪制區域是否在padding里面。默認為true。如果你設置了此屬性值為false,就能實現一個在布局上事半功陪的效果。先看一個效果圖。


上圖中的ListView頂部默認有一個間距,向上滑動后,間距消失,如下圖所示。

如果使用margin或padding,都不能實現這個效果。加一個headerView又顯得大材小用,而且過于麻煩。此處的clipToPadding配合paddingTop效果就剛剛好。

2、match_parent和wrap_content


按理說這兩個屬性一目了然,一個是填充布局空間適應父控件,一個是適應自身內容大小。但如果在列表如ListView中,用錯了問題就大了。ListView中的getView方法需要計算列表條目,那就必然需要確定ListView的高度,onMesure才能做測量。如果指定了wrap_content,就等于告訴系統,如果我有一萬個條目,你都幫我計算顯示出來,然后系統按照你的要求就new了一萬個對象出來。那你不悲劇了?先看一個圖。

假設現在ListView有8條數據,match_parent需要new出7個對象,而wrap_content則需要8個。這里涉及到View的重用,就不多探討了。所以這兩個屬性的設置將決定getView的調用次數。

由此再延伸出另外一個問題:getView被多次調用

什么叫多次調用?比如position=0它可能調用了幾次。看似很詭異吧。GridView和ListView都有可能出現,說不定這個禍首就是wrap_content。說到底是View的布局出現了問題。如果嵌套的View過于復雜,解決方案可以是通過代碼測量列表所需要的高度,或者在getView中使用一個小技巧:parent.getChildCount == position

? ? ? ? @Override

? ? ? ? public ?View getView(intposition, View convertView, ViewGroup parent) {

? ? ? ? ? ? ? ?if(parent.getChildCount() ==position) {

? ? ? ? ? ? ? ? //does things here

? ? ? ? ? ? ? ?}

? ? ? ? ? ? ? ? returnconvertView;

? ? ? ? }

3、IllegalArgumentException: pointerIndex out of range


出現這個Bug的場景還是很無語的。一開始我用ViewPager + PhotoView(一個開源控件)顯示圖片,在多點觸控放大縮小時就出現了這個問題。一開始我懷疑是PhotoView的bug,找了半天無果。要命的是不知如何try,老是crash。后來才知道是android遺留下來的bug,源碼里沒對pointer index做檢查。改源碼重新編譯不太可能吧。明知有exception,又不能從根本上解決,如果不讓它crash,那就只能try-catch了。解決辦法是:自定義一個ViewPager并繼承ViewPager。請看以下代碼:

/*** 自定義封裝android.support.v4.view.ViewPager,重寫onInterceptTouchEvent事件,捕獲系統級別異常*/

? ? ? ? ?public ?class ?CustomViewPager ?extends ?ViewPager {

? ? ? ? ? ? ? ? ? public ?CustomViewPager(Context context) {this(context,null);

? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? public ?CustomViewPager(Context context, AttributeSet attrs) {super(context, attrs);

? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ?@Override

? ? ? ? ? ? ? ? ?public ?boolean ?onInterceptTouchEvent(MotionEvent ev) {try{returnsuper.onInterceptTouchEvent(ev);

? ? ? ? ? ? ? ? ? }catch(IllegalArgumentException e) {

? ? ? ? ? ? ? ? ? ? ? LogUtil.e(e);

? ? ? ? ? ? ? ? ? }catch(ArrayIndexOutOfBoundsException e) {

? ? ? ? ? ? ? ? ? ? ?LogUtil.e(e);

? ? ? ? ? ? ? ? ? }return ?false;

? ? ? ? ? ? ? ? }

? ? ? ? ?}

把用到ViewPager的布局文件,替換成CustomViewPager就OK了。

4、ListView中item點擊事件無響應


listView的Item點擊事件突然無響應,問題一般是在listView中加入了button、checkbox等控件后出現的。這個問題是聚焦沖突造成的。在android里面,點擊屏幕之后,點擊事件會根據你的布局來進行分配的,當你的listView里面增加了button之后,點擊事件第一優先分配給你listView里面的button。所以你的點擊Item就失效了,這個時候你就要根據你的需求,是給你的item的最外層layout設置點擊事件,還是給你的某個布局元素添加點擊事件了。

解決辦法:在ListView的根控件中設置(若根控件是LinearLayout, 則在LinearLayout中加入以下屬性設置)descendantFocusability屬性。

android:descendantFocusability="blocksDescendants"

官方文檔也是這樣說明。

5、getSupportFragmentManager()和getChildFragmentManager()


有一個需求,Fragment需要嵌套3個Fragment。基本上可以想到用ViewPager實現。開始代碼是這樣寫的:

mViewPager.setAdapter(new ?CustomizeFragmentPagerAdapter(getActivity().getSupportFragmentManager(), subFragmentList));

導致的問題是嵌套的Fragment有時會莫名其妙不顯示。開始根本不知道問題出現在哪,當你不知道問題的原因時,去解決這個問題顯然比較麻煩。經過一次又一次的尋尋覓覓,終于在stackoverflow上看到了同樣的提問。說是用getChildFragmentManager()就可以了。真是這么神奇!

mViewPager.setAdapter(new ?CustomizeFragmentPagerAdapter(getChildFragmentManager, subFragmentList));

讓我們看一下這兩個有什么區別。首先是getSupportFragmentManager(或者getFragmentManager)的說明:

Return the FragmentManager for interacting with fragments associated with this fragment's activity.

然后是getChildFragmentManager:

Return aprivate FragmentManager for placing and managing Fragments inside of this Fragment.

Basically, the difference is that Fragment's now have their own internal FragmentManager that can handle Fragments. The child FragmentManager is the one that handles Fragments contained within only the Fragment that it was added to. The other FragmentManager is contained within the entire Activity.

已經說得比較明白了。

6、ScrollView嵌套ListView


這樣的設計是不是很奇怪?兩個同樣會滾動的View居然放到了一起,而且還是嵌套的關系。曾經有一個這樣的需求:界面一共有4個區域部分,分別是公司基本信息(logo、名稱、法人、地址)、公司簡介、公司榮譽、公司口碑列表。每部分內容都需要根據內容自適應高度,不能寫死。鄙人首先想到的也是外部用一個ScrollView包圍起來。然后把這4部分分別用4個自定義控件封裝起來。基本信息和公司簡介比較簡單,榮譽需要用到RecyclerView和TextView的組合,RecyclerView(當然,用GridView也可以,3列多行的顯示)存放榮譽圖片,TextView顯示榮譽名稱。最后一部分口碑列表當然是ListView了。這時候,問題就出來了。需要解決ListView放到ScrollView中的滑動問題和RecyclerView的顯示問題(如果RecyclerView的高度沒法計算,你是看不到內容的)。

當然,網上已經有類似的提問和解決方案了。

給一個網址:

四種方案解決ScrollView嵌套ListView問題

ListView的情況還比較好解決,優雅的做法無非寫一個類繼承ListView,然后重寫onMeasure方法。

? ? ? ? ?@Override

? ? ? ? ?protected ?void ?onMeasure(intwidthMeasureSpec,intheightMeasureSpec) {intexpandSpec = ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? MeasureSpec.AT_MOST);super.onMeasure(widthMeasureSpec, ? expandSpec);

? ? ? ? ?}

ListView可以重寫onMeasure解決,RecyclerView重寫這個方法是行不通的。

說到底其實計算高度嘛。有兩種方式,一種是動態計算RecycleView,然后設置setLayoutParams;另外一種跟ListView的解決方式類似,定義一個類繼承LinearLayoutManager或GridLayoutManager(注意:可不是繼承RecyclerView),重寫onMeasure方法(此方法比較麻煩,此處不表,下次寫一篇文章再作介紹)。

動態計算高度如下:

? ? ? ? ? ? ?int ?heightPx = DensityUtil.dip2px(getActivity(), (imageHeight + imageRowHeight) *lines);

? ? ? ? ? ? ?MarginLayoutParams mParams=newMarginLayoutParams(LayoutParams.MATCH_PARENT, heightPx);

? ? ? ? ? ? ?mParams.setMargins(0, 0, 0, 0);

? ? ? ? ? ? ?LinearLayout.LayoutParams lParams=newLinearLayout.LayoutParams(mParams);

? ? ? ? ? ? ?honorImageRecyclerView.setLayoutParams(lParams);

思路是這樣的:服務端返回榮譽圖片后,由于是3列顯示的方式,只需要計算需要顯示幾行,然后給定行間距和圖片的高度,再設置setLayoutParams就行了。

? ? ? ? ? ? ?int ?lines = (int) Math.ceil(totalImages / 3d);

至此,這個奇怪的需求得到了解決。

可是在滑動的時候,感覺出現卡頓的現象。聰明的你肯定想到是滑動沖突了。應該是ScrollView的滑動干擾到了ListView的滑動。怎么辦呢?能不能禁掉ScrollView的滑動?

百度一下,你肯定能搜索到答案的。先上代碼:

/***@authorLeo

*

*? ? ? ? Created in 2015-9-12

*? ? ? ? 攔截ScrollView滑動事件*/publicclassCustomScrollViewextendsScrollView {privateintdownY;privateinttouchSlop;publicCustomScrollView(Context context) {this(context,null);

}publicCustomScrollView(Context context, AttributeSet attrs) {this(context, attrs, 0);

}publicCustomScrollView(Context context, AttributeSet attrs,intdefStyleAttr) {super(context, attrs, defStyleAttr);

touchSlop=ViewConfiguration.get(context).getScaledTouchSlop();

}

@OverridepublicbooleanonInterceptTouchEvent(MotionEvent e) {intaction =e.getAction();switch(action) {caseMotionEvent.ACTION_DOWN:

downY= (int) e.getRawY();break;caseMotionEvent.ACTION_MOVE:intmoveY = (int) e.getRawY();if(Math.abs(moveY - downY) >touchSlop) {returntrue;

}

}returnsuper.onInterceptTouchEvent(e);

}

}

只要理解了getScaledTouchSlop()這個方法就好辦了。這個方法的注釋是:Distance in pixels a touch can wander before we think the user is scrolling。說這是一個距離,表示滑動的時候,手的移動要大于這個距離才開始移動控件,如果小于此距離就不觸發移動。

看似很完美了。

但是還有另外一個問題:我每次加載這個界面花的時間太長了,每次由其它界面啟動這個界面時,都要卡上1~2秒,而且因手機性能時間不等。并不是由于網絡請求,取數據由子線程做,跟UI線程毫無關系。這樣的體驗自己看了都很不爽。

幾天過去了,還是那樣。馬上要給老板演示了。這樣的體驗要被罵十次呀。

難道跟ScrollView的嵌套有關?

好吧,那我重構代碼。不用ScrollView了。直接用一個ListView,然后add一個headerView存放其它內容。因為控件封裝得還算好,沒改多少布局就OK了,一運行,流暢順滑,一切迎刃而解!

本來就是這么簡單的問題,為什么非得用ScrollView嵌套呢?

stackoverflow早就告訴你了,不要這樣嵌套!不要這樣嵌套!不要這樣嵌套!重要的事情說三遍。

ListView inside ScrollView is not scrolling on Android

當然,從android 5.0 Lollipop開始提供了一種新的API支持嵌入滑動,此時,讓像這樣的需求也能很好實現。

此處給一個網址,大家有興趣自行了解,此處不再討論。

Android NestedScrolling 實戰

7、EmojiconTextView的setText(null)


這是開源表情庫com.rockerhieu.emojicon中的TextView加強版。相信很多人用到過這個開源工具包。TextView用setText(null)完全沒問題。但EmojiconTextView setText(null)后就悲劇了,直接crash,顯示的是null pointer。開始我懷疑時這個view沒初始化,但并不是。那就調試一下唄。

@OverridepublicvoidsetText(CharSequence text, BufferType type) {

SpannableStringBuilder builder=newSpannableStringBuilder(text);

EmojiconHandler.addEmojis(getContext(), builder, mEmojiconSize);super.setText(builder, type);

}

EmojiconTextView中的setText看來沒什么問題。點SpannableStringBuilder進去看看,源碼原來是這樣的:

/*** Create a new SpannableStringBuilder containing a copy of the

* specified text, including its spans if any.*/publicSpannableStringBuilder(CharSequence text) {this(text, 0, text.length());

}

好吧。問題已經找到了,text.length(),不空指針才怪。

text = text ==null? "": text;

SpannableStringBuilder builder=newSpannableStringBuilder(text);

加一行判斷就行了。

8、cursor.close()


一般來說,database的開和關不太會忘記,但游標的使用可能并不會引起太多重視,尤其是游標的隨意使用。比如用ContentResolver結合Cursor查詢SD卡中圖片,很容易寫出以下的代碼:

Cursor cursor = contentResolver.query(uri,null, MediaStore.Images.Media.MIME_TYPE + "=? or "? ? ? ? ? ? ? ? ? ? ? ? + MediaStore.Images.Media.MIME_TYPE + "=?",newString[] { "image/jpeg", "image/png"},

MediaStore.Images.Media.DATE_MODIFIED);while(cursor.moveToNext()) {//TODO}

cursor都不做非空判斷,而且往往在關閉游標的時候不注意有可能異常拋出。

以前在項目中,經常出現由于游標沒及時關閉或關閉出異常沒處理好導致其它的問題產生,而且問題看起來非常的詭異,不好解決。后來,我把整個項目中有關游標的使用重構一遍,后來就再沒發生過類似的問題。

原則很簡單,所有Cursor的聲明為:

Cursor cursor =null;

且放在try-catch外面;需要用到cursor,先做非空判斷。然后在方法的最后用一個工具類處理游標的關閉。


/***@authorLeo

*

*? ? ? ? Created in 2015-9-15*/publicclassIOUtil {privateIOUtil() {

}publicstaticvoidcloseQuietly(Closeable closeable) {if(closeable !=null) {try{

closeable.close();

}catch(Throwable e) {

}

}

}publicstaticvoidcloseQuietly(Cursor cursor) {if(cursor !=null) {try{

cursor.close();

}catch(Throwable e) {

}

}

}

}

我想,這樣就沒必要在每個地方都try-catch-finally了。

先想到這么多,以后再補充。

參考:

android:clipToPadding和android:clipChildren

HowTo: ListView, Adapter, getView and different list items’ layouts in one ListView

android ListView 在初始化時多次調用getView()原因分析

java.lang.IllegalArgumentException: pointerIndex out of range Exception - dispatchTouchEvent

What is difference between getSupportFragmentManager() and getChildFragmentManager()?

標簽:Android,ScrollView嵌套ListView

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,182評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,489評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,290評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,776評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,510評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,866評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,860評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,036評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,585評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,331評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,536評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,058評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,754評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,154評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,469評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,273評論 3 399
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,505評論 2 379

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,733評論 25 708
  • afinalAfinal是一個android的ioc,orm框架 https://github.com/yangf...
    passiontim閱讀 15,484評論 2 45
  • ¥開啟¥ 【iAPP實現進入界面執行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開一個線程,因...
    小菜c閱讀 6,494評論 0 17
  • 愿你我都能清醒的活… 晨起,我硬生生地擺脫起床氣跑了幾公里。期間聽張德芬小時空修心課,一本邁克·辛格的《清醒的活》...
    夏沫er閱讀 234評論 0 1
  • 本月的主題是"沒有放棄"!必須做的事情堅持起來不難,可做可不做的事情堅持起來才難!本月(其實是后半...
    靜靜是我啦閱讀 220評論 0 0