前言

一个好的开发规范可以让团队中的人对他人的代码更熟悉,新人也可以更好的了解产品的业务逻辑。开发规范并不是一个死的一成不变的,每个团队可能都有自己的开发规范,只要是适合团队的开发规范就是最好的开发规范。
编码规范对于程序员而言尤为重要,有以下几个原因:

  • 一个软件的生命周期中,80%的花费在于维护;
  • 几乎没有任何一个软件,在其整个生命周期中,均由最初的开发人员来维护;
  • 编码规范可以改善软件的可读性,可以让程序员尽快而彻底地理解新的代码;
  • 如果你将源码作为产品发布,就需要确任它是否被很好的打包并且清晰无误,一如你已构建的其它任何产品。

经验总结:编码不规范,同行泪两行

一、常规规范

1.1 使用 0px 代替 0dp,这样就可以在获取时避免系统进行换算,提升代码的执行效率。

1.2 字符串比较,应该用 “xxx”.equals(object),而不应该用 object.equals(“xxx”),因为 object 对象可能为空,我们应该把不为空的条件放置在表达式的前面。

1.3 尽量采用 switch case 来判断,如果不能实现则再考虑用 if else,因为在多条件下使用 switch case 语句判断会更加简洁。

1.4 不推荐用 layout_marginLeft,而应该用 layout_marginStart;不推荐用 layout_marginRight,而应该用 layout_marginEnd,原因有两个,一个是适配 Android 4.4 反方向特性(可在开发者选项中开启),第二个是 XML 布局中使用 layout_marginLeft 和 layout_marginRight 会有代码警告,padding 属性也是同理,这里不再赘述。

1.5 如果在 layout_marginStart 和 layout_marginEnd 的值相同的情况下,请替换用 layout_marginHorizontal,而 layout_marginTop 和 layout_marginBottom 也同理,请替换使用 layout_marginVertical,能用一句代码能做的事不要写两句,padding 属性也是同理,这里不再赘述。

1.6 过期 和 高版本 的 API 一定要做对应的兼容(包含 Java 代码和 XML 属性),如果不需要兼容处理的,需要对警告进行抑制。

1.7 在能满足需求的情况下,尽量用 invisible 来代替 gone,因为 gone 会触发当前整个 View 树进行重新测量和绘制。

1.8 api 和 implementation,在能满足使用的情况下,优先选用 implementation,因为这样可以减少一些编译时间。

1.9 ListView 和 RecyclerView 都能实现需求的前提下,优先选用 RecyclerView,具体原因不解释,大家应该都懂。

1.10 ScrollView 和 NestedScrollView 都能实现需求的前提下,优先选用 NestedScrollView,是因为 NestedScrollView 和 RecyclerView 支持相互嵌套,而 ScrollView 是不支持嵌套滚动的。

1.11 不能在项目中创建副本文件,例如创建 HomeActivity2.java、home_activity_v2.xml 类似的副本文件,因为这样不仅会增加项目的维护难度,同时对编译速度也会造成一定的影响,正确的做法应该是在原有的文件基础上面修改,如果出现需求变更的情况,请直接使用 Git 或者 SVN 进行版本回退。

1.12 如果一个类不需要被继承,请直接用 final 进行修饰,如果一个字段在类初始化过程中已经赋值并且没有地方进行二次赋值,也应当用 final 修饰,如果一个字段不需要被外部访问,那么需要用 private 进行修饰。

1.13 每个小组成员应当安装阿里巴巴代码约束插件,并及时地对插件所提示的代码警告进行处理或者抑制警告。

1.14 用图标应该放在 mipmap 目录下,其他图片资源应当放到 drawable 目录下,具体原因可以看谷歌官方文档对这两个文件夹给出的介绍:
【Android -- 规范】Android 编码规范_ide

二、后台接口规范

