Bitmap 是内存优化逃不了的一个东西,本文探讨下,Bitmap 中的 density 到底是什么东西,它是如何影响到内存的使用的
先看下 density 的文档注释
1 | /** |
简单来说 density 是用来绘制缩放用的,默认情况下的 density 就是屏幕的 density(resources.displayMetrics.densityDpi
),假如我修改了一张 Bitmap 的 density,那么图片的显示应该会发生缩放,写个简单的 demo 验证下
Bitmap 是内存优化逃不了的一个东西,本文探讨下,Bitmap 中的 density 到底是什么东西,它是如何影响到内存的使用的
先看下 density 的文档注释
1 | /** |
简单来说 density 是用来绘制缩放用的,默认情况下的 density 就是屏幕的 density(resources.displayMetrics.densityDpi
),假如我修改了一张 Bitmap 的 density,那么图片的显示应该会发生缩放,写个简单的 demo 验证下
某天某时某刻,脑内突然发现一个疑问:RippleDrawable
是怎么把波纹绘制到所在 View
外面的?
稍微了解点 Android
绘制知识的就知道,子 View
的 onDraw(canvas)
获取到的画布默认是被父亲裁剪掉的,导致子 View
无法绘制到自身外面
那么问题就来了,为毛 RippleDrawable
可以绘制到外面,用了什么原理?莫非有特权?
之前一篇文章,我在 Android 上用 FFmpeg 来压缩视频 (文章链接),对于 exit_program
的处理,是直接改成 return
的。稍微处理不好一点(因为 c 没有异常),程序就会 creash。还有就是在 Android 上,其实可以使用 MediaCodec
来硬解码视频的。本文讲述怎么解决上面两个问题。
前几天项目需要压缩视频,Github
上找了许多库,要么就是太大,要么就是质量不高,其实我只需要压缩视频,最好的方案还是定制编译一个 FFmpeg
给 Android
用。
本项目使用
FFmpeg
和libx264
(一个第三方的视频编码器) 来编译出可以在Android
上使用的动态库
用过 iOS 的都知道,拟物理的回弹效果在上面非常普遍,因为这是 iOS 系统支持的一套 UI 框架,但是 Android 就没有了,就拿图片查看器来讲,iOS 的效果就是感觉一张图片被绑定在了弹簧装置上,滑动很自然,Android 没有自带的图片查看器,需要自己实现
市面上主流的图片查看器都没有回弹的效果,一部分原因是没有这个需求,还有一部分是实现麻烦,这里讲述一个个人认为最好的方案
一个图片查看器,要求可以滑动 Fling,触碰到边界的时候回弹,有越界回弹的效果,支持双指缩放,双击缩放
咋一看需求,应该好写,滚动的时候用 Scroller
来解决,回弹效果直接用 ValueAnimator
,设置插值器为减速插值器来解决。看似简单,但是因为是仿物理效果,中间牵扯到从滚动到回弹的时候(Scroller
动画切换到ValueAnimator
动画)的速度衔接问题,要看上去从滚动到开始回弹至结束没有突兀,中间的特判边界处理是很麻烦的,还要牵扯到缩放,所以不考虑这种方案
既然是要模拟现实中的物理效果,为何不在每一帧根据当前的状态得到对用的加速度,然后去计算下一帧的状态位置,这样只要模拟现实中的物理加速度不就可以实现了吗,那些边界特判之类的就可以去见阎王了
方案确定完毕,接下来就是选定加速度的方程,要模拟弹簧的效果,拉力很简单,用胡克定律嘛!F = k * dx
,摩擦力呢?Ff = μ*FN
? 这里推荐一个更加好的方案,借鉴自 Rebound 库,这是 Facebook 的一个弹簧动画库,设定一个目的数值,它会根据当前的拉力,摩擦力,速度然后变化到目标值,加速度方程为
$$
\vec a=tension\cdot\vec {dx} - friction\cdot\vec v
$$
其中 tension
为弹性系数,friction
为摩擦力系数,为什么让摩擦力和速度成正比呢?如果摩擦力和速度成正比,那么就不存在静摩擦力,也就是不存在物体静止情况下拉力小于摩擦力的情况(因为速度为0的时候,阻力为0,除非拉力为0),物体肯定会向目标地点靠近,遏制了物体摩擦力过大而无法达到目的地情况
iOS
系统自带了模糊效果,但是Android
却没有,网上有很多实现方式,基本上都是用 faseBlur 算法,或者使用 RenderScript。
本文用的也是这两种方法,当 api < 17 的时候用 faseBlur 算法,当 api >= 17 的时候用 RenderScript。不同点是,网上基本都是通过获取一张和视图大小相同的 Bitmap
然后压缩、模糊。现在手机的屏幕分辨率都很高,如果使用这种方法的话,第一次创建的 Bitmap
将会占用很大一部分内存,很容易出现 OOM 现象。因为我们做模糊处理,所以只需要一张视图的缩略图即可,不需要一张高清的图片,有没有办法获取到 View
的缩略图呢?答案是有的。
一般获取 View
的 Bitmap
用的是 View
的 getDrawingCache
这个方法,此方法返回的是一张和 View
大小一模一样的位图。所以我们需要自己写获取位图的方法,参考 getDrawingCache
,里面是通过 view.draw(canvas)
在一张位图上画出视图的。这个就简单了,我们可以创建一个小型的 Bitmap
然后通过设置 Canvas
的缩放在这张小画布上画出视图的缩略图,这样子即省内存,也省时间(原先压缩需要时间)。
添加毛玻璃背景效果,模糊内存优化,详见 快速模糊效果
在iOS
上的chrome
中有侧滑返回上一个页面的功能,感觉蛮好用的,刚好Android
没有自带的侧滑返回效果,如果使用透明的Activity
的话比较浪费性能,所以打算在实现一个简单的DragBackActivity
,拖动的效果模仿iOS
上的Chrome
侧滑返回。
效果图如下:
使用方法:
继承自DragBackActivity
就可以有侧滑返回效果。
如果想要禁用,重写isDisableDrag()
函数返回true
。
定制返回动画的色彩:
1 | //图标色,变化前 |
粗略解析:
因为需要实现的目的是继承自这个DragBackActivity
就可以实现拖动返回的效果,因为是靠近边缘的侧滑返回,所以要用到手势处理,需要获取到当前Activity
的根视图,手势的处理是要放在视图层处理的。
每个Activity
都有一个id
为android.R.id.content
的根视图,setContentView
所设置的View
就是该根视图的子View
,本项目就是根据这个特性来移动整个Acivity
的。
知道怎么移动Activity
了,接下来就只剩拦截手势和绘制返回动画了。Android
的事件传递是根据整棵视图树来传递的,所以视图越靠近树的根就越先收到触摸事件。所以我需要在android.R.id.content
上或者同级的地方添加自定义的View
,这时候就需要Window
出场了,window
有一个方法是获取到当前窗口的根视图:
1 | //此函数返回的是view 需要强转成view group |
获取到窗口的根视图之后就可以往上面添加自定义视图了,手势拦截处理写在自定义视图中。废话不多说下面来看源码。
此文章紧接事件分发机制源码解析(一),建议顺序翻阅。
EventDispatcher
中有表示当前分发的事件数的私有成员变量 _inDispatch
,它是一个 int
类型的数据,用于表示当前正有多少事件正在分发。既然是表示事件正在分发的数量,可定有 ++, – 的操作,在DispatchEvent
中会有+1和-1的操作,但是藏得比较深,怎么个深法,看下述源码。
1 | void EventDispatcher::dispatchEvent(Event* event) |
我去,这啥都都没有啊???发现这里面只有使用 _inDispatch
创建了一个变量,并没有++,- -操作。其实 DispatchGuard
这个类的构造函数的参数是一个 int
类型的引用,构造时候对其+1,析构函数的时候会-1操作。用法很妙,借助了局部变量是在栈里面这个特性,当方法DispatchEvent
结束的时候,局部变量guard会析构,此时会-1操作。下面是 DispatchGuard
的源码。
1 | class DispatchGuard |
Cocos
的事件分发机制,怎么说呢,总感觉有些乱,借此整理一下。先看看与事件分发相关的类。
Event 相关类, 分发事件的中的事件:
Event
(基类),EventCustom
(自定义事件),EventTouch
(触摸事件),EventMouse
(鼠标事件),EventKeyboard
(键盘事件),EventFocus
(控件获取焦点事件),EventAcceleration
(加速计事件)
事件监听器:
EventListener
,EventListenerCustom
,EventListenerFocus
,EventListenerMouse
,EventListenerTouch
,EventListenerKayboard
,EventListenerAcceleration
事件ID
ListenerID
(事件的区分标志,其实就是std::string
)
事件分发器:
EventDispatcher
(事件分发机制逻辑集合体)
创建事件就简单的new
一个Event
的子类即可。
事件监听器,也就是说EventListener
。添加事件监听器有三个方法,都在EventDispatch
中,分别是:
1 | /** Adds a event listener for a specified event with the priority of scene graph. |
事件分发的时候主要有两种优先级。
第一种是:ScenePriority
第二种是:FixedPriority
仔细跳到addEventListenerWithSceneGraphPriority
函数去看看会发现:
1 | void EventDispatcher::addEventListenerWithSceneGraphPriority(EventListener* listener, Node* node) |