Android 笔记

1. RecyclerView

  • recyclerview notifyChange 没有效果,只有滑动一下才能显示,参考链接:CymChad
    大致意思是使用了 Rx 的话确保是 observerOn 而不是 subscribeOn;并且最好不要贸然使用 Android 控件的 alpha 和 beta 版本。
  • requiresFadingEdge没有效果,原因是我设置了overScrollMode为 never
  • RecyclerView.canScrollVertically(1)的值表示是否能向上滚动,false表示已经滚动到底部
  • RecyclerView.canScrollVertically(-1)的值表示是否能向下滚动,false表示已经滚动到顶部
    byw_dongua
  • computeVerticalScrollExtent()是当前屏幕显示的区域高度
  • computeVerticalScrollOffset() 是当前屏幕之前滑过的距离
  • computeVerticalScrollRange()是整个RecycleView控件的高度
  • recyclerView.getLastVisiblePosition()获取当前可见的最后一个item的position
    Android 笔记_第1张图片

2. AppbarLayout

  • 定义:
    一个 orientation为 Vertical 的LinearLayout。
  • 特性:
    子 View 添加 scroll_flags可指定页面中view滑动时子 view 的相应动作。
  • 取值:
    下:下拉,上:上拉,出:出现,隐:隐藏,顶:下拉到顶部
      1. scroll,子view 作为页面中滑动组件的一部分参与动作。(下到顶出,上隐)
      1. enterAlways,和 1 一起设置。(下出,上隐)
      1. enterAlwaysCollapsed,和 1、2 一起设置。(下出,顶最大,上隐)
      1. exitUntilCollapsed,和 1 一起设置,需设置 minHeight。(下到最大,上到最小)
  • 运行默认有阴影,并且设置 elevation 和 translationZ 没有效果
    参考链接:月影涛声
AppBarLayout的style定义在Widget.Design.AppBarLayout

再进一步可以找到elevation的高度是design_appbar_elevatio

因此只要在自己项目的dimens中重写这个值即可

   0dp

3. 自定义 view

一开始因为定义太多,搞不清楚,妈的

大致流程是这样的:

  • 按功能决定要不要自己写布局
    • 有布局:在初始化的时候 LayoutInflater这个 xml 文件进来,之后就可以像正常操作Activity 一样操作这个 xml 文件 里的对象了。
    • 没有布局文件:那直接下一步。
  • 写一个自己的类继承自布局类
    有布局的话 继承自布局的最外层 view/ViewGroup
    没布局的话 继承你想继承的 Android 提供的 view/viewGroup
  • 实现四个构造方法,这四个构造方法一般是层层嵌套的,意思是1 个参数的调用 2 个参数的,2 调 3,3 调 4,然后我们就只用在 4 个参数的构造方法里面 init 就可以了。(但 kotlin 貌似继承完直接在init下写东西就完事了。)
  • 如果想自定义一些可以在 xml里面直接指定的属性,那么我们可以在src/value下新建一个 attr 文件,然后定义一些属性,然后在 init 里面获取就 OK 了。

其实分类可以分为两类:

  1. 自定义 view(这里是狭义的,标题自定义 view 是广义的,理解为自定义控件)
  2. 自定义 viewGroup

另外根据自定义方式可以分为三大类:

  1. 在 Android 原有的 view 上添加一些功能
    例如:我希望我的所有 TextView 都是红色字,那就直接定义一个类,继承自 Appcompat 下的 TextView(为了兼容性),然后在构造函数里面直接调用setTextColor(Int int)函数就行了。
  2. 自己写 layout 布局
    比如:我想自定义搜索框,然后在很多页面都可以复用,直接当做一个使用使用,那我定义一个类,继承自我这个搜索框布局的最外层对应的 viewGroup 类(LinearLayout、ConstraintLayout…),然后在构造函数中LayoutInflater这个 xml 文件,然后完事。
  3. 纯手画
    Canvas、Paint、TextPaint,画画需要画布、画笔、可以写字的笔,这个最好玩,但是又要学新知识,一堆概念,还在努力中。