2.1 后台返回的 id 值,不要使用 int 或者 long 类型来接收,而应该用 string 类型来接收,因为我们不需要对这个 id 值进行运算,所以我们不需要关心它是什么类型的。

2.2 后台返回的金额数值应该使用 String 来接收,而不能用浮点数来接收,因为 float 或者 double 在数值比较大的时候会容易丢失精度,并且还需要自己手动转换出想要保留的小数位,最好的方式是后台返回什么前端就展示什么,而到了运算的时候,则应该用 BigDecimal 类来进行转换和计算,当然金额在前端一般展示居多,运算的情况还算是比较少的。

2.3 我们在定义后台返回的 Bean 类时,不应当将一些我们没有使用到的字段添加到代码中,因为这样会消耗性能,因为 Gson 是通过反射将后台字段赋值到 Java 字段中,所以我们应当避免一些不必要的字段解析,另外臃余的字段也会给我们排查问题造成一定的阻碍。

2.4 如果后台给定的字段名不符合代码命名的时候,例如当遇到 student_name 这种命名时,我们应当使用 Gson 框架中的 @SerializedName 注解进行重命名。

2.5 请求的接口参数和返回字段必须要写上注释,除此之外还应该备注对应的后台接口文档地址,以便我们后续能够更好地进行维护和迭代。

2.6 后台返回的 Bean 类字段不能直接访问,而应该通过生成 Get 方法,然后使用这个 Get 方法来访问字段。

2.7 接口请求成功的提示可以不显示,但请求失败的提示需要显示给到用户,否则会加大排查问题的难度,也极有可能会把问题掩盖掉,从而导致问题遗留到线上去。

三、变量命名规范

3.1 严禁使用中文或者中文拼音进行重命名。

3.2 使用驼峰式命名风格(单词最好控制在三个以内)。

3.3 局部变量或者公开的成员变量应该以作用来命名,例如:

public String name;
public TextView nameView;
public FrameLayout nameLayout;

// 命名规范附带技巧(当布局中同个类型的控件只有一个的时候,也可以这样命名)
public TextView textView;
public RecyclerView recyclerView;

3.4 非公开的成员变量必须以小 m 开头,例如:

private String mName;
private TextView mNameView;
private FrameLayout mNameLayout;

// 命名规范附带技巧(当布局中同个类型的控件只有一个的时候,也可以这样命名)
private TextView mTextView;
private RecyclerView mRecyclerView;

3.5 布尔值命名规范,无论是局部变量还是成员变量,都不应该携带 is,例如:

// 不规范写法示例
private boolean mIsDebug;
boolean isDebug;
// 规范写法示例
private boolean mDebug;
boolean debug;

3.6 静态变量则用小 s 开头,例如:

static Handler sHandler;

3.7 常量则需要用大写,并且用下划线代替驼峰,例如:

static final String REQUEST_INSTALL_PACKAGES;

四、包名命名规范

4.1 不允许包名中携带英文大写

4.2 包名应该以简洁的方式命名

4.3 包名要按照模块或者作用来划分

4.4 请不要在某一包名下放置一些无关的类

五、方法命名规范

5.1 initXX:初始化相关方法,使用 init 为前缀标识,如初始化布局 initView

5.2 isXX:方法返回值为 boolean 型的请使用 is 或 check 为前缀标识

5.3 getXX:返回某个值的方法,使用 get 为前缀标识,例如 getName

5.4 setXX:设置某个属性值,使用 set 为前缀标识,例如 setName

5.5 handleXX/processXX:对数据进行处理的方法,例如 handleMessage

5.6 displayXX/showXX:弹出提示框和提示信息,例如 showDialog

5.7 updateXX:更新某个东西,例如 updateData

5.8 saveXX:保存某个东西,例如 saveData

5.9 resetXX:重置某个东西,例如 resetData

5.10 clearXX:清除某个东西,例如 clearData

5.11 removeXX:移除数据或者视图等,例如 removeView

5.12 drawXX:绘制数据或效果相关的,使用 draw 前缀标识,例如 drawText

