Android基础知识点汇总一

1.EditText的使用总结

禁止输入回车符号

1
2
3
4
5
6
7
etSearch.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
//禁止edittext输入回车键
return (event.getKeyCode() == KeyEvent.KEYCODE_ENTER);
}
});

点击搜索框隐藏hint内容

1
2
3
4
5
6
7
8
9

etSearch.setOnFocusChangeListener((v, hasFocus) -> {
EditText editText = (EditText) v;
if (hasFocus) {
editText.setHint("");
} else {
editText.setHint("搜索");
}
});

禁止输入空格与特殊字符

InputFilterUtils工具类见文末.

禁止输入空格

1
InputFilterUtils.setEditTextInhibitInputSpace(edt);

禁止输入特殊字符

1
InputFilterUtils.setEditTextInhibitInputSpaChat(etSearch);

2. 修改键盘回车键的样式

参考博客:EditText的imeOptions属性的设置

点击EditText会弹出键盘, 一般右下角有一个回车键, 默认情况下它是起换行的作用, 但是有时候我们需要对它进行修改,例如点击回车键发送请求,开始搜索等等.

我们可以对EditText的imeOptions属性进行修改来实现自定义的样式和功能.imeOptions属性的常用的值如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
IME_ACTION_UNSPECIFIED. 编辑器决定Action按钮的行为
IME_ACTION_GO Action按钮将作为 “开始” 按钮。点击后跳转到输入字符的意图页面
IME_ACTION_SEARCH 执行“搜索”按钮。点击后跳转到输入字符的搜索结果页面
IME_ACTION_SEND. 执行 “发送”按钮。点击后将输入字符发送给它的目标
IME_ACTION_NEXT. Action按钮将作为next(下一个)按钮。点击后将进行下一个输入框的输入
IME_ACTION_DONE. Action按钮将作为done(完成)按钮。点击后IME输入法将会关闭
IME_ACTION_PREVIOUS. 作为”上一个”按钮。点击后将进行上一个输入框的输入
IME_FLAG_NO_FULLSCREEN. 请求IME输入法永远不要进入全屏模式
IME_FLAG_NAVIGATE_PREVIOUS. 类似IME_FLAG_NAVIGATE_NEXT, 表明这里有后退导航可以关注的兴趣点
IME_FLAG_NAVIGATE_NEXT. 表明这里有前进导航可以关注的兴趣点,类似IME_ACTION_NEXT,不过允许IME输入多行且提供前进导航。
IME_FLAG_NO_EXTRACT_UI. 请求IME输入法不要显示额外的文本UI
IME_FLAG_NO_ACCESSORY_ACTION. 和一个Action结合使用表明在全屏输入法中不作为可访问性按钮
IME_FLAG_NO_ENTER_ACTION. 多行文本将自动设置了该标志位,执行Action时为换行效果,如果未设置,IME输入法将把Enter按钮自动替换为Action按钮
IME_FLAG_FORCE_ASCII. 请求IME输入法接受ASCII字符的输

例如我们要将键盘的回车键改为搜索的样式,

首先在xml中修改editext代码,加入imeOptions属性,将该属性的值设置为actionSearch.如下所示

1
2
3
4
5
6
7
8
9
<EditText
android:id="@+id/et_search"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/transparent"
android:hint="搜索"
android:textColor="@color/color_gray_hint"
android:imeOptions="actionSearch"
android:textSize="@dimen/size_14sp" />

然后再activity页面中作如下修改

1
2
3
4
5
6
7
8
9
10
11
12

//etSearch为搜索的EditText
etSearch.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView textView, int actionId, KeyEvent keyEvent) {
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
//todo 请求搜索的接口
return true;
}
return false;
}
});

设置完成之后运行项目, 发现并没有生效, 根据博客Android Edittext设置android:imeOptions=”actionSearch”不起作用, 在EditText中添加android:singleLine="true"就好使了.


3. 进入页面EditText抢焦点的问题

如果一个页面中包含有EditText控件的时候,进入页面的时候会抢占焦点, 弹出键盘. 比较影响用户体验,我们可以通过两行代码来解决这个问题.

在该控件的父布局中添加以下两行代码, 这样父布局就会先于EditText获得焦点.

