Android5.0相关
点按水波纹效果的属性设置
Android5.0系统以后,为了让数字世界更加贴近于真实的物理世界,在点击控件时,可以设置水波纹效果,这个动画被认为是数字世界对物理世界的还原与致敬。对于程序来说,只要给控件设置下面两句属性,就可以有点击水波纹效果:
- 水波纹效果会超过控件的边界:
android:background="?android:attr/selectableItemBackgroundBorderless"
- 水波纹效果在控件边界里面:
android:background="?android:attr/selectableItemBackground"
如果直接使用第一种,在5.0以上的系统可以运行,但是5.0以下系统因为没有这个属性,所以程序会出现崩溃,刚开始的解决方案是新建values-v21
的资源文件夹,在里面的styles
用第一种属性;在values
里的styles
用第二种属性,这样就可以解决兼容的问题。
后来发现一种不用设置两种属性的方式,直接在属性里面设置android:background="?attr/selectableItemBackgroundBorderless
,这句代码的意思是:如果控件背景属性支持Borderless的,就用Borderless,如果没有,则自动进行兼容。
Snackbar出现时控件上移效果
分析
在研究了极简笔记Snackbar
相关的代码后,发现CoordinatorLayout+FloatingActionButton+Snackbar
三个在一起,可以达到Snackbar出现时FloatingActionButton上移,Snackbar消失后FloatingActionButton下移回到原位置的效果,这个效果依赖于5.0系统在源码上实现了FloatingActionButton对Snackbar的状态的监听。而对于我们自己定义的控件,则需要自己去实现这个监听。
方案
重写Snackbar
类,主体保持与系统源码相同,只增加一个静态抽象类Callback
,并且在Snackbar出现后以及消失后实现该抽象类的两个方法:
onShown
onDismissed
然后在创建Snackbar的时候,加入setCallback(Snackbar.Callback callback)
方法,并在onShown()
和onDismissed()
里实现具体的逻辑。示例代码
|
|
定制Snackbar
除了上面一条Snackbar出现控件上移
,Snackbar在UI上也可以进行定制,定制是在SnackbarLayout
这个内部类里实现,并且Snackbar的布局文件也有两个,一个是没有撤销
键时的样式,另一个是有撤销
键时的样式,原生组件这两个布局文件名字分别是:
layout_snackbar.xml
–无撤销键layout_snackbar_include.xml
–有撤销键
Toolbar使用方法
Toolbar
作为5.0以后出现的一个控件,支持很多自定义的效果,是ActionBar
一个很好的代替,下面就来介绍下它的一般使用步骤:
- 单独创建一个toolbar的layout,在需要使用的时候对其进行引用:
<include layout="@layout/toolbar"/>
。在toolbar的layout里,可以设置它的一些属性 - 在BaseActivity里,需要有方法可以判断是否需要toolbar这个控件,也需要有方法可以获取到toolbar,以便于在具体Activity里可以重新设置toolbar的样式:background、TitleTextColor等
- 关于toolbar一些基本属性的介绍,可以参考:Android 5.x Theme 与 ToolBar 实战
各种类和接口使用分析
Adapter加载机制
注意点一
实例化结束后,会先执行getCount()
方法,当getCount()
方法返回的值不为0时,会继续执行getView()
方法;若getCount()
返回的值是0,则不会再继续执行getView()
方法。
注意点二
在V1.3.1版本以前,会出现点击选择图片闪烁的问题,刚开始都以为是Adapter的问题,在跟许可探讨了这个问题之后,发现可能是UniversalImageLoader
这个第三方开源库自己的bug,因为许可之前在使用该开源库时也有过点按闪烁的问题。
在之前解决该问题时,尝试过重新写了一个Adapter,也还是有闪烁问题,共同的一个现象就是,在点按选择照片出现闪烁的时候,getCount()
和getView()
方法都会明显比不闪烁的时候执行更多的次数,说明Adapter在重复进行刷新。
Handler采用弱引用方式(WeakReference)
分析
简单来说,之前都是用内部类(包括匿名类)的方式创建Handler,用这种方式Handler会隐式地持有一个外部类对象(通常是Activity对象)。在系统进行耗时的网络请求时,Activity被关闭,但这时候线程会继续持有对Handler的引用,而Handler又会持有对Activity的引用,导致Activity无法被系统正常回收,造成内存泄漏,进而会导致OOM(内存溢出)。
方案
采用静态类+弱引用的方式(因为静态类不再持有对外部类的引用,Activity被关闭时,系统可以正常收回;而对于Handler无法操作Activity中对象的问题,采用弱引用的方式解决,而弱引用不会影响系统对Activity的收回)
示例代码
|
|
注意在Activity初始化时不要忘记实例化Handler对象,详细的解释请参考Android中使用Handler造成内存泄露的分析和解决
Fragment+FragmentPagerAdapter
两者之间如何通信
|
|
在FragmentPagerAdapter
(或者它的子类)里重写getItem()
方法,用Bundle
类将当前的position
存起来,再用fragment.setArguments(bundle)
方法将当前位置信息传给fragment,在Fragment
(或者它的子类)里,用getArguments()
方法取得当前的位置,再将View与位置一一对应起来进行设置。
Fragment里的onCreateView与onViewCreated
onCreateView
一般只用于LayoutInflater.inflate去初始化一个页onViewCreated
顾名思义是当View加载完成之后,马上执行,确定View里面具体放置一些什么控件
自定义WechatCallback接口
分析
微信分享接收回调是用了WXEntryActivity
这个类,并给这个活动设置了透明主题。这个版本中引入了Snackbar,而Snackbar的绘制机制是会找一个父视图,如果没有这个父视图,则会创建不成功。所以在透明主题下,微信回调使用Snackbar出现不会显示的问题。
方案
- 自定义一个WechatCallback接口,在WXEntryActivity里实现该接口对应的四个方法:
void onShareSuccess(int statusCode, int result)
void onShareCancel(int statusCode, int result)
void onShareDenied(int statusCode, int result)
void onShareDefaultStatus(int statusCode, int result)
- 在目标页面
setCallback
,这个回调最终是用弱引用的方式获得 - 在目标页面接收回调,并进行相应的处理
动画
点击Flash展开的动画(直接代码设置)
逻辑
- 点击
Flash
按钮展开选项,可以用setPivotX
方法设置展开的位置 - 点击
Flash
按钮关闭选项,这个时候Flash
按键本身有一个旋转动画:- 离
Flash
按钮近的转到远的,旋转角度是顺时针 - 其它情况是逆时针
- 离
主要代码
整个动画主代码
|
|
展开关闭动画
|
|
旋转动画
|
|
5.0系统以下点击按钮透明度变化(引入xml文件属性生成)
因为5.0以下系统不支持点击按钮水波纹效果,所以设计成按钮透明度变化来产生点按效果,与上面完全代码实现动画效果不同的是,在完成这个效果时,将属性写在了xml文件里。具体代码如下:
更多属性动画
的介绍,可以参考:Android属性动画–Property Animation(一)和Android属性动画—Property Animation(二)
Bug分析思考与记录
Bug1:横向图片马赛克操作会报空指针
分析
之前代码在onPathEvent
方法中定义了马赛克操作不能超过图片尺寸的边界限制,所以对于横向图片,当手指从边界外划到边界里时,只能识别到ACTION_MOVE手势(前面由于在边界外,没有执行到ACTION_DOWN就返回了),这样也就只执行了ACTION_MOVE里面的代码,mTouchPath没有实例化,会报空指针错误。
方案
取消边界限制,让ACTION_DOWN里面的实例化以及其他初始化操作在任何时候都能执行到。
Bug2:点击删除-撤销-删除后无法最终删除图片
这个Bug最初是由于我快速随便点按才出现的,刚开始不知道Bug是偶现还是必现,后来慢慢操作了很多次(总的加起来应该有1个多小时),终于发现这个Bug是必现的,操作步骤就是:删除-撤销-删除。在思考的过程中,也走了一些弯路才最终定位到问题的地方,在此记录下来,下次遇到类似的非崩溃型Bug应该如何思考:
- 与Bug出现相关的方法多打Log,Log打的位置要有选择:分析某个方法里面代码分成几段(
例如哪里有return的,可能导致该方法中途就退出的
),分别插入Log - 再根据Bug出现的步骤进行复现,分析Log,看最终导致Bug出现的原因
这个Bug产生的原因是:删除图片所需的Zfid
为空,deleteFileAndUpdate
方法没有执行完就已经退出。最终通过修改FileUtil类
里的parseHeaderZfid
方法,解决了这个Bug。
Bug3:performing stop of activity that is not resumed
分析
这个Bug是非崩溃型的,刚开始报错的时候都没有去注意它,后来有空了,在stackoverflow找到相应的答案,在没门儿的场景里面产生这个报错的原因是:
- 进行完临时保存图片(非加密)的task以后,在回调中要进行跳转到另外一个activity
- 在跳转之前,要
restartCameraPreview
,涉及到UI操作,所以用handler
发送emptyMessage
,在再handleMessage
里进行处理 - 开始的时候跳转activity的操作直接在回调里面进行
- 跳转时执行
onStop
,但当前activity的重启操作还没有进行完,也就是没有执行onResume
方案
将跳转activity的操作也在handleMessage
里面处理,位置在restartCameraPreview
后面,这样就保证了SecureCameraActivity
重启完,再进行跳转,也就不会报上面的错了。
小知识点
xmlns属性
xmlns
是xml namespace的缩写,标准的格式是xmlns:namespace-prefix="namespaceURI"
,这个属性需要定义在最外层开始的标记中,并且所有带有相同prefix的子元素都会与这个命名空间(namespace)相关联。如果没有在最外层定义相关的命名空间,子元素也就不能使用该命名空间。
一般常用的命名空间有:
- xmlns:android=”namespaceURI”
- xmlns:app=”namespaceURI”
- xmlns:tools=”namespaceURI”
- 对于自定义控件,可以自定义xmlns,具体参考:xmlns:android作用以及自定义布局属性
string.xml中%1$s、%1$d等用法
如果在string.xml中定义的部分内容需要在代码中进行替换,则可以用%1$s
这样的用法,具体用法如下:
格式
- %1$s(表示第1个需要替换的位置,是字符串类型)
- %1$d(表示第1个需要替换的位置,是整型类型)
- %1$f(表示第1个需要替换的位置,是浮点数类型)
- %2$d(表示第2个需要替换的位置,是整型类型)
用法
- 在string.xml进行定义,例如:
<string name="photo_import_progress">正在导入(%1$d/%2$d),请稍后...</string>
- 在代码中用
getString(R.string.photo_import_progress, int, int)
或者String.format(getString(R.string.photo_import_progress), int, int)
来进行替换组成新的字符串
- 在string.xml进行定义,例如:
swtich-case代码规范–case后面有没有大括号的区别
不加大括号
整个switch里面的case都在一个作用域里,相同的变量只能初始化一次。
加大括号
每个case里面都是一个作用域,相同的变量名可以在各自作用域里进行初始化,而不会互相影响。为了保险起见,最好在case后面都加上大括号,避免作用域问题引起程序编译问题。
详细说明参考:java Switch中的case后面加上大括号({})和不加大括号的区别
判断用户是否第一次进入应用以显示介绍页
将应用的VersionCode用SecurePreferences类进行保存,有两种情况:
- 新用户:
mSharedPrefs.getString(mPreferencesFirstIn, "")
为空,此时进入IntroductionActivity - 老用户升级以后:
mSharedPrefs.getString(mPreferencesFirstIn, "")
获得的VersionCode跟保存着的老版本不一样,此时也进入IntroductionActivity。等用户点击开始体验
进入应用后,将VersionCode进行保存,这样第二次进入时,就不会再显示介绍页
onActivityResult方法
如果在页面跳转时如果使用了startActivityForResult
方法,那当返回当前页面时,传回来的值,可以重写onActivityResult
方法来完成自己想要的一些操作,在V1.3.2.0版本开发过程中,规范了页面跳转的逻辑,尽量使用startActivityForResult
进行页面跳转,并用RESULT_CANCEL
、RESULT_OK
等进行传值,而不是像以前一样每次都重新startActivity,造成不必要的浪费。
注意点
在开发过程中,由于QQ分享
的回调也要重写onActivityResult
方法,放开始的时候,没有判断requestCode
,造成QQ分享
回调一直不成功,后来才被告知是没有判断requestCode
,造成回调也无法判断。所以在以后进行开发时,要把requestCode
和resultCode
一起进行判断,避免跟其它一些返回的值有冲突。