ScratchView:一步步打造万能的 Android 刮奖效果控件

我身边有一部分开发的小伙伴,存在着这样一种习惯。某一天,突然看到某一款 App 上有个很漂亮的自定义控件(动画)效果,就会绞尽脑子想办法去自己实现一发。当然,我自己也是属于这类型的骚年,看到某种效果就会手痒难耐琢磨着实现套路。个人觉得这是一种需求驱动进步的方法,当你绞尽脑子去实现自己想要的效果时,你就会发现你对 Android 自定义控件(动画)的知识体系认识越深,久而久之,自己也能轻松的造出各种控件(动画)效果。要是哪天,产品童鞋拿着个原型(或者对着某款 App )跟你讲:“XXXX,你看这个效果我们能不能实现?”,然后你瞥了一眼,胸有成竹丢回一句:“开玩笑,还有我实现不了的效果?”。想想心里是不是有点小激动?好了,差不多要说回正题了,这是我第一篇关于自定义控件的文章,以也会陆续穿插更新此类型的文章,希望大家能够喜欢。(偷偷剧透下,我下篇文章是关于性能优化的干货。当然我自己觉得很干货,希望到时候发出来不要打脸,哈哈哈!)

实现效果

说了这么多,还是先给大家看看最终的实现效果先

上面只是基本实现效果的一部分,你会看到下方还有很多其他控件,它们是用来干嘛的,接下来即将为你揭晓一切。

基本实现

日常生活中,我们对刮奖效果想必不会陌生,其原理就是通过在原有图案和文字上添加刮层来实现的。如果我们想看到刮层后面藏的图案和文字是什么,势必要通过刮开刮层才行。知道了这样的套路,就可以开始整理一下编码实现思路,然后愉快开干。

我一开始的实现思路是想通过重写 ImageView 和 TextView ,然后在分别用代码在图像和文字上添加图层,这样的话就能实现出效果了。然而回头一想,不对,这种实现存在的局限性比较大。如果照这种思路实现,那么刮层下面只能存在图片或者文字,如果产品经理要求同时存在图片和文字呢?要求存在两张图片呢?要求同时存在图片和文字,且文字放在图片的上(下、左、右)呢?…我们都知道,世界上最善变的除了妹纸的心,就是产品经理和他们的需求了。于是,便想出另外一种实现思路,直接继承 View 来实现一个刮层,让这个刮层和图片以及文字不产生任何依赖,再结合 FrameLayout 将刮层放置最上一层,刮层之下你想放多少图片文字,图片文字要怎么布局摆放都行。到此,思路明确,可以愉快的开始编码了。

继续阅读全文 »

知乎和简书的Android客户端夜间模式实现方式

说到夜间模式,在网上看到很多童鞋都说用什么什么框架来实现这个功能,然后仔细去看一下各个推荐的框架,发现其实都是动态换肤的,动态换肤可比夜间模式要复杂多了,未免大材小用了。说实话,我一直没用什么好思路,虽然网上有童鞋提供了一种思路是通过 setTheme 然后再 recreate Activity 的方式,但是这样带来的问题是非常多的,看起来就相当不科学(为什么不科学,后文会说)。于是,直接想到了去逆向分析那些夜间模式做得好的应用的源代码,学习他们的实现套路。所以,本文的实现思路来自于编写这些应用的夜间模式功能的童鞋,先在这里向他们表示感谢。我的手机里面使用高频的应用不少,其中简书和知乎是属于夜间模式做得相当 nice 的。先给两个效果图大家对比感受下

简书 知乎

如果大家仔细观察,肯定会发现,知乎的切换效果更漂亮些,因为它有一个渐变的效果。那么它们的夜间模式到底是如何实现的呢?别急接着往下看,你也可以。

实现套路

这里先展示一下我的实现效果吧

简书实现效果 知乎实现效果

此处分为两个部分,一部分是 xml 文件中要干的活,一部分是 Java 代码要实现的活,先说 xml 吧。

XML 配置

