Activity
透明与方向
当且仅当Android 8.0
系统中,不能对一个Activity
同时设置透明(windowIsTranslucent
和windowIsFloating
)和方向(screenOrientation
),否则会抛出Only fullscreen opaque activities can request orientation
异常崩溃
解决方法:
在代码中先判断系统版本,再设置方向
1 | override fun onCreate(savedInstanceState: Bundle?) { |
TextView
重新布局
TextView
在调用setText
后不一定会走requestLayout
方法,在某些情况下会导致显示异常,比如因RecyclerView
复用导致长度不符合预期的问题
解决方案:简单来说当TextView
的width
属性不为WRAP_CONTENT
且文字高度没发生变化的情况下,它就不会重新布局,如果你需要它重新计算宽高的话,注意以上的条件,设置合适的属性即可,具体的源码分析可以参考 从 TextView.setText() 看 requestLayout 和 invalidate 方法有什么不同 这篇文章
ellipse
TextView
在某些情况下,ellipse
属性会失效
setText
设置了BufferType
为NORMAL
以外的其他值TextView
设置了MovementMethod
layout
通过TextView
中的android.text.Layout
,我们可以通过它计算很多东西,比如通过x ,y坐标去获取字符下标呀,或者通过字符下标去计算这个文字x坐标等等,但需要注意的是,android.text.Layout
在计算的过程中不会去考虑TextView
的padding
,所以在开发的过程中我们自己需要进行一些处理,比如说通过x ,y坐标去获取字符下标的时候,传入的x, y值要减去paddingLeft
,paddingTop
,当通过字符下标去计算文字x坐标后,要再加上paddingLeft
才是正确的x坐标
滑动
- 滑动嵌套
滑动组件的嵌套可能会产生以下一些问题:
- 滑动冲突
解决方法:使用NestedScrollView
替代ScrollView
,RecyclerView
可以设置属性android:nestedScrollingEnabled="false"
或代码里setNestedScrollingEnabled(false);
来禁用组件自身的滑动
注意:如果RecyclerView
只能显示一个Item的话,需要设置NestedScrollView
的属性android:fillViewport="true"
- 滑动失效
ScrollView
设置fillViewport="true"
的情况下,如果对ScrollView
的直接子view设置上下margin,在超出内容的高度小于设置的margin的情况下,可能会导致整个ScrollView
滑动失效
- 焦点抢占
ScrollView
、RecyclerView
等滑动组件可能会抢占焦点,导致界面显示时直接滑动到对应组件的位置,而不是顶部
解决方法:在顶部View(或者其他你所期望的初始位置)加上属性android:focusable="true"
和android:focusableInTouchMode="true"
新解决方法:在顶部View上加android:descendantFocusability
属性,该属性是用来定义父布局与子布局之间的关系的,它有三种值:
beforeDescendants
:父布局会优先其子类控件而获取到焦点afterDescendants
:父布局只有当其子类控件不需要获取焦点时才获取焦点blocksDescendants
:父布局会覆盖子类控件而直接获得焦点
使用blocksDescendants
覆盖子布局焦点以解决焦点抢占问题
RecyclerView
Adapter
- 在
onBindViewHolder
中设置子View回调时需要注意
如果回调的参数包括position时,需要注意有没有地方会调用notifyItemRemoved
或notifyItemRangeRemoved
,如果有,需要使用holder.getAdapterPosition()
来代替onBindViewHolder
方法的position参数
原因:notifyItemRemoved
不会对其他的Item重新调用onBindViewHolder
,这样可能会导致position错位。holder.getAdapterPosition()
方法会返回数据在 Adapter 中的位置(即使位置的变化还未刷新到布局中)
- 如何在更新数据后重新定位到顶部
1 | //重写父类方法,获得绑定的RecyclerView |
之前尝试过mRecyclerView.scrollTo(0, 0);
但没有起效,不清楚为什么
- 动态部分更新数据时
如果RecyclerView
需要动态更新部分数据,并且在onBindViewHolder
时对某些view设置了事件或者回调等,如果此时使用到了position参数需要注意,如果你只notify了部分数据更新,可能会导致更新后部分ViewHolder中的回调里的position不正确,建议:
- 使用
notifyDataSetChanged()
- 使用
notifyItem
,但是在onBindViewHolder
中设置回调时不要使用position参数,而是使用holder.getAdapterPosition()
替代(注意这个方法在ViewHolder
没有和RecyclerView
绑定时会返回-1NO_POSITION
)
ItemDecoration
StaggeredGridLayoutManager
下ItemDecoration
的offset
计算错误
主要是因为RecyclerView
动态更新数据时,会执行多次measure
,但只会在第一次measure
的时候调用ItemDecoration.getItemOffsets
(因为LP
里的mInsetsDirty
变量),此时获得的spanIndex
是一个错误值
这个问题的具体分析可以看这篇文章,暂时没有什么好的解决方案,不建议大家使用反射,毕竟你不知道Android
会不会更改这个变量
嵌套ViewPager
在RecyclerView
中嵌套ViewPager
的情况下,当你将一个ViewPager
滑动出视野再滑回来,这个ViewPager
的下一个切换会没有动画
原因:当RecyclerView
的Item
滑入滑出屏幕时分别会调用子View
的onAttachedToWindow
和onDetachedFromWindow
方法,当ViewPager
触发onAttachedToWindow
后,会将其里面的一个表示是否为第一次布局的成员变量mFirstLayout
赋值为true
,当这个变量为true
时,ViewPager
会以无动画的方式显示当前Item
解决方法:重写RecyclerView.Adapter
的onViewAttachedToWindow
方法,在里面对ViewPager
调用其requestLayout
方法,在ViewPager.onLayout
方法最后,会将mFirstLayout
变量重新赋值为false
Bitmap
RenderScript高斯模糊
在使用RenderScript
做高斯模糊时,需要注意,它只支持格式为ALPHA_8
、ARGB_4444
、ARGB_8888
、RGB_565
的Bitmap
,对于其他格式的Bitmap
,可以尝试使用Bitmap.reconfigure
方法转换格式(这个方法不能将Bitmap
从小格式转换成大格式,比如不能从占用32个bits的ARGB_8888
转换成占用64个bits的RGBA_F16
)
Dialog
- 生命周期
- 初始化时需要注意
Dialog在第一次调用show()
方法后才会执行onCreate(Bundle savedInstanceState)
方法,因此建议自定义Dialog时将findViewById
等初始化操作放在构造函数中进行,避免外部使用时因在show()
之前设置视图数据导致NPE
PopupWindow
- 点击没反应
PopupWindow
如果不设置背景的话,在某些5.x以下系统机型上会出现点击没反应的问题
解决方法:给PopupWindow设置一个空背景popupWindow.setBackgroundDrawable(new BitmapDrawable(mContext.getResources(), (Bitmap) null));
详见:https://juejin.cn/post/6844903761488379912
广播
- 隐式广播
在Android8.0以上的系统,大部分的隐式广播都被限制不可使用。
解决方法:
- 使用动态广播
- 使用显示广播
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20// 方式一: 设置Component
Intent intent = new Intent(SOME_ACTION);
intent.setComponent(new ComponentName(context, SomeReceiver.class));
context.sendBroadcast(intent);
// 方式二: 设置Package
Intent intent = new Intent(SOME_ACTION);
intent.setPackage("com.dreamgyf.xxx");
context.sendBroadcast(intent);
// 不知道包名的话可以通过PackageManager获取所有注册了指定action的广播的package
Intent actionIntent = new Intent(SOME_ACTION);
PackageManager pm = context.getPackageManager();
List<ResolveInfo> matches = pm.queryBroadcastReceivers(actionIntent, 0);
for (ResolveInfo resolveInfo : matches) {
Intent intent = new Intent(actionIntent);
intent.setPackage(resolveInfo.activityInfo.applicationInfo.packageName);
intent.setAction(SOME_ACTION);
context.sendBroadcast(intent);
}
软键盘
- 弹起软键盘
网上大部分文章所写的弹起软键盘的方法并不完美,大部分文章让你在onResume
时再弹起,有的文章甚至让你postDelayed
,非常不靠谱,经过本人分析,软键盘的弹起需要满足以下几个条件:
控件为
EditText
或其子类控件所在的
window
要获得焦点控件本身要获得焦点
根据以上几个条件,我写了一个完美弹起软键盘的方法,onCreate
时也可以照常使用:
1 | fun View.showKeyboard() { |
实体键盘
EditText
有焦点时会拦截键盘的数字键
解决方法:使用TextWatcher
等监听EditText
输入
内存泄漏
- 动画
在Activity销毁之前如果没有cancel掉,会导致这个Activity内存泄漏
ClickableSpan
使用SpannableString.setSpan
方法设置ClickableSpan
可能导致内存泄漏
原因:TextView
在onSaveInstanceState
时会将ClickableSpan
复制一份,由于某些原因,SpannableString
不会删除这个ClickableSpan
,从而导致内存泄漏,详见:
StackOverflow
解决方法:自定义一个抽象类同时继承ClickableSpan
和实现NoCopySpan
接口,外部setSpan
时使用这个抽象类
Fragment
Fragment
尽量不要使用带参构造函数,一定要保证有一个不含参的构造函数,否则在Activity
重建时尝试反射newInstance
恢复Fragment
时会抛出Could not find Fragment constructor
异常
混淆
- 反射
如果使用到了反射,需要特别注意需不需要在proguard-rules
中加入keep规则
- module混淆
如果是多module项目,想要在module中增加混淆规则,proguardFiles
属性是无效的,应该使用consumerProguardFiles
属性
1 | android { |
相机开发
- 拍照角度
相机的方向一般是以手机横向作为正方向,这样如果我们以竖屏的方式拍照,拍出来的照片可能会出现旋转了90度的情况,这时候就需要在拍照完后处理一下图片,旋转到正确位置。
具体介绍与算法在Android SDK中CaptureRequest.JPEG_ORIENTATION
的注释中
1 | private int getJpegOrientation(CameraCharacteristics c, int deviceOrientation) { |
计算好角度后就可以对图片做旋转了,网上有很多文章都说使用这种方式做旋转
1 | captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getJpegOrientation(deviceRotation)); |
但实际上在某些系统上 (MIUI),设置的这个参数并不会生效,所以我的方案是,获得拍摄好的照片Bitmap后,再对其进行旋转
1 | public Bitmap rotateBitmap(Bitmap bitmap, int angle) { |