踢開Android 開發中的絆腳石

在開發過程中,許多并算不上高級技能甚至連基礎知識都不算的東西經常被忽略,但這些東西還經常是開發過程中的絆腳石,很長時間都解決不了,一旦找到了解決辦法,就茅塞頓開了“原來是這樣啊,這不是小菜一碟嗎?下次我注意就是了”。但是時間長了真的發現“好記性不如爛筆頭”,當再次遇到同樣的問題,發現還是一臉懵逼,但可以肯定之前遇到過這個問題。為了避免重走冤枉路,所以將它們記錄下來。

你還在為開發中頻繁切換環境打包而煩惱嗎?快來試試 Environment Switcher 吧!使用它可以在app運行時一鍵切換環境,而且還支持其他貼心小功能,有了它媽媽再也不用擔心頻繁環境切換了。https://github.com/CodeXiaoMai/EnvironmentSwitcher

  1. 虛線
  2. ScrollView 嵌套 ListView 或 RecyclerView

1. 虛線

在 drawable 目錄下新建 dash_line.xml

<?xml version="1.0" encoding="utf-8"?>  
<shape xmlns:android="http://schemas.android.com/apk/res/android"  
    android:shape="line">  
    <stroke  
        android:width="3px"  
        android:color="#000000"  
        android:dashWidth="20px"  
        android:dashGap="5px" />  
</shape>  
  • width:線條的粗細
  • dashWidth:破折線的長度
  • dashGap:破折線之間的空隙的長度,當dashGap = 0時,就是實線。

注意

  • 如果在<stroke>標簽中設置了android:width,則在<View>標簽中android:layout_height的值必須大于android:width的值,否則虛線不會顯示。如果不設置,默認android:width = 0。

  • 默認情況下,在 Android 4.0 以上設備虛線會變成實線,有下面兩種方法解決:

    1. 代碼中可以添加:

      line.setLayerType(View.LAYER_TYPE_SOFTWARE, null);  
      
    2. XML中可以添加:

      android:layerType="software"  
      

      下面是完整示例:

      <View
         android:layout_width="match_parent"
         android:layout_height="4px"
         android:layerType="software"
         android:background="@drawable/dash_line"/>
      

2. ScrollView 嵌套 ListView 或 RecyclerView

如果 ScrollView 中除了 ListView 或 RecyclerView 還包含其他如 RelativeLayout、LinearLayout 之類的布局,當 ListView 或 RecyclerView 不在頂部時,ScrollView 會自動滾動到它們所在的位置。解決辦法:

recyclerView.setFocusable(false);
recyclerView.setFocusableInTouchMode(false);

此外,由于 ScrollView 和 ListView (RecyclerView)會產生滑動沖突,當觸摸到 ListView(RecyclerView)的布局時,會感覺很不流暢,解決辦法:

recyclerView.setNestedScrollingEnabled(false);

ListView的解決辦法同上。

3. 換行轉譯符

一個TextView中的文本內容如果使用\r\n換行,在部分手機上會出現空白的一行,方法是使用字符串替換方法,將\r去掉。

4. Activity 與 Fragment 創建順序

  1. 第一次創建時,先創建Activity 再 創建 Fragment。
  2. 當開啟不保留活動后,頁面恢復創建時,先創建 Fragment ,再創建 Activity,這時在 Activity 中,使用 (Support)FragmentManager.findFragmentById() 或 findFragmentByTag() 就可以獲取到恢復的 Fragment。

5. MediaPlayer 的一些小技巧

  1. MediaPlayer 播放網絡音頻時, 邊下邊播過程中,若緩存未完成且已緩存的部分已經播放完時,不會觸發onError() 回調,而是會觸發onCompletion()回調。這時可以通過 mediaPlayer.getCurrentPosition() 和 mediaPlayer.getDuration() 進行比較,如果 currentPosition 小于 duration 說明播放未完成,可以認為是播放過程中出現異常。
    經測試發現,即使正常情況下音頻全部緩沖完后,播放完畢時,mediaPlayer.getCurrentPosition() 的值也很可能小于 mediaPlayer.getDuration() 的值,所以上面的方法容易造成誤判,改用新的方法。

調用 mediaPlayer.setOnBufferingUpdateListener(),這是緩沖進度的回調,percent 的值為 0 - 100,因此,可以根據 percent 的值判斷是否緩沖完畢,當回調觸發 onCompletion 時,如果 percent 的值是 100 認為正常播放完畢,否則認為沒有緩沖完畢,播放錯誤。

// 播放出錯的位置,記錄下來,下次繼續從這個點開始播放
private var errorPosition = 0
private var hasBuffered = false

fun setUrl(url: String) {
    if (mediaPlayer == null) {
        mediaPlayer = MediaPlayer()
    }
    mediaPlayer?.also {
        it.reset()
        try {
            it.setDataSource(url)
        } catch (e: Exception) {
            e.printStackTrace()
        }
        it.setOnErrorListener { mp, what, extra ->
            if (mp.currentPosition > 0) {
                errorPosition = mp.currentPosition
            }
            callback?.onError()
            stop()
            setUrl(url)
            return@setOnErrorListener true
        }
        it.setOnCompletionListener {
            if (!hasBuffered) {
                errorPosition = it.currentPosition
                callback?.onError()
                setUrl(url)
                return@setOnCompletionListener
            }
            it.seekTo(0)
            val message = handler.obtainMessage(what, it.duration, it.duration)
            handler.sendMessage(message)
            callback?.onFinish()
        }
        it.setOnBufferingUpdateListener { mp, percent ->
            hasBuffered = percent >= 100
        }
        it.setOnPreparedListener {
            if (errorPosition > 0) {
                it.seekTo(errorPosition)
                errorPosition = 0
            }
            callback?.onPrepared(it.duration)
        }
        it.prepareAsync()
    }
}