首先,先写一套UI界面出来,上方左边是两个 TextView,右边是两个 CheckBox,下方是一个 RecyclerView ,实现很简单,这里我不贴代码了。

接着,在 styles 文件中添加两个 Theme,一个是日间主题,一个是夜间主题。它们的属性都是一样的,唯一区别在于颜色效果不同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!--白天主题-->
<style name="DayTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="clockBackground">@android:color/white</item>
<item name="clockTextColor">@android:color/black</item>
</style>
<!--夜间主题-->
<style name="NightTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="colorPrimary">@color/color3F3F3F</item>
<item name="colorPrimaryDark">@color/color3A3A3A</item>
<item name="colorAccent">@color/color868686</item>
<item name="clockBackground">@color/color3F3F3F</item>
<item name="clockTextColor">@color/color8A9599</item>
</style>

需要注意的是,上面的 clockTextColor 和 clockBackground 是我自定义的 color 类型属性

1
2
3
4
5
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="clockBackground" format="color" />
<attr name="clockTextColor" format="color" />
</resources>

然后再到所有需要实现夜间模式功能的 xml 布局文件中,加入类似下面设置,比如我在 RecyclerView 的 Item 布局文件中做了如下设置

稍稍解释下其作用,如 TextView 里的 android:textColor=”?attr/clockTextColor” 是让其字体颜色跟随所设置的 Theme。到这里,xml 需要做的配置全部完成,接下来是 Java 代码实现了。

继续阅读全文 »

RecyclerView 和 ListView 使用对比分析

本文主要记录 RecyclerView 和 ListView 的使用对比,文章主要包括以下几点的内容:

  • RecyclerView 和 ListView 布局效果的对比
  • RecyclerView 和 ListView 一些常用的功能 和 API 的对比
  • RecyclerView 和 ListView 在 Android L 引入嵌套滚动机制之后的对比

有一点需要强调下,文中所有的效果在真机上都是很流畅的,因为录制 GIF 图很容易掉帧,所以特地放慢了操作,千万不要误会成卡顿了啊!

布局效果对比

作为一枚控件,要引起开发者使用的欲望自然先是从显示效果看起(看脸的世界),ListView 大家对效果已经很熟悉了,这里直接跳过,而作为 RecyclerView,它能带给效果要比 ListView 强大得多,如下图

Android 默认提供的 RecyclerView 就能支持 线性布局网格布局瀑布流布局 三种(这里我们暂且不提代码细节,后文再说),而且同时还能够控制横向还是纵向滚动。怎样,从效果上足以碾压 ListView 有木有。

  • 横向滚动的ListView开源控件是不是可以不用再找了?对,你没看错!
  • 瀑布流效果的开源控件是不是可以不用再找了?对,你没看错!
  • 连横向滚动的GridView都不用找了!对,你没看错!

到此,展示效果上的差距一目了然。

API 使用对比

当然,一个控件我们不能完全只看效果,关键还是要看实用性,看看有没有方便我们调用的 API提高我们的开发效率。所以,接下来我们就从各个方面来看看 RecyclerView 和 ListView 在提供的API调用上的一些实践比较。

基础使用

ListView 的基础使用大家再熟悉不过,其使用的关键点主要如下:

  • 继承重写 BaseAdapter 类
  • 自定义 ViewHolder 和 convertView 一起完成复用优化工作

由于 ListView 已经老生常谈,所以此处就不去写示例代码了。 RecyclerView 基础使用关键点同样有两点:

  • 继承重写 RecyclerView.Adapter 和 RecyclerView.ViewHolder
  • 设置布局管理器,控制布局效果

示例代码大致如下:

继续阅读全文 »

深入理解 Android 中的 Matrix

