万丈红尘千杯酒,
千秋霸业一壶茶。
context.getResources().getDisplayMetrics().heightPixels 的含义:
一般情况下,它等于屏幕真实高度 - 导航栏高度。然后在 Android P 上, 它等于 屏幕真实高度 - 导航栏高度 - 刘海高度。
但是总有意外发生,某些全面屏手机(例如小米、vivo),在设置上提供了 导航栏/全面屏手势 选项,如果选择全面屏手势,界面是不存在导航栏的,但是 context.getResources().getDisplayMetrics().heightPixels 的值依然会减掉这个不存在的导航栏的高度,这就导致依赖它的相关计算就会不准确。。。。
RecyclerView 在 ItemAnimator 的辅助下,item 的“增”、“删”动画非常完美,但是“改”的动画并不总是被需要,容易给用户闪烁的感觉。因而,官方在 SimpleItemAnimator 里添加了方法 setSupportsChangeAnimations 来供开发者选择是否需要“改”的动画。
val itemAnimator = DefaultItemAnimator()
itemAnimator.supportsChangeAnimations = false
mRecyclerView.itemAnimator = itemAnimator
使用 EditText 的 setInputType 方法的一个麻烦点在于: 很多时候设置一个属性是不生效的,往往需要同时设置多个属性才能生效。 例如:
// 密码
EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD
// 纯数字密码
EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD
// 小数
InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL
Android Studio 3.0 会默认在 debug 包的 manifest 文件里添加 android:testOnly="true"属性,导致 IDE 中跑出的 apk 在大部分手机上只能用 adb install -t <apk>来安装,在 oppo, vivo 等手机上甚至安装不了,会出现下列错误提示:
Failed to install app-debug.apk: Failure [INSTALL_FAILED_TEST_ONLY: installPackageLI]
这个时候可以在 gradle.properties 文件中添加下列配置:
# gradle.properties
android.injected.testOnly=false
Android 从 API 24 版本开始支持 multiple windows。那么 app 就可能处于 visible 状态而不处于 active 状态,即处于 onStart 状态。
关于 View.offsetTopAndBottom (View.offsetLeftAndRight):
Android 的键盘处理比较奇葩,换行与确定不能兼得。如果要键盘的 Action 为发送、确定等时,一定要求 EditText 为单行,自动换行都做不到,差评!
使用 ApplicationContext 去 startActivity 在某些 Android 版本下必须要加上 FLAG_ACTIVITY_NEW_TASK,否则会 crash。 详情请见 ContextImpl.startActivity(Intent, Bundle) 。(注:API 23 以下 和 API 23 及其以上的实现并不一样)
当我们想 TextView 在上使用跑马灯效果时, 我们会发现简单的设置 android:ellipsize="marquee" 和 android:singleLine="true" 并不生效。我们还需要主动调用 setSelect(true) 才行。
Dialog 的创建不能使用 ApplicationContext,会 crash。
RecyclerView 的 LinearLayoutManager 提供了两个比较好玩的 API :reverseLayout 和 stackFromEnd
View.getWindowVisibleDisplayFrame(Rect rect) :
作用:返回当前 view 所 attach 的 window 的可视区域的大小
影响因子:statusBar(rect.top)、navigationBar(rect.bottom)、虚拟键盘(rect.bottom)
具体表现:
如果 window 不与 statusBar、navigationBar 和虚拟键盘重叠,那么 rect.top = 0,rect.bottom = screenRealHeight - navigationBarHeight
rect.top = 全屏 ?0 : (widow.layoutparam.height == MATCH_PARENT ? 状态栏高度 : window 与状态栏高度重叠的高度)
rect.bottom(键盘升起) = widow.layoutparam.height == MATCH_PARENT ? screenRealHeight - navigationBarHeight - keyboardHeight :screenRealHeight - navigationBarHeight - 键盘与window重叠区域的高度
rect.bottom(键盘未升起) = screenRealHeight - navigationBarHeight
当我们的 ListView 拥有多着类型的 itemView 时,一定要注意 adapter.getViewTypeCount 的维护,特别是后期添加类型时,经常忘记更改 getViewTypeCount,因此就会出现如下 crash :
如果对于老手,可能快速反应出问题的关键点,但对于新手或者不熟悉 ListView 多类型的开发者,就会有点不知所措了。对于 RecyclerView,就不会有这个问题了。
当 ListView 的 itemView 的子 view 存在点击事件或者 clickable 为 true 时, 那么点击 itemView 不会触发 ListView 的 mOnItemClickListener;反之,如果你不想 ListView 的 mOnItemClickListener 被触发,你可以设置 itemView.setClickable(true)
View 的 detach 和 attach 可能会因为 Animation 而变得混乱,不经意间就造成 View 假死,例如:
mView.startAnimation(disappearAnimation);
ViewGroup parent = (ViewGroup) mView.getParent();
parent.removeView(mView); // 移除 view 时伴随动画
//...
parent.addView(mView); // 动画未结束时又将 view 加回来
mView.startAnimation(appearAnimation);
现象:mView 无法接收事件,使用 uiautomatorviewer 时也无法查看其信息
原因:当 mView.getAnimation() != null 时,parent.removeView() 并不会detach 掉 mView,而是暂时将 mView 放入的成员变量 mDisappearingChildren 中,在 parent.dispatchDraw() 中当 mView.getAnimation() != null 且 动画已经结束时,才执行 mView 的 detach逻辑。但是当动画中途执行 mView.StartAnimation(newAnimation) 时, parent.dispatchDraw() 中 mView.getAnimation() 所获取的对象已经变了,那么 detach 的时机也就变了。
上述代码的执行顺序大致为:mView.startAnimation(disappearAnimation) -> parent.removeView -> mDisappearingChildren.add(mView) -> parent.addView -> mView.attach -> mView.startAnimation(appearAnimation) -> appearAnimation.end -> mDisappearingChildren.remove(mView) & mView.detach。最终 mView 处于 detach 状态,故而 View 会假死。
现实场景:当 fragment A 切换到 fragment B 时,如果转场动画还没结束时就快速按返回键使 fragment B pop 掉,最终会造成页面假死在 fragment A 上。QMUI 上就遇到这个问题,具体解决措施也可参考参考
MIUI8 USB开发过程中禁用装包提示:设置->更多设置->开发者选项->关掉MIUI优化,重启,再进入安全中心->授权管理->右上角的小齿轮->关掉USB安装管理
ListView、GridView 在 getView 时要避免调用 View 的 setLayoutParam。因为 setLayoutParam 会触发 requestLayout 导致 ListView 刷新 layout,而 ListView 重新 layout 时又可能触发 getView,导致多次甚至循环 layout。这种循环 layout 会产生以下几个现象:
当 Activity 设置了 android:windowIsTranslucent 后,其屏幕旋转取决于透明 Activity 背后的那个 Activity,我们可以把透明 Activity 当做一种特殊的 Dialog,这样大致可以理解 Android 开发团队为何要这样做了。如果我们需要动态修改旋转特性,我目前有两种方案:
当 Activity 设置了 android:windowIsTranslucent 或者 android:windowIsFloating 后,默认情况下 onConfigurationChanged 就不再被调用到,因为这种情况下会触发透明 Activity 后的那个 Activity 的绘制(耗费性能),必要时候需要添加 android:screenOrientation="sensor" 强制使旋转生效。
ViewPager 的 PagerAdapter 存在一个方法 setPrimaryItem,在 item 被认为是 “primary” 时会被调用。但是其在 ViewPager.onMeasure 时也会调用到,如果使用不当,可能会造成死循环。
App 的 Context 的数量 = Activity 的数量 + Service 的数量 + 1(Application)
用 Activity A 去启动一个 Translucent Activity B,那么 Activity A 将不会调用 onStop 方法,因为 onStart 与 onStop 对应于 visible lifetime。只有当 Activity 不可见时,onStop才会调用。
自定义 View 在定义 attr 时需要注意: attr 的 name 不能是系统已有的,也不能是第三方库已经定义的,也不能是项目已经定义了的。推荐加上前缀来避免冲突。
RecylerView 使用 LinearLayoutManager 时,需要注意 LinearLayoutManager 的 generateDefaultLayoutParams() 默认返回的宽高都是 WRAP_CONTENT,如果我们希望 orientation 为 VERTICAL 且 子元素 宽度 MATCH_PARENT 时,我们需要重写 generateDefaultLayoutParams() 来达到目的。