fun stop() {
    handler.removeCallbacks(runnable)
    handler.removeMessages(what)
    mediaPlayer?.stop()
    mediaPlayer?.release()
    mediaPlayer = null
}

6. Retrofit Post 亂碼問題

在請求頭中指定編碼格式:

@Headers("Content-Type:application/x-www-form-urlencoded; charset=utf-8")
@POST("/xxx/xxx")
@FormUrlEncoded
Observable<Result> send(@Field("id") int id, @Field("content") String content);

7. 自定義 Dialog 頂部有條藍色的線

重寫 show() 方法

override fun show() {
        super.show()
        this.setCanceledOnTouchOutside(false)
        val dividerId = context.resources.getIdentifier("android:id/titleDivider", null, null)
        val divider = findViewById<View>(dividerId)
        // 并不是所有手機都會有這條藍線,所以要空安全處理
        divider?.setBackgroundColor(Color.TRANSPARENT)
        val window = window
        window.setGravity(Gravity.BOTTOM)
        window.setBackgroundDrawableResource(R.color.transparent)
        window.setWindowAnimations(R.style.dialog_anim_style)
        val params = window.attributes
        params.width = ViewGroup.LayoutParams.MATCH_PARENT
        window.attributes = params
}

8. 自定義圓形ProgressBar

  1. 在 drawable 目錄下新建 progress.xml 文件
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/loading"
    android:fromDegrees="0"
    android:pivotX="50%"
    android:pivotY="50%"
    android:toDegrees="360">
</rotate>
  1. 在 ProgressBar 中使用
 <ProgressBar
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:indeterminateDrawable="@drawable/progress" />

9. SurfaceView 重疊的問題

SurfaceView 是 View 的子類,它內嵌了一個專門用于繪制的 Surface,你可以控制這個 Surface 的格式和尺寸,SurfaceView 控制這個Surface 的繪制位置。surface 是縱深排序(Z-ordered)的,說明它總在自己所在窗口的后面。SurfaceView 提供了一個可見區域,只有在這個可見區域內的 surface 內容才可見。surface 的排版顯示受到視圖層級關系的影響,它的兄弟視圖結點會在頂端顯示。這意味者 surface 的內容會被它的兄弟視圖遮擋,這一特性可以用來放置遮蓋物(overlays)(例如,文本和按鈕等控件)。注意,如果 surface 上面有透明控件,那么每次 surface變化都會引起框架重新計算它和頂層控件的透明效果,這會影響性能。

SurfaceView 默認使用雙緩沖技術的,它支持在子線程中繪制圖像,這樣就不會阻塞主線程了,所以它更適合于游戲的開發。

兩個 SurfaceView 重疊會發生上層的 SurfaceView 變成透明的,看不見。解決方法:

對上層的 SurfaceView 使用 setZOrderMediaOverlay(boolean isMediaOverlay)setZOrderOnTop(boolean onTop) 方法。

  • setZOrderOnTop

    控制 SurfaceView 的 surface 是否放置在其 window 的頂部。一身情況下它放在 window 的下面,以允許它(大部分)看起來與層次結構中的其他視圖融合。通過設置它,可以將它放在 window 上方。這意味著此SurfaceView 所在 window 的所有內容都不會在其 Surface 上顯示。請注意,必須在包含 SurfaceView 的 window 附加到 WindowManager 之前設置此項。調用它會覆蓋以前對setZOrderMediaOverlay(boolean) 的調用。

  • setZOrderMediaOverlay

    控制 SurfaceView 的 Surface 是否位于同一 window 中另一個普通的 SurfaceView 的上面(但仍然在 window 下面)。這通常用于將疊加層放置在底層是播放視頻的 SurfaceView 的上面。

    和 setZOrderOnTop(boolean) 方法一樣,該方法必須在包含 SurfaceView 的 window 附加到 WindowManager 之前設置此項。調用它會覆蓋以前對setZOrderOnTop(boolean) 的調用。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,333評論 25 708
  • afinalAfinal是一個android的ioc,orm框架 https://github.com/yangf...
    passiontim閱讀 15,556評論 2 45
  • 一直覺得繡花是舊社會三從四德的女子干的事,跟我沒半毛錢關系,從沒想過有一天我也會接觸到。為了辦惠安...
    東亭閱讀 439評論 6 4
  • 一連幾周的陰雨 淮河小城滿身青苔 濕漉漉的街 濕漉漉的整個人都在發呆 周末分割線 天氣預報有雨 回到老家 一如既往...
    橙朵拉閱讀 301評論 0 1
  • 如果你是自己創業,或者在創業公司工作,那應該非常了解Gartner提出的技術成熟度曲線(以下簡稱Hype Cycl...
    忙果優服閱讀 274評論 0 0