在 Android 开发中,矩阵是一个功能强大并且应用广泛的神器,例如:用它来制作动画效果、改变图片大小、给图片加各类滤镜等。对于矩阵,Android 官方 SDK 为我们提供了一个强大的类 Matrix (还有 ColorMatrix )是一直困扰着我的问题,虽然大致能够调用相应的 API ,但却一直 get 不到其内在的梗。但是出来混总是别想着蒙混过关的,所以最近重新操起一年毕业的线性代数,再本着小事问老婆,大事问Google的心态,终于把多年不解的问题给破了。出于好记性不如烂笔头的原因,便有了本文。在此先感谢下面两篇令我茅舍顿开的文章:

读完本文,相信你能够搞明白以下三个问题:

  • 为什么 Matrix 是个 3 X 3 的矩阵
  • Matrix 这个 3 X 3 的矩阵每个元素的作用
  • Matrix 的 setXXX、preXXX、postXXX API 方法的工作原理

Matrix 的结构

Matrix 是 Android SDK 提供的一个矩阵类,它代表一个 3 X 3 的矩阵(不懂矩阵为何物的童鞋就要自行 Google 了)。 Matrix 提供了让我们获得 Matrix 值的 API —— getValues

利用此 API 传入一个长度为 9 的 float 数组,即可获得矩阵中每个元素的值。那么这 9 个浮点数的作用和意义是什么呢,从 Android 官方文档上看,它为这个数组中的每一个元素都定义了一个下标常量

这个 9 个常量取值分别是 0 - 8

如果我们将这个 float 排成直观的矩阵格式,那它将是下面这样子的

实际上我们平常利用 Matrix 来进行 Translate(平移)、Scale(缩放)、Rotate(旋转)的操作,就是在操作着这个矩阵中元素的数值来达到我们想要的效果。但是现在问题来了,上面提到的平移、缩放、旋转操作中,旋转和缩放可以用乘法表示,而平移就只能用加法表示,而且 Matrix 是一个 3 X 3 的矩阵,实际上表示这些操作 2 X 2 的矩阵足矣!

继续阅读全文 »

那些值得你试试的Android竞品分析工具

本文整理了一些自己在开发过程中经常会用到的竞品分析工具,这些工具可以帮助分析竞品。让我们得以了解竞品相应的一些技术信息,例如:代码质量、某种业务的实现方式、用了什么第三方库等。除此之外,也有一些高端玩家会玩起 HOOK ,更有甚者是通过修改代码然后进行二次打包。当然这些损害开发者利益的事情,是不值得提倡的。但如果只是出于学习的目的,我是十分建议多折腾的。

提前声明:

  • 本文只对工具做简要功能介绍,要求面面俱到讲解每个工具使用,本人表示能力有限啊;
  • 下文所介绍的工具,都会附上这些工具的官方地址以及相应的使用教程链接(如果有);
  • 有童鞋对下文提到的工具已经用得出神入化,欢迎写成文章,可以的话,也欢迎给个链接让我补充进本文,顺带学习一下;
  • 本文所有提到的工具只做分析学习使用,请不要拿去做损害他人利益的事情

Apk 内部结构

为了方便介绍工具,需要先简单科普一下 Apk 的内部结构,已经很熟悉的童鞋可以忽略此章节。需要注意的是,这里介绍的 Apk 结构并不包含加固的情况,虽然很多厂家推出了加固服务用于对抗反编译,但是加固也有诸多的问题存在,另外基本上分析的大厂应用都没有发现有加固的,可能也是考虑到加固后安装包存在的诸多问题吧。

直接使用 Android Studio 创建一个 HelloWorld 的 Moudle,然后打个 release 的 Apk 安装包,并修改后缀 apk 为 zip 后进行解压,可以看到下面一个标准的结构:

  • META-INF: 存放签名文件签名信息的目录,用于系统签名校验
  • res: 存放资源文件的目录,包含项目中的 xml 和 图片资源等
  • AndroidManifest.xml: Android项目中的配置文件
  • classes.dex: 由Java产生的字节码文件打包生成为虚拟机可以解读的字节码文件,所有的源码都在其中
  • resources.arsc: 资源文件的ID索引表,如:layout、drawable、mipmap都会在R文件生成相应的ID资源
  • 其他目录:开发者自行添加的目录,如:存放资源的 asserts 、存放依赖包的 lib 目录等