1
2
android:focusable="true"
android:focusableInTouchMode="true"

4.Android 内部存储和外部存储的总结

参考博客: Android存储路径你了解多少
Android内、外存储 易混淆点剖析(/mnt/sdcard、/storage/sdcard0、/storage/emulated/0等区别)(写的也很好)

Android存储路径你了解多少这篇博客写的还是不错的, 但是个人认为里边有些地方理解的还不是很正确. 下边简单谈谈自己对内部存储和外部存储的理解.

自己有两台Android测试机,分别是华为和红米,都是6.0版本的系统. 如下两张图,第一张是华为手机,第二张是红米手机

华为手机
红米手机

可以看到这两个系统都显示为内部存储, 那么这个内部存储具体的路径是什么呢?

要想彻底弄清Android的文件是如何存储的需要对自己的手机进行root. 自己的红米手机是root过的, 下边使用ES文件浏览器对自己的红米手机进行分析.

首先我们看下什么是RAM和Rom, 下边是中关村在线显示的小米10的参数

小米10

上图中的内存8Gb说的就是手机的运存,也就是运行时内存,类似于电脑的内存条.然后我们点击查看详细参数,会看到下图的参数

小米10

其中RAM也就是上边说的运存, 那么 ROM 128Gb是什么鬼呢? 它相当于我们的硬盘, 我们下边分析的内部存储和外部存储其实都是在这个ROM上划分的.

接着往下分析, 在编写代码的过程中我们经常调用 Environment.getExternalStorageDirectory() 来获取外部存储的路径, 那么它返回的结果到底是什么呢?
有的博主说返回的是 storage/emulated/0 , 有的人说返回的是 /storage/sdcard0 ,还有人说返回的结果是 /mnt/sdcard/0 , 其实这是Android 不通版本逐渐演变造成的,他们其实对应的是同一个目录.
更加详细的解释可以参考博客Android内、外存储 易混淆点剖析(/mnt/sdcard、/storage/sdcard0、/storage/emulated/0等区别)

关于linux挂载知识的扩展:
linux中分区挂载的解释
什么是挂载,Linux挂载详解

首先自己使用ES文件浏览器点击手机根路径,如下图所示

红米手机根路径


然后使用ES文件浏览器分别点击上边的三个路径, 经过自己的验证,这三个路径都指向的是同一个位置,如下边三张图,他们的内容都一样,

storage/emulated/0


/storage/sdcard0


/mnt/sdcard/0




这个里边的内容是不是很熟悉啊, 没错,它和文章开头打开手机文件管理器中查看的那个目录中的内容中完全一样. 这时我们就纳闷了,命名调用的方法是getExternalStorageDirectory(),直译的意思就是获取外部存储,为什么手机上给我们显示的是内部存储.

一定要明白,这里文件管理器显示的内部存储是相对用手机本身来说的,如果我在卡槽里插入sd卡,相对于卡槽中的sd卡他就是内部存储. 这个电脑上的硬盘类似, 硬盘数据笔记本的内部存储,我们插入的U盘就是电脑的外部存储, 此时的内部和外部的参照物是电脑.

那么我们平时开发中所说的, 如上图手机根目录的 /data/data属于内部存储, storage/emulated/0属于外部存储,这是什么意思呢?
个人理解这里的内部存储和外部存储是相对用户来说的, /data/data用户手机不root它就看不到, 相对于用户来说他就是内部存储, storage/emulated/0用户可以看到,可以对其修改,所以他就是外部存储, 但是他们其实都是在ROM中,例如文章开头说的小米10的128G Rom .

所以用户所说的内部存储和我们开发时的内部存储不是一个概念.

我们手机的android包就安装在内部存储/data/data/目录中,已经手机运行中的数据库可文件, sp文件, asstes目录都在这个目录中. 这个目录用户手机不root是看不到的.
这个目录中常用的路径如下
1
2
3
4
5
getCacheDir() = /data/data/com.my.app/cache
getFilesDir() = /data/data/com.my.app/files

//数据库存放路径
getDatabasePath(“test”) = /data/data/com.my.app/databases/test

我们开发中常用的外部存储storage/emulated/0,也就是手机系统显示在文件管理器上的内部存储字样的目录中常用的目录有