4. 自定义 view 的坑

  • 调用顺序:init() ==> onMeasure() ==> onLayout() ==> onDraw()
  • 自定义 view 中获取宽度,getWidth/width 是拿不到的,只有在 onMeasure方法后面通过 getMeasuredWdith获取。如果必须在 init 中取得,那…其实也看情况,比如我需要获取 recyclerview 的itemview 的宽度,这时候 recyclerview 都还没onMeasure,你获取啥宽度都是 0,(然后我剑走偏锋,想直接在 init 里面调用 onMeasure(),发现需要传父布局给的参数...参数带一个标识和一个表示大小的数值,放弃了)。
    然后呢,我发现我需要重写的那个方法(也就是我需要使用用宽度的地方)居然参数有个 view,like this:
 recycler_view_fgd.outlineProvider = object : ViewOutlineProvider() {
            override fun getOutline(view: View, outline: Outline) {
                outline.setRoundRect(0, 0, view.width / titles.size, view.height, layoutRadius)
            }
        }

直接调用view.width即可。
或者呢如果是监听事件需要,设置全部变量,然后在 onMeasure() 里面初始化。

5. 监听输入法回车键

    1. 利用keycode监听
    1. 利用actionID监听
      google输入法英文模式不响应按键监听(Mi pad 4)
      有些输入法不响应actionid监听
      最保险的方法:
      edittext设置下inputType和IMEoption:键盘回车键图标随动作改变
      设置监听方法editorActionEvents,判断(action==xxx || keyCode == xxENTER)

#6. 屏幕适配
由于 API 26 及以上的 Activity#getResources()#getDisplayMetrics() 和 Application#getResources()#getDisplayMetrics() 是不同的引用,所以在 API 26 及以上适配是没有影响的,但在 API 26 以下 Activity#getResources()#getDisplayMetrics() 和Application#getResources()#getDisplayMetrics() 是相同的引用,导致适配有问题。

7. kotlin 监听输入框文字,只要有字就显示“X”图标

一开始我是这么写的,错了

              override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
              if (p0.toString().trim().isNotEmpty())
                  clear_search.visibility = View.VISIBLE
              else
                  clear_search.visibility = View.INVISIBLE
          }

发现输入空格也会出现,是因为kotlin默认允许null值toString(),并且返回“null”
解决方法:前面加个判断或者使用RxBinding,见下文。

8. 舒服的RxBinding(感谢LeiHolmes)

9. Android Studio 取消方法的参数间自动换行,使看起来更一致

Preference -> Editor-> Code Style -> kotlin -> Wrapping and braces -> Keep when reformatting
取消勾选 Line breaks,会看见右边模拟代码出会有这次修改产生的效果。
Keep when reformatting同一级的、在其上方可以看见 Hard wrap at,意思是一行达到这个字数就强制换行,可以修改成想要的字数,默认是default<==>100。

9. Glide 加载大图到Recyclerview的集中方式比较,ImageView宽度全屏,高度自适应

  • 我的结论是Glide的DecodeFormat.PREFER_RGB_565配合into配合鲁班压缩配合jpg。
    . 原图为png(菜狗想的格式),存储大小300+K,加载到内存平均一个图5k1k4/(1k1k) = 20M,Glide还有缓存,不作任何处理的话,直接
    GlideApp.with(context).load("file:///android_asset/weird/$position.png").into(img)
    加载几个图片就OOM。
    1. 想到了不用Glide,我Bitmap解析还可以设定粒度,相当于压缩率,但是想想Glide的生命周期的处理和确实方便的缓存,还是继续调优,bitmap操作方式关键词:bitmapOption。
    1. 其实直接into(img)会自动压缩,比如原图4096px,屏幕1024px,显示的时候就是1024px,Glide有自动压缩,再配合设置下DecodeFormat.PREFER_RGB_565就差不多了。但是Glide一直提示Glide treats LayoutParams.WRAP_CONTENT as a request for an image the size of this device's screen dimensions. If you want to load the original image and are ok with the corresponding memory cost and OOMs (depending on the input size), use override(Target.SIZE_ORIGINAL). Otherwise, use LayoutParams.MATCH_PARENT, set layout_width and layout_height to fixed dimension, or use .override() with fixed dimensions.,没办法,只能不管了,显示没问题。
    1. asBitMap使用后,内存量翻倍…无论怎么优化,放弃。
    1. 尝试优化布局,去除ImageView改为设置Layout的background,卒,background不是内容,不会将其撑起。