上面介绍完了一个最基本的 Apk 解压后的目录结构,下面直接拿微信作为示例,看看大厂应用的结构是怎样的:

继续阅读全文 »

你需要知道的Android拍照适配方案

说起调用系统相机来拍照的功能,大家肯定不陌生,现在所有应用都具备这个功能。例如最基本的,用户拍照上传头像。Android开发的孩纸都知道,碎片化给拍照这个功能的实现带来挺多头疼的问题。所以,我决定写写一些网上不多见但又经常听到童鞋们吐槽的问题。

拍照功能实现

Android 程序上实现拍照功能的方式分为两种:第一种是利用相机的 API 来自定义相机,第二种是利用 Intent 调用系统指定的相机拍照。下面讲的内容都是针对第二种实现方式的适配。

通常情况下,我们调用拍照的业务场景是如下面这样的:

  1. A 界面,点击按钮调用相机拍照;
  2. A 界面得到拍完照片,跳转到 B 界面进行预览;
  3. B 界面有个按钮,点击后触发某个业务流程来处理这张照片;

实现的大体流程代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//1、调用相机
File mPhotoFile = new File(folder,filename);
Intent captureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
Uri fileUri = Uri.fromFile(mPhotoFile);
captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri);
mActivity.startActivityForResult(captureIntent, CAPTURE_PHOTO_REQUEST_CODE);
//2、拿到照片
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == CapturePhotoHelper.CAPTURE_PHOTO_REQUEST_CODE && resultCode == RESULT_OK) {
File photoFile = mCapturePhotoHelper.getPhoto();//获取拍完的照片
if (photoFile != null) {
PhotoPreviewActivity.preview(this, photoFile);//跳转到预览界面
}
finish();
} else {
super.onActivityResult(requestCode, resultCode, data);
}
}
//3、各种各样处理这张图片的业务代码

到这里基本科普完了如何调用系统相机拍照,相信这些网上一搜一大把的代码,很多童鞋都能看懂。

有没有相机可用?

前面讲到我们是调用系统指定的相机app来拍照,那么系统是否存在可以被我们调用的app呢?这个我们不敢确定,毕竟 Android 奇葩问题多,还真有遇到过这种极端的情况导致闪退的。虽然很极端,但作为客户端人员还是要进行处理,方式有二:

继续阅读全文 »

关于 Android 进程保活,你所需要知道的一切

早前,我在知乎上回答了这样一个问题:怎么让 Android 程序一直后台运行,像 QQ 一样不被杀死?。关于 Android 平台的进程保活这一块,想必是所有 Android 开发者瞩目的内容之一。你到网上搜 Android 进程保活,可以搜出各种各样神乎其技的做法,绝大多数都是极其不靠谱。前段时间,Github还出现了一个很火的“黑科技”进程保活库,声称可以做到进程永生不死

怀着学习和膜拜的心情进去Github围观,结果发现很多人提了 Issue 说各种各样的机子无法成功保活。

看到这里,我瞬间就放心了。坦白的讲,我是真心不希望有这种黑科技存在的,它只会滋生更多的流氓应用,拖垮我大 Android 平台的流畅性。

扯了这么多,接下来就直接进入本文的正题,谈谈关于进程保活的知识。提前声明以下四点

  • 本文是本人开发 Android 至今综合各方资料所得
  • 不以节能来维持进程保活的手段,都是耍流氓
  • 本文不是教你做永生不死的进程,如果指望实现进程永生不死,请忽略本文
  • 本文有错误的地方,欢迎留下评论互相探讨(拍砖请轻拍)

保活手段

当前业界的Android进程保活手段主要分为 黑、白、灰 三种,其大致的实现思路如下:

  • 黑色保活:不同的app进程,用广播相互唤醒(包括利用系统提供的广播进行唤醒)
  • 白色保活:启动前台Service
  • 灰色保活:利用系统的漏洞启动前台Service

继续阅读全文 »

我的 Android 开发实战经验总结