1
2
3
4
5
6
7
8
Environment.getDownloadCacheDirectory() = /cache
Environment.getExternalStorageDirectory() = /mnt/sdcard
//外部存储私有目录(app卸载之后这些目录就会被删除)
getExternalFilesDir(null) = /mnt/sdcard/Android/data/com.my.app/files
getExternalCacheDir() = /mnt/sdcard/Android/data/com.my.app/cache
//外部共有目录(app卸载后对应的文件不会被删除),更详细的可以参考: [Android存储路径你了解多少](https://www.jianshu.com/p/2de0113b3164)
Environment.getExternalStoragePublicDirectory(DIRECTORY_DOWNLOADS)//storage/sdcard0/Download
Environment.getExternalStoragePublicDirectory(DIRECTORY_PICTURES)///storage/sdcard0/Pictures

[!tip]
比较优秀的程序都会专门写一个获取缓存地址的方法:

1
2
3
4
5
6
7
8
9
10
public String getDiskCacheDir(Context context) {
String cachePath = null;
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
|| !Environment.isExternalStorageRemovable()) {//当SD卡存在或者SD卡不可被移除的时候
cachePath = context.getExternalCacheDir().getPath();
} else {
cachePath = context.getCacheDir().getPath();
}
return cachePath;
}

附常用目录:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Environment.getDataDirectory() = /data
Environment.getDownloadCacheDirectory() = /cache
Environment.getExternalStorageDirectory() = /mnt/sdcard
Environment.getExternalStoragePublicDirectory(“test”) = /mnt/sdcard/test
Environment.getRootDirectory() = /system
getPackageCodePath() = /data/app/com.my.app-1.apk
getPackageResourcePath() = /data/app/com.my.app-1.apk
getCacheDir() = /data/data/com.my.app/cache
getDatabasePath(“test”) = /data/data/com.my.app/databases/test
getDir(“test”, Context.MODE_PRIVATE) = /data/data/com.my.app/app_test
getExternalCacheDir() = /mnt/sdcard/Android/data/com.my.app/cache
getExternalFilesDir(“test”) = /mnt/sdcard/Android/data/com.my.app/files/test
getExternalFilesDir(null) = /mnt/sdcard/Android/data/com.my.app/files
getFilesDir() = /data/data/com.my.app/files


5.@TargetApi和@RequiresApi的含义

参考博客 @TargetApi和@RequiresApi含义

在开发中有一些api是高版本的sdk中新增加的,此时为了编译通过,你可以使用 @TargetApi和@RequiresApi这个两个注解来使编译通过.
但是对应的代码仍然会执行.官方建议使用@RequiresApi.

添加了这两个注解的方法仍然会执行, 所以正确的做法是加上版本判断来适应不同的版本. 实例代码如下

1
2
3
4
5
6
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
// todo ...
}else{
// todo ...
}
}

6.代码中获取drawable资源

在代码中可以通过Context的getDrawable(resId)方法来获取drawable资源,但是会提示Call requires API level 21 (current min is 19):,在api为21上才有此方发,api小于21无法使用.

谷歌为推荐我们使用ContextCompat.getDrawable(context,R.mipmap.indexz)来获取drawable资源文件.

7.TextView的基本使用

7.1 ellipsize属性-多余文字用省略号显示

TextView中可以设置一个ellipsize属性,作用是当文字长度超过textview宽度时的显示方式

1
2
3
4
android:ellipsize=”start”—–省略号显示在开头 “…lmn”
android:ellipsize=”end”——省略号显示在结尾 “abcdec…”
android:ellipsize=”middle”—-省略号显示在中间 “ab…lmn”
android:ellipsize=”marquee”–跑马灯效果(需要额外处理)

例如: 设置超过n行显示省略号,示例代码

1
2
3
4
5
<TextView
...
android:maxLines="n" (n=1,2,3...)
android:ellipsize="end"
... />

在某些情况下这个属性会失效,例如当使用了inputType属性的时候,这个属性就会失效.参考博客关于TextView的android:ellipsize=”end”属性无效的挣扎

8. Android仿微信朋友圈添加图片