六、 类文件命名规范

6.1 业务模块:请以 模块 + 类型 来命名,例如:

HomeActivity.java
SettingFragment.java
HomeAdapter.java
AddressDialog.java

6.2 技术模块:请以类的 作用 来命名,例如:

CrashHandler.java
GridSpaceDecoration.java
PickerLayoutManager.java

七、 接口文件命名规范

7.1 如果是监听事件可以参考 View 的写法及命名:

public class View {
private View.OnClickListener mListener;

public void setOnClickListener(OnClickListener listener) {
mListener = listener;
}

public interface OnClickListener {

void onClick(View v);
}
}

7.2 如果是回调事件可以参考 Handler 的写法及命名:

public class Handler {
public interface Callback {

boolean handleMessage(Message msg);
}
}

至于接口写在内部还是外部,具体可以视实际情况而定,如果功能比较庞大,就可以考虑抽取成外部的,只作用在某个类上的,则就可以直接写成内部的。

八、 接口实现规范

8.1 一般情况下,我们会在类中这样实现接口,这样写的好处是,可以减少对象的创建,并且代码也比较美观。

public class PasswordEditText extends EditText implements View.OnTouchListener, View.OnFocusChangeListener, TextWatcher {
public PasswordEditText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setOnTouchListener(this);
setOnFocusChangeListener(this);
addTextChangedListener(this);
}

@Override
public void onFocusChange(View view, boolean hasFocus) {
...
}

@Override
public boolean onTouch(View view, MotionEvent event) {
...
}

@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
...
}

@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
...
}

@Override
public void afterTextChanged(Editable s) {
...
}
}

8.2 但是有两个美中不足的地方,就是在实现的接口过多时,我们很难分辨是哪个方法是哪个接口的,这个时候可以使用注释的方式来解决这个问题,加上 @link 还可以帮助我们快速定位接口类在项目中所在的位置;另外一个是 implements 修饰符换行的问题,合理的换行会使代码更加简单直观。

public final class PasswordEditText extends EditText
implements View.OnTouchListener,
View.OnFocusChangeListener, TextWatcher {

public PasswordEditText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setOnTouchListener(this);
setOnFocusChangeListener(this);
addTextChangedListener(this);
}

/**
* {@link OnFocusChangeListener}
*/

@Override
public void onFocusChange(View view, boolean hasFocus) {
...
}

/**
* {@link OnTouchListener}
*/

@Override
public boolean onTouch(View view, MotionEvent event) {
...
}

/**
* {@link TextWatcher}
*/

@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
...
}

@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
...
}

@Override
public void afterTextChanged(Editable s) {
...
}
}

九、 异常捕获规范

9.1 请不要使用此方式捕获异常,因为这种方式会把问题给隐藏掉,从而会加大后续排查问题的难度。

try {
Xxx.xxx();
} catch (Exception e) {
}

9.2 如需捕获异常,请用以下方式进行捕获,列出具体的异常类型,并在代码中输出对应的日志。

// 捕获这个异常,避免程序崩溃
try {
// 目前发现在 Android 7.1 主线程被阻塞之后弹吐司会导致崩溃,可使用 Thread.sleep(5000) 进行复现
// 查看源码得知 Google 已经在 Android 8.0 已经修复了此问题
// 主线程阻塞之后 Toast 也会被阻塞,Toast 因为显示超时导致 Window Token 失效
mHandler.handleMessage(msg);
} catch (WindowManager.BadTokenException | IllegalStateException e) {
// android.view.WindowManager$BadTokenException:Unable to add window -- token android.os.BinderProxy is not valid; is your activity running?
// java.lang.IllegalStateException:View android.widget.TextView has already been added to the window manager.
e.printStackTrace();
}

9.3 如果这个异常不是通过方法 throws 关键字抛出,则需要在 try 块中说明崩溃的缘由,并注明抛出的异常信息。