以前一直想写一篇总结 Android 开发经验的文章,估计当时的我还达不到某种水平,所以思路跟不上,下笔又捉襟见肘。近日,思路较为明朗,于是重新操起键盘开始码字一番。先声明一下哈,本人不是大厂的程序猿。去年毕业前,就一直在当前创业小团队从事自己热爱的打码事业至今。下面总结是建立在我当前的技术水平和认知上写的,如有不同看法欢迎留下评论互相交流。

1.理解抽象,封装变化

目前 Android 平台上绝大部分开发都是用着 Java ,而跟 Java 这样一门面向对象的语言打交道,不免要触碰到 抽象封装 的概念。我身边接触过的一些开发者,有一部分还对这些概念停留在写一个抽象类、接口、或者一个方法(或抽象方法)。至于为什么,我不大清楚是他们表达不出来,还是不理解。下面我也不高谈阔论,直接举例子来解释我所理解的抽象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//Activity 间使用 Intent 传递数据的两种写法 下面均是伪代码形式,请忽略一些细节
//写法一
//SrcActivity 传递数据给 DestActivity
Intent intent = new Intent(this,DestActivity.class);
intent.putExtra("param", "clock");
SrcActivity.startActivity(intent);
//DestActivity 获取 SrcActivity 传递过来的数据
String param = getIntent.getStringExtra("param");
//写法二
//SrcActivity 传递数据给 DestActivity
Intent intent = new Intent(this,DestActivity.class);
intent.putExtra(DestActivity.EXTRA_PARAM, "clock");
SrcActivity.startActivity(intent);
//DestActivity 获取 SrcActivity 传递过来的数据
public final static String EXTRA_PARAM = "param";
String param = getIntent.getStringExtra(EXTRA_PARAM);

写法一,存在的问题是,如果 SrcActivity 和 DestActivity 哪个把 “param” 打错成 “para” 或者 “paran” ,传递的数据都无法成功接收到。而写法二则不会出现此类问题,因为两个 Activity 之间传递数据只需要知道 EXTRA_PARAM 变量即可,至于 EXTRA_PARAM 变量到底是 “param” 、 “para” 、”paran” 这一点并不需要关心,这就是一种对可能发生变化的地方进行抽象封装的体现,它所带来的好处就是降低手抖出错的概率,同时方便我们进行修改。

基于抽象和封装,Java 本身很多 API 在设计上就有这样的体现,如 Collections 中的很多排序方法:

这些方法都是基于 List 这个抽象的列表接口进行排序,至于这是一个用什么样的数据结构实现 List(ArrayList 还是 LinkedList),排序方法本身并不关心。看,是不是体现了 JDK 的设计人员的一种抽象编程的思维,因为 List 的具体实现可能有千万种,如果每一类 List 都要写一套排序方法,估计要哭瞎了。

小结:把容易出现变化的部分进行抽象,就是对变化的一种封装。

继续阅读全文 »

Android开发:最详细的 NavigationDrawer 开发实践总结

继前面写的两篇文章之后(有问题欢迎反馈哦):

  1. Android Translucent System Bar 使用指南
  2. Android Toolbar 使用指南

接着来写写Android系统UI新特性,本文是我对最近开发过程中应用 NavigationDrawer 特性的详细总结。本文涉及到的所有代码实现细节,会在文末附上源码地址。有问题欢迎在下方留言讨论

NavigationDrawer 是 Google 在 Material Design 中推出的一种侧滑导航栏设计风格。说起来可能很抽象,我们直接来看看 网易云音乐 的侧滑导航栏效果

Google 为了支持这样的导航效果,推出一个新控件 —— DrawerLayout 。而在 DrawerLayout 没诞生之前,需求中需要实现侧滑导航效果时,我们必然会选择去选择一些成熟的第三方开源库(如最有名的 SlidingMenu)来完成开发 。效果上,普遍都像 手Q 那样:

在对比过 DrawerLayoutSlidingMenu 的实现效果后,基于以下的几点,我认为完全可以在开发中使用 DrawerLayout 取代以前的 SlidingMenu

  1. 从动画效果上看,你会发现两者仅仅是在移动的效果上有些差别外,其他地方并没有太大的差异
  2. 在交互效果上,我认为这两者都差不多的,就算你把 网易云音乐 的效果套到了 手Q 上,也不会影响到用户的交互
  3. DrawerLayout 用起来比 SlidingMenu 更简单,代码量更少(往下看就知道了)
  4. DrawerLayout 是向下兼容的,所以不会存在低版本兼容性问题
  5. Google 亲儿子,没理由不支持啊!!!!!!

到这里,要是你还没有引入 DrawerLayout 开发的冲动,请继续听我为你好好安利一番。

初识 DrawerLayout

一般情况下,在 DrawerLayout 布局下只会存在两个子布局,一个 内容布局 和 一个 侧滑菜单布局,这两个布局关键在于 android:layout_gravity 属性的设置。如果你想把其中一个子布局设置成为左侧滑菜单,只需要设置 android:layout_gravity=”start” 即可(也可以是 left,右侧滑则为 end 或 right ),而没有设置的布局则自然成为 内容布局 。那么,使用 DrawerLayout 到底有多简单呢,我们先直接看看下面的布局文件 layout/activity_simple_drawer.xml

继续阅读全文 »

Android Toolbar 使用指南

过年前发了一篇介绍 Translucent System Bar 特性的文章 Android Translucent System Bar 使用指南,收到很多开发者的关注和反馈。今天开始写第二篇,全面的介绍一下 Toolbar 的使用。说起 Toolbar ,可能有很多开发的童鞋还比较陌生,没关系,请接着往下看。

初识 Toolbar

Toolbar 是在 Android 5.0 开始推出的一个 Material Design 风格的导航控件 ,Google 非常推荐大家使用 Toolbar 来作为Android客户端的导航栏,以此来取代之前的 Actionbar 。与 Actionbar 相比,Toolbar 明显要灵活的多。它不像 Actionbar 一样,一定要固定在Activity的顶部,而是可以放到界面的任意位置。除此之外,在设计 Toolbar 的时候,Google也留给了开发者很多可定制修改的余地,这些可定制修改的属性在API文档中都有详细介绍,如:

  • 设置导航栏图标;
  • 设置App的logo;
  • 支持设置标题和子标题;
  • 支持添加一个或多个的自定义控件;
  • 支持Action Menu;

总之,与 Actionbar 相比,Toolbar 让我感受到Google满满的诚意。怎样?是否已经对 Toolbar 有大概的了解,跃跃欲试的感觉出来了有木有?接下来,我们就一步一步的来看如何使用 Toolbar(其实是我使用 Toolbar 踩坑填坑的血泪史,你们接下去看,我先擦个眼泪…. )。

开始使用 Toolbar

前面提到 Toolbar 是在 Android 5.0 才开始加上的,Google 为了将这一设计向下兼容,自然也少不了要推出兼容版的 Toolbar 。为此,我们需要在工程中引入 appcompat-v7 的兼容包,使用 android.support.v7.widget.Toolbar 进行开发。下面看一下代码结构,同样把重点部分已经红圈圈出:

  • ToolbarActivity 包含了 Toolbar 的一些基本使用, ZhiHuActivity 是在熟悉了 Toolbar 后对知乎主页面的一个高仿实现。

  • layout和menu文件夹分别是上面提到的两个Activity的布局文件 和 actionmenu 菜单文件。

  • values、values-v19、values-v21 中包含了一些自定义的 theme,后面用到的时候会顺带讲解。

我们先来看一下 ToolbarActivity 的运行效果

按照效果图,从左到右分别是我们前面提及到的 导航栏图标App的logo标题和子标题自定义控件、以及 ActionMenu 。接着,我们来看下布局文件和代码实现。

首先,在布局文件 activity_tool_bar.xml 中添加进我们需要的 Toolbar 控件

继续阅读全文 »