踢开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) 的调用。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,198评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,663评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,985评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,673评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,994评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,399评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,717评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,407评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,112评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,371评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,891评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,255评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,881评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,010评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,764评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,412评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,299评论 2 260

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,544评论 25 707
  • afinalAfinal是一个android的ioc,orm框架 https://github.com/yangf...
    passiontim阅读 15,072评论 2 44
  • 一直觉得绣花是旧社会三从四德的女子干的事,跟我没半毛钱关系,从没想过有一天我也会接触到。为了办惠安...
    东亭阅读 404评论 6 4
  • 一连几周的阴雨 淮河小城满身青苔 湿漉漉的街 湿漉漉的整个人都在发呆 周末分割线 天气预报有雨 回到老家 一如既往...
    橙朵拉阅读 257评论 0 1
  • 如果你是自己创业,或者在创业公司工作,那应该非常了解Gartner提出的技术成熟度曲线(以下简称Hype Cycl...
    忙果优服阅读 232评论 0 0