9.4 还有一个问题,有异常就一定要 try catch ?,这种想法其实是错的,例如我们项目用 Glide 加载图片会抛出以下异常:

Caused by: java.lang.IllegalArgumentException: You cannot start a load for a destroyed activity
at com.bumptech.glide.manager.RequestManagerRetriever.assertNotDestroyed(RequestManagerRetriever.java:348)
at com.bumptech.glide.manager.RequestManagerRetriever.get(RequestManagerRetriever.java:148)
at com.bumptech.glide.Glide.with(Glide.java:826)

这是因为 Activity 的销毁了而去加载图片导致的(场景:异步执行图片加载),大多人的解决方式可能是:

try {
// Activity 销毁后执行加载图片会触发 crash
Glide.with(this)
.load(url)
.into(mImageView);
} catch (IllegalArgumentException e) {
// java.lang.IllegalArgumentException: You cannot start a load for a destroyed activity
e.printStackTrace();
}

虽然这种方式可以解决 crash 的问题,但是显得不够严谨,Glide 抛异常给外层,其实无非就想告诉调用者,调用的时机错了,正确的处理方式不是直接捕获这个异常,而是应该在外层做好逻辑判断,避免会进入出现 crash 的代码,正确的处理示例如下:

if (isFinishing() || isDestroyed()) {
// Glide:You cannot start a load for a destroyed activity
return;
}
Glide.with(this)
.load(url)
.into(mImageView);

所以尽量不要通过捕获的方式来处理异常,除非外层真的判断不了,否则应该通过一些逻辑判断来避免进入一些会 crash 的代码。

十、Activity 跳转约定

10.1 应当将 Intent 中的 key 常量保存到一个管理类中,又或者定义在目标的 Activity 中

public class IntentKey {

/** id */
public static final String ID = "id";
/** token */
public static final String TOKEN = "token";
/** 订单 */
public static final String ORDER = "order";
/** 余额 */
public static final String BALANCE = "balance";
/** 时间 */
public static final String TIME = "time";
/** URL */
public static final String URL = "url";
/** 路径 */
public static final String PATH = "path";
/** 其他 */
public static final String OTHER = "other";
}

10.2 如果跳转的 Activity 需要传递参数,应该在目标的 Activity 中定义静态的 start 又或者 newIntent 方法

public final class WebActivity extends Activity {

public static void start(Context context, String url) {
Intent intent = new Intent(context, WebActivity.class);
intent.putExtra(IntentKey.URL, url);
context.startActivity(intent);
}
}
public final class WebActivity extends Activity {

public static Intent newIntent(Context context, String url) {
Intent intent = new Intent(context, WebActivity.class);
intent.putExtra(IntentKey.URL, url);
return intent;
}
}

10.3 如果创建的 Fragment 需要传递参数,应该在目标的 Fragment 中定义静态的 newInstance 方法

public final class WebFragment extends Fragment {

public static WebFragment newInstance(String url) {
WebFragment fragment = new WebFragment();
Bundle bundle = new Bundle();
bundle.putString(IntentKey.URL, url);
fragment.setArguments(bundle);
return fragment;
}
}

10.4 如果跳转的 Activity 或者创建的 Fragment 不需要传任何参数,可以不需要定义这些静态方法

十一、第三方框架使用规范

11.1 集成一些第三方框架或者 SDK,必须注明作用和出处,以便出现问题时能够快速核查和反馈。

// 权限请求框架:https://github.com/getActivity/XXPermissions
implementation 'com.hjq:xxpermissions:9.8'

11.2 尽量不要选择功能两套相同的框架,应当引用最合适的一套框架进行开发。

11.3 使用第三方库必须要依赖指定的版本号,而不能使用 + 号来指定依赖库最新的版本号。

11.4 使用第三方开源库出现问题或者 Bug 时应及时通知到开源库的作者,如果没有及时回复就根据实际情况对问题进行修复。