10. Android端接入支付宝问题

* 横竖屏切换的生命周期
```
  WelcomeActivity:onCreate
  WelcomeActivity:onStart
  WelcomeActivity:onResume
  WelcomeActivity:onPause
  WelcomeActivity:onResume
  WelcomeActivity:onConfigurationChanged
  WelcomeActivity:onPause
```

11. Retrofit之坑

没有一个好用的框架,适合自己的只有自己写的,干。

首先使用的时候按照大家喜闻乐见的方式调用,取消的话就直接call.cancel(),但偶然发现tmd这个所谓动态代理模式完成的Retrofit为了解耦,cancel()是不能及时让回调知道的。

上面说的简直不是人话,行业陋习,能简单解释清楚的非要整一堆术语。

其实就是callback里的回调方法在判断call!!.isCanceled()的时候不及时,cancel了还是返回false,也就是没有被取消。

心路历程

首先是测试在aActivity发请求,这时候由于某种问题直接跳转到bActivity然后finish aActivity,并且a的onDestory调用了call?.cancel(),但callback的回调会依然执行,并且,在已经与服务器建立连接的情况下会默认调用onFailure(),那假如我在失败的回调中有UI处理,或者只是简单的非Application级别的Toast,APP直接FC
⬇️
然后我分别在回调中判断call!!.isCanceled()发现还是不行,一直返回false
⬇️
然后看见Android 笔记_第2张图片
ok,在onFailure()的回调中加上判断,继续,还是不行,调试发现message为socketClosed
⬇️
继续研究发现,没建立连接消息为canceled,建立后canceled直接会socketClosed,那我要判断socketClosed作为取消的条件吗?不对
分析需要的状态(比如下载文件):

  • 成功 - onResponse()(progressBar更新进度,dialog提示下载完成
  • 失败 - onFailure() && !canceled()(dialog提示下载失败请重试
  • 取消 - canceled()(取消弹窗,toast取消下载

但是别忘了,没有网络或者网络错误也是会有socketClosed消息并且走onFailure()

瞬间想骂娘。

然后看见有自定义CallBack的,自定义Call的,想了想,还tm不如不用了。

但是权衡之后感觉,配合rx应该还行,所以还是没得出一个比较好的结论。

12. Fragment的生命周期

无论是怎么创建的,都是onCreateView -> onViewCreated -> onActivityCreated 这个顺序。

13. Window一些属性

Window布局:Window>DecorView>ContentView

属性名称 作用
windowIsFloating 自定义dialog中match_parent属性无效,因为默认此属性为true,会直接设置window的宽高为wrap_content
windowFrame 带不带默认dialog的边框

14. 自定义View获取不到所有属性

原因是context.obtainStyledAttributes(attrs,R.styleable.xxx)这里的attrs没写。

15. 自定义View使用TypedArray获取Drawable数组或者其他数组

以获取drawable数组为例

// 获取对应的资源id,类似R.array.xxx
val resIcon = ta.getResourceId(R.styleable.MuseTabBar_tab_bar_icons, -1)
// 通过id获取资源,类似resource.getIntArray()
val iconTa = resources.obtainTypedArray(resIcon)
// 本地建立数组
val icons = IntArray(iconTa.length())
// 遍历资源
for (i in 0 until iconTa.length()) {
    // 获取资源中的每个drawable id
    icons[i] = iconTa.getResourceId(i, 0)
}

你可能感兴趣的:(Android,自定义View)