参考博客
Android仿微信朋友圈添加图片
demo github地址
自己收藏的项目的本地路径为/home/shaoyance/android-studio-project/Demo/WXCircleAddPic-master

效果如下图

仿微信朋友圈添加图片

在做意见反馈选择图片功能的时候遇到到了类似微信朋友圈添加图片的需求, 通过参照参考博客中的demo实现了该功能.

该demo中添加图片主要使用了第三方库PictureSelector, 选择图片的控件使用的是GridView, 预览和删除图片使用了ViewPager,整体上还是非常不错的, 是学习GridView和ViewPager控件的好demo.

9.上传单张图片和多张图片

参考博客Retrofit2 & RxJava2实现单文件和多文件上传

核心代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

UploadFileRequestBody uploadFileRequestBody =
new UploadFileRequestBody(file, fileUploadObserver);
//上传单张图片
API()
.uploadFile(getToken(),MultipartBuilder.fileToMultipartBody(file,
uploadFileRequestBody))
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.subscribe(fileUploadObserver);

//上传多张图片
MultipartBody multipartBody = MultipartBuilder.filesToMultipartBody(files, fileUploadObserver);

API()
.uploadFile(getToken(),multipartBody)
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.subscribe(fileUploadObserver);

UploadFileRequestBody , MultipartBuilder, FileUploadObserver这三个类的核心代码在参考博客中都有, 直接用即可.

10 Android p 配置使用http请求

参考博客android p适配

现象: 在加载验证码的时候,自己6.0手机上可以正常显示,但是同事的9.0手机上却无法加载. 发现控制台输出如下错误日志:

1
2
W/Glide: Load failed for http://192.168.0.1****
java.io.IOException: Cleartext HTTP traffic to xxx-99billxx.ufile.ucloud.cn not permitted

这是因为从Android P(9.0 api28)开始默认情况下启用网络传输层安全协议 (TLS),也就是https请求, 此api是在api23 就引入的.

如果你的应用Target 28+,则默认情况下 isCleartextTrafficPermitted() 函数返回 false。如果你仍想使用http明文传输,则需要在应用的安全配置中将 cleartextTrafficPermitted 的属性设置为 true.

你可以为特定的域名指定明文传输, 定义配置文件 res/xml/network_security_config.xml

1
2
3
4
5
6
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <domain-config cleartextTrafficPermitted="false">
        <domain includeSubdomains="true">secure.example.com</domain>
    </domain-config>
</network-security-config>

也可以为所有的请求指定为明文传输, 定义配置文件 res/xml/network_security_config.xml

1
2
3
4
<?xml version="1.0" encoding="utf-8"?>
<network-security-config xmlns:android="http://schemas.android.com/apk/res/android">
<base-config cleartextTrafficPermitted="true" />
</network-security-config>

然后在manifest.xml 中引用该配置文件

1
2
3
4
5
6
<manifest ... >
    <application android:networkSecurityConfig="@xml/network_security_config"
                    ... >
        ...
    </application>
</manifest>

InputFilterUtils工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public class InputFilterUtils {

/**
* 禁止EditText输入空格
* @param editText
*/
public static void setEditTextInhibitInputSpace(EditText editText){
InputFilter filter=new InputFilter() {
@Override
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
if(source.equals(" "))return "";
else return null;
}
};
editText.setFilters(new InputFilter[]{filter});
}

/**
* 禁止输入特殊字符
* @param editText
*/
public static void setEditTextInhibitInputSpaChat(EditText editText) {
InputFilter filter_space = new InputFilter() {
@Override
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
if (source.equals(" "))
return "";
else
return null;
}
};
InputFilter filter_speChat = new InputFilter() {
@Override
public CharSequence filter(CharSequence charSequence, int i, int i1, Spanned spanned, int i2, int i3) {
String speChat = "[`~!@#_$%^&*()+=|{}':;',\\[\\].<>/?~!@#¥%……&*()— +|{}【】‘;:”“’。,、?]";
Pattern pattern = Pattern.compile(speChat);
Matcher matcher = pattern.matcher(charSequence.toString());
if (matcher.find()) return "";
else return null;
}
};
editText.setFilters(new InputFilter[]{filter_space, filter_speChat});
}

}

博客编号 36