11.5 尽量避免 Copy 第三方库的技术代码到项目中,特别是在放置到项目业务模块中,因为这样会增加项目的复杂度,从而降低可维护性。

11.6 如果出现问题不能找到开源库的作者,如果需要修改,应当将这些代码抽取到单独的 Module 中。

11.7 能用框架就用成熟框架,尽量不要自己编写或者修改框架,如果有需要,要对这块进行严格测试。

十二、多模块规范

12.1 模块命名规范:应该以简单明了的方式来命名

app 
base
widget
umeng
course
socket
live
shop

12.2 模块混淆配置:请不要使用 proguardFiles 语句,而是应该使用 consumerProguardFiles 语句,因为 consumerProguardFiles 语句会将混淆规则和资源代码一同合并到 aar 包中,这样做的好处在于:在项目编译时会将 aar 包中的混淆规则合并到主模块中。

android {

defaultConfig {
// 模块混淆配置
consumerProguardFiles 'proguard-xxx.pro'
}
}

12.3 资源前缀限制:我们应该在模块中加入此限制,这样我们在模块中添加资源时,编译器如果发现资源名称前缀不符合规范,则会出现代码警告。这样做的好处在于,以某一名称作为前缀,可以有效避免在编译时引发的一些资源合并冲突。

android {
// 资源前缀限制
resourcePrefix "xxx_"
}

12.4 框架版本管理:我们应该统一抽取框架的版本到 config.gradle 文件中:

ext {

android = [compileSdkVersion : 28,
minSdkVersion : 19,
targetSdkVersion : 28,
versionCode : 40102,
versionName : "4.1.2",
]
dependencies = [
"appcompat" : "androidx.appcompat:appcompat:1.2.0",
"material" : "com.google.android.material:material:1.2.0",
]
}

然后在每个模块下这样定义,这样做的好处是可以做到版本号的统一管理。

apply from : '../config.gradle'

android {
compileSdkVersion rootProject.ext.android["compileSdkVersion"]

defaultConfig {
minSdkVersion rootProject.ext.android["minSdkVersion"]
targetSdkVersion rootProject.ext.android["targetSdkVersion"]
}
}
dependencies {
implementation rootProject.ext.dependencies["appcompat"]
implementation rootProject.ext.dependencies["material"]
}

12.5 除此之外还有另外一种写法,我们可以把 config.gradle 修改成这样:

android {
compileSdkVersion 28

defaultConfig {
minSdkVersion 19
targetSdkVersion 28
versionName '4.1.2'
versionCode 40102
}
}

dependencies {
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.2.1'
}

然后在每个模块上添加一句引用即可,相比上一种写方法,这种方式更强大,因为它不仅可以配置版本号,还支持统一其他的配置项。

具体要用哪一种,可以根据实际情况而定,如果项目采用的是组件化,则可以考虑使用第一种方式,如果项目采用的是模块化,则可以考虑使用第二种方式。

十三、代码注释规范

13.1 类注释规范:author 是创建者(必填项)、time 是创建时间(必填项)、desc 是类的描述(必填项),doc 是文档地址(非必填),github 是开源地址(如果项目是开源的则必填,否则不填)

/**
* author : Android 轮子哥
* github : https://github.com/getActivity/XXPermissions
* time : 2018/06/15
* desc : 权限请求实体类
* doc : https://developer.android.google.cn/reference/android/Manifest.permission?hl=zh_cn
* https://developer.android.google.cn/guide/topics/permissions/overview?hl=zh-cn#normal-dangerous
*/
public class Permission {
...
}

13.2 方法注释规范:方法注释可根据实际情况而定

/**
* 设置请求的对象
*
* @param activity 当前 Activity,可以传入栈顶的 Activity
*/
public static XXPermissions with(FragmentActivity activity) {
return ....;
}

13.3 字段注释规范:根据字段的作用而定

/** 请求的权限组 */
private static final String REQUEST_PERMISSIONS = "request_permissions";

/** 权限回调对象 */
private OnPermissionCallback mCallBack;

13.4 变量注释规范(如果 API 是比较常见并且容易理解可以不用写,如果是复杂并且羞涩难懂则需要写上)

// 设置保留实例,不会因为屏幕方向或配置变化而重新创建
fragment.setRetainInstance(true);

说明:注释什么情况下要写?什么情况下不用写?个人的建议是尽量用规范的命名来减少不必要的注释,很多时候我们只需要换位思考一下,忘记这段代码是自己写的,再问一下自己能不能一下子读懂,如果可以的话,注释就可以不用写,否则注释还是要考虑写上。

十四、代码硬编码规范

14.1 请尽量避免使用硬编码,例如系统的一些常量值,不能直接写死,而是应该通过代码引用,例如:

// 不规范写法示例
if (view.getVisibility() != 0) {
return;
}

Intent intent = new Intent("android.settings.APPLICATION_DETAILS_SETTINGS");
startActivity(intent);

// 规范写法示例
if (view.getVisibility() != View.VISIBLE) {
return;
}

Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
startActivity(intent);

14.2 在项目开发中,被多次使用到的数值或者字符串也应该提取成常量来供外部引用,例如:

public final class UserInfoManager {

/** 学生 */
public static final int TYPE_STUDENT = 0;

/** 老师 */
public static final int TYPE_TEACHER = 1;

/** 家长 */
public static final int TYPE_PATRIARCH = 2;
}

14.3 但并不代表所有的数值都需要常量化,有一些数值常量化的意义并不大,例如:

ValueAnimator animator = ValueAnimator.ofInt(0, 100);
animator.setDuration(500);
animator.start();

衡量一个数值或者字符串是否进行常量化的标准有两点:

  • 这个数值或者字符串是否会被多次使用;
  • 这个数值或者字符串是否具有一定的含义。

十五、布局文件命名规范

15.1 以 模块 + 类型 来命名,例如:

home_activity.xml

setting_fragment.xml

menu_item.xml

address_dialog.xml

15.2 这样写的好处在于,由于 res 文件夹下是没有层级概念的

15.3 通过前缀的命名可以帮助我们更好定位到同一模块下的资源

15.4 例如分享对话框中,有对话框 Root 布局和 Item 布局

share_dialog.xml(Root 布局)

share_item.xml(Item 布局)

十六、资源文件命名规范

16.1 如果是业务模块下的资源,以 模块 + 类型 来命名,例如分享对话框的资源:

share_link_ic.png(复制链接)

share_moment_ic.png(分享到朋友圈)

share_qq_ic.png(分享到 QQ 好友)

share_qzone_ic.png(分享到 QQ 空间)

share_wechat_ic.png(分享到微信好友)

16.2 如果和业务模块不相干的资源,以 作用 + 类型 来命名,例如通用的控件样式资源:

button_rect_selector.xml(通用直角按钮样式)

button_round_selector.xml(通用圆角按钮样式)

16.3 这种资源有一个共同特点,它不属于哪个模块,但是在不同模块都有用到,所以不能用业务的模块名作为文件名前缀,最后附上常见类型名称对应表:
【Android -- 规范】Android 编码规范_ide_02

十七、 String ID 命名规范

17.1 请以 模块 + 功能 来命名,例如:

<!-- 主界面 -->
<string name="home_nav_index">首页</string>
<string name="home_nav_found">发现</string>
<string name="home_nav_message">消息</string>
<string name="home_nav_me">我的</string>
<string name="home_exit_hint">再按一次退出</string>

<!-- 登录界面 -->
<string name="login_register">注册</string>
<string name="login_phone_hint">请输入手机号</string>
<string name="login_password_hint">请输入密码</string>
<string name="login_forget">忘记密码?</string>
<string name="login_text">登录</string>
<string name="login_other">其他登录方式</string>

<!-- 注册界面 -->
<string name="register_title">注册</string>
<string name="register_hint">手机号仅用于登录和保护账号安全</string>
<string name="register_login">登录</string>
<string name="register_password_hint1">设置密码</string>
<string name="register_password_hint2">再次输入密码</string>
<string name="register_password_input_error">两次密码输入不一致,请重新输入</string>

<!-- 设置界面 -->
<string name="setting_title">设置</string>
<string name="setting_language_switchover">语言切换</string>
<string name="setting_language_simple">简体中文</string>
<string name="setting_language_complex">繁体中文</string>

17.2 另外有一类 String 被多个模块所引用,需要以 common + 作用 来命名,例如:

<string name="common_loading">加载中&#8230;</string>

<string name="common_confirm">确定</string>
<string name="common_cancel">取消</string>

<string name="common_year"></string>
<string name="common_month"></string>
<string name="common_day"></string>

<string name="common_hour"></string>
<string name="common_minute"></string>
<string name="common_second"></string>

十八、 Color ID 命名规范

请以 模块 + 含义 + color 来命名,例如:

<color name="logcat_level_verbose_color">#FFBBBBBB</color>
<color name="logcat_level_debug_color">#FF33B5E5</color>
<color name="logcat_level_info_color">#FF99CC00</color>
<color name="logcat_level_warn_color">#FFFFBB33</color>
<color name="logcat_level_error_color">#FFFF4444</color>
<color name="logcat_level_other_color">#FFFFFFFF</color>

十九、 Anim ID 命名规范

19.1 应用到某个模块 View,例如:

login_left_balloon_view.xml
login_right_balloon_view.xml

19.2 应用到全局 Activity,例如:

left_in_activity.xml
left_out_activity.xml

19.3 应用到全局 Dialog,例如:

bottom_in_dialog.xml
bottom_out_dialog.xml

二十、 View ID 命名规范

20.1 应该以 控件的缩写 + 模块名 + 作用 来命名,例如:

@+id/R.id.rg_login_type

@+id/R.id.et_login_phone

@+id/R.id.et_login_sms

@+id/R.id.et_login_password

@+id/R.id.btn_login_commit

20.2 View 和 Layout 控件缩写表,这里列举最常见的几个
【Android -- 规范】Android 编码规范_android_03

二十一、 Style 命名规范

21.1 如果只是主题相关的样式,以 Theme 命名结尾,控件样式则以 Style 命名结尾,命名要求尽量简洁,并且需要有代码注释,示例如下:

<!-- 应用主题样式 -->
<style name="AppTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
.....
</style>

<!-- 全屏主题样式 -->
<style name="FullScreenTheme" parent="AppTheme">
.....
</style>

<!-- 闪屏页主题样式 -->
<style name="SplashTheme" parent="FullScreenTheme">
.....
</style>
<!-- 默认圆角按钮样式 -->
<style name="ButtonStyle" parent="Widget.AppCompat.Button.Borderless">
.....
</style>

<!-- 不带圆角按钮样式 -->
<style name="RectButtonStyle" parent="ButtonStyle">
.....
</style>

<!-- 默认文本框样式 -->
<style name="EditTextStyle">
.....
</style>

<!-- 验证码按钮样式 -->
<style name="CountdownViewStyle">
.....
</style>

二十二、 XML 编码规范

22.1 不推荐用 dp 作为字体单位,虽然在大部分手机上面 dp 和 sp 计算是差不多的,但是会有一部分老年用户群,例如咱们的长辈,他们通常会把手机显示的字体大小调大,这样他们才不需要带眼镜看手机,如果我们用 dp 作为字体单位,无论手机怎么调整字体大小,应用的字体大小都不会有任何的变化,所以这种操作显然是非常不人性化的。

<!-- 不规范写法示例 -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="18dp" />
<!-- 规范写法示例 -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="18sp" />

22.2 不能根据设计图给定的宽高把 TextView 或者 Button 的宽高定死,而是通过 wrap_content 和 padding 的方式来调整 View 的宽高,因为在不同手机上面字体大小不一致,在字体显示比较小的手机上面会显示正常,但是在字体显示比较大的平板上面文字上半部分极有可能会出现被裁剪的情况,所以我们不能把宽高定死,而是通过 padding 来调整到控件的大小。不过需要注意的是,TextView 有自带的文字间距,我们在拿设计图给定的 padding值时,需要拿设计图给定的值适当减去这一部分值(一般大概是在 2~3dp)。

<!-- 不规范写法示例 -->
<Button
android:layout_width="180dp"
android:layout_height="60dp" />
<!-- 规范写法示例 -->
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="80dp"
android:paddingTop="20dp"
android:paddingEnd="80dp"
android:paddingBottom="20dp" />

22.3 ImageView 的宽高任一项定义成 match_parent 时,另外一项不能写死大小,而是应该使用 wrap_content,否则很可能会因为比例不对导致图片变形,另外还需要使用 android:adjustViewBounds=“true” 属性,否则 ImageView 无法根据图片的宽高来调整自己的宽高。

<!-- 不规范写法示例 -->
<ImageView
android:layout_width="match_parent"
android:layout_height="300dp"
android:src="@drawable/example_bg" />
<!-- 规范写法示例 -->
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:src="@drawable/example_bg" />

22.4 XML 节点编写应该规范,在没有子节点的情况下,应当以 /> 节点结尾,如果有则以 </xxx.xxx.xxx> 节点结尾

<!-- 不规范写法示例 -->
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent">
</androidx.recyclerview.widget.RecyclerView>

<!-- 不规范写法示例 -->
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent">
</TextView>
<!-- 规范写法示例 -->
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent" />

<!-- 规范写法示例 -->
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent" />

二十三、 预览属性约定

23.1 应该在布局文件根布局中定义 tools:context 属性,以便在布局文件中快速定位到对应的类

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.activity.HomeActivity">

</FrameLayout>
tools:context=".ui.activity.HomeActivity"

tools:context=".ui.fragment.SettingFragment"

tools:context=".ui.adapter.HomeAdapter"

tools:context=".ui.dialog.PersonDataDialog"

23.2 此外,tools 属性还有各种各样的用途,例如 RecyclerView 的 tools 属性

<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_pay_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:overScrollMode="never"

tools:listitem="@layout/item_dialog_pay_password"
tools:itemCount="9"

tools:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
tools:spanCount="3" />

23.3 这种命名方式不止可以应用于 RecyclerView,还可以应用于其他 View 的属性,比如常用的 TextView 和 ImageView

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="学生姓名" />

<ImageView
android:id="@+id/iv_home_course_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:src="@drawable/bg_home_placeholder" />

如果某个 TextView 显示的字符串是一成不变的,那么可以直接定义在布局文件中,如果是动态变化的,那么应该使用 tools:text 预览属性,而不应该使用 android:text,其他布局属性也同理。

二十四、资源硬编码规范

  • String 硬编码规范:如果项目已经适配了多语种,则严禁写死在 Java 代码或者布局文件中,如果没有这块需求的话,也建议将 String 资源定义在 string.xml 文件,此项不强制要求,大家根据实际情况而定。
  • Color 硬编码规范:在没有使用夜间模式的情况下,允许大部分 Color 值直接定义在布局文件中,但是如果某个色值引用得比较多(例如主题强调色、默认背景色等),需要抽取到 color.xml 文件中。
  • Dimens 硬编码规范:允许写死在 Java 代码或者布局文件中,但是如果使用了通配符方案对屏幕进行适配,那么则不能直接写死。
  • Style 样式规范:对于一些常用并且样式比较统一的控件,例如 Button、EditText 等,我们对这些控件的样式进行抽取到 style.xml 文件中来,避免属性重复定义。