安卓开发笔记

开发必做

distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-7.3.3-bin.zip

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
46
buildscript {

repositories {

maven {
url 'http://maven.aliyun.com/nexus/content/groups/public/'
allowInsecureProtocol = true
}
maven {
url 'http://maven.aliyun.com/nexus/content/repositories/jcenter'
allowInsecureProtocol = true
}
maven {
url 'http://maven.aliyun.com/nexus/content/repositories/google'
allowInsecureProtocol = true
}
maven {
url 'http://maven.aliyun.com/nexus/content/repositories/gradle-plugin'
allowInsecureProtocol = true
}

google()
jcenter()
}

}

allprojects {
repositories {
maven {
url 'http://maven.aliyun.com/nexus/content/groups/public/'
allowInsecureProtocol = true
}
maven {
url 'http://maven.aliyun.com/nexus/content/repositories/google'
allowInsecureProtocol = true
}
maven {
url 'http://maven.aliyun.com/nexus/content/repositories/gradle-plugin'
allowInsecureProtocol = true
}

google()
jcenter()
}
}

kotlin为

maven{ setUrl("http://maven.aliyun.com/nexus/content/groups/public/")}

crtl + p 提示

ctrl +win+alt+L格式化整理代码

测试安装不了

1
android.injected.testOnly=false

连接手机

先在电脑cmd窗口adb tcpip 5555,再把手机置静态IP连接热点,之后adb connect 192.168.137.66:5555

1、先确认Android设备开启开发者模式,并且开启USB调试;

2、确认Android设备和电脑处于同一局域网;

如果上述都确认还是出现 “由于目标计算机积极拒绝,无法连接。 (10061)” 这个问题,那就极有可能是端口被占用了:

(1)使用如下adb命令可以查看端口使用情况:

1
netstat -ano | findstr 5037

如果出现以下情况:

1
2
3
4
5
TCP    127.0.0.1:5037         0.0.0.0:0              LISTENING       5596
TCP 127.0.0.1:5037 127.0.0.1:49508 ESTABLISHED 5596
TCP 127.0.0.1:5037 127.0.0.1:50671 TIME_WAIT 0
TCP 127.0.0.1:5037 127.0.0.1:50672 TIME_WAIT 0
TCP 127.0.0.1:5037 127.0.0.1:50673 TIME_WAIT 0

从上面的 “TCP 127.0.0.1:5037 127.0.0.1:49508 ESTABLISHED 5596” 可以看出进程5596占用了端口,这时找到5596,并关掉它就可以了。关掉之前可以先看看是什么进程,查看进程的命令:

1
tasklist |findstr 5596

关掉进程的命令:

1
taskkill /pid 5596 /f

此时再次尝试adb连接Android设备,如果还不行,则使用下面的最后一种方法;

(2)使用USB连接电脑,然后执行以下命令行:

1
adb tcpip 5555

在没有报错的前提下,断开USB,再使用命令:

1
adb connect IP地址:5555

此时就能连接Android设备了,如果还不行,对不起,我也没办法了!

themes设置相关
1
2
3
4
5
6
7
8
9
10
11
12
13
colorPrimary : 顧名思義,就是主要的顏色,這個通常指得是 App 本身產品的代表色,通常也是品牌的主要視覺色
colorPrimaryVariant:主要顏色的變體,通常會從 colorPrimary 往較淡或較濃的色澤
colorOnPrimary:字面意思就是主要顏色上頭的顏色,這個顏色通常使用在背景色是主要顏色的元件上頭(例如字樣 Label 、icon 等)
colorSecondary:app 次要的品牌顏色,這些用於裝飾某些特定需要的 widget
colorSecondaryVariant:次要顏色的變體,也就是次要顏色偏暗或偏亮的樣式
colorOnSecondary:用於顯示於次要顏色上元件的顏色
colorError:顯示錯誤的顏色 (最常見的就是紅色)
colorOnError:在錯誤顏色上頭元件的顏色
colorSurface:表層顏色(就是 Sheet 的顏色)
colorOnSurface:在表層顏色上的的元件顏色
android:colorBackground:最底的背景色
colorOnBackground:用於對底背景色上頭的元件用的顏色
利用这些属性,搭配上面的那些技巧,可以組合出很棒的效果。

.bridge是让按钮等不默认使用主题色,.NoActionBar是不显示顶部导航条,现在用Toolbar替代ActionBar了,要用自带标题栏就用Toolbar

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!--安卓默认生成-->
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.MyApplication" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->

<item name="colorPrimary">@color/purple_500</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/white</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_700</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>
沉侵式体验

沉浸式状态栏

1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Window window = getWindow();
window.getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
window.setStatusBarColor(Color.TRANSPARENT);
}

setContentView(R.layout.activity_main); // 设置你的布局文件
}

沉浸式导航栏

1
2
3
4
5
6
7
8
9
10
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Window window = getWindow();
window.getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
window.setStatusBarColor(Color.TRANSPARENT);
window.setNavigationBarColor(Color.TRANSPARENT);
}

使用沉浸式布局

1
2
3
4
5
6
7
8
9
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">

<!-- Your content here -->

</RelativeLayout>

这将会让布局内容在状态栏和导航栏区域留出空间。

透明状态栏
1
2
3
4
5
6
7
8
9
10
11
//沉浸式状态栏,显示上面图标但状态栏透明,下面2行代码连用,如果整个界面是白色图标会消失
//下面这样代码一定要这么写,1024和256,单用1024就可以了其实
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
//下面这句是设置背景色的,下面这个是设置为透明,自己按需求改,只用这条属性不做设置上面各种小图标也会消失,透明了,最新方法
getWindow().setStatusBarColor(Color.TRANSPARENT);

//隐藏状态栏并且会自动消失,与其他一起使用无效
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
//显示状态栏,
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
属性 作用
View.SYSTEM_UI_FLAG_VISIBLE 状态栏和Activity共存,Activity不全屏显示。也就是应用平常的显示画面
View.SYSTEM_UI_FLAG_FULLSCREEN Activity全屏显示,且状态栏被覆盖掉,但下拉会出现并且不能再隐藏
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN Activity全屏显示,但是状态栏不会被覆盖掉,而是正常显示,只是Activity顶端布 局会被覆盖住
View.INVISIBLE Activity全屏显示,隐藏状态栏
隐藏ActionBar
1
getSupportActionBar().hide();

隐藏导航栏

1
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);

依赖设置

1
2
jcenter()
maven { url 'https://www.jitpack.io' }
1
implementation 'com.google.android.material:material:1.1.0'//降为这个版本,不然预览视图有bug

全屏手势

1
2
3
4
5
6
7
getWindow().getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
属性 作用
View.SYSTEM_UI_FLAG_VISIBLE 状态栏和Activity共存,Activity不全屏显示。也就是应用平常的显示画面
View.SYSTEM_UI_FLAG_FULLSCREEN Activity全屏显示,且状态栏被覆盖掉
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN Activity全屏显示,但是状态栏不会被覆盖掉,而是正常显示,只是Activity顶端布 局会被覆盖住
View.INVISIBLE Activity全屏显示,隐藏状态栏
设置图标

第一种:(最简单的方法)

将你准备好的 图标放入res目录下的drawable,在AndroidManifest.xml文件中,找到android:icon以及android:roundIcon这两个属性,设置为你放入的图标文件。

在这里,这两个属性都能对图标进行设置,在设置时只使用一个也可以达到效果,但如果两个同时使用的话,属性指定的对象需要设置一致。若不一致,我测试结果是显示的roundIcon指定的对象,找到android:roundIcon 属性的解释:

android:roundIcon 属性指定一个图标,但只有你需要给应用设置一个特别的圆形图标时才要用到这个属性。

第二种:(稍微复杂)

更详细的解释可以看这两篇文章

https://www.jb51.net/article/188580.htm

[Android神兵利器之Image Asset Studio]

https://www.jb51.net/article/138346.htm

[application中 android:icon 和 android:roundIcon 的区别]

使用外部字体

src/main中创建了一个文件夹assets文件夹,在assets文件夹中创建fonts文件夹,把字体文件放到fonts目录下

此外,在具有以下内容的同一文件夹内创建一个名为fonts.xml的文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
<font-family xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">

<font
app:fontStyle="normal"
app:fontWeight="700"
app:font="@font/customfont"/>

<font
app:fontStyle="normal"
app:fontWeight="700"
app:font="@font/customfont_bold"/>
</font-family>

然后,编辑文件app> src> res> values> styles.xml,为整个应用程序应用默认字体。

1
2
3
4
5
6
7
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="android:fontFamily">@font/customfont</item>
</style>

如果要更改单个UI元素的字体,请执行以下操作:

1
2
3
4
5
6
7
8
<TextView
android:fontFamily="@font/customfont"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:textColor="@color/black"
android:textSize="18sp"
android:text="Some text"
/>

java中使用

1
2
3
TextView tv=(TextView)findViewById(R.id.custom);
Typeface face=Typeface.createFromAsset(getAssets(),"fonts/Verdana.ttf");//记得加fonts文件名
tv.setTypeface(face);
使用内置绘制矢量图

Project模式下右击选择src,然后选择New/Vector Asset

双击退出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//双击返回
@SuppressLint("WrongConstant")
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
long tempTime = System.currentTimeMillis();
if (keyCode == KeyEvent.KEYCODE_BACK) {
if (tempTime - startTime >= 2000) {
startTime = tempTime;
Toast.makeText(this, "再点击一次退出应用", Toast.LENGTH_SHORT).show();
return true;
} else {
System.exit(0);
}
}
return super.onKeyUp(keyCode, event);
}

网络请求

  • OKHttp
  • Retrofit(对OKHttp的封装,下面的Retrofit2的库包含了OKHttp)
1
2
3
//retrofit2和retrofit转换器
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation "com.squareup.retrofit2:converter-gson:2.9.0"

布局

布局代码过长时可以把一部分代码放带到另一个xml文件中,然后在要引用的地方<include layout="@layout/tab1"/>

  • 线性布局
1
2
/*android:layout_gravity="center_horizontal"在线性布局中没有作用是因为
没有设置布局属性android:orientation="vertical"*/

一些技巧,把一个控件放在底部时,要让上面的整个界面布满可以用android:layout_weight="1"属性,如在fragment和viewpager

使用时

控件

设置控件可见与不可见

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//xml 
android:visibility="visible";
//(可见)
android:visibility="invisible";
//(不可见,保留布局位置)
android:visibility="gone";
//(隐藏不保留布局位置)


//java
view.setVisibility(View.VISIBLE);
//(可见)
view.setVisibility(View.INVISIBLE);
//(不可见,保留布局位置)
view.setVisibility(View.GONE);
//(隐藏不保留布局位置)

View的一些公共属性

android:id="@+id/tv_hello"dp是与设备无关的属性,px像素就不一样了,sp是给字体用的

android:layout_width="match_parent"

android:layout_height="match_parent"

android:gravity="right|center_vertical" //gravity属性字面意思重力的意思,控制view里面的内容的,多个属性用竖线分隔

android:layout_gravity="center_vertical" //这个是来控制这个控件在父布局中的位置的,也会由父布局的特性影响到

android:background="@color/teal_200"

padding和margin

自定义背景

  • 新建Drawable Resource File文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
//android:shape属性设置形状

//这个属性设置圆角弧度
<corners
android:radius="20dp"/>

//这个属性设置描边
<stroke
android:color="@color/teal_200"
android:width="2dp"/>

//这个属性设置描边内部的填充色
<solid
android:color="@color/teal_200"/>

</shape>

Dialog(对话框)

1. 最简单的提示对话框

(1)创建AlertDialog.Builder实例对象。

(2)通过Builder对象设置对话框的属性。

  • setTitle()设置标题
  • setIcon ()设置图标
  • setMessage ()设置要显示的内容
  • setItems()设置在对话框中显示的项目列表
  • setView()设置自定义的对话框样式
  • setPositiveButton ()设置确定按钮
  • setNegativeButton ()设置取消按钮
  • setNeutralButton ()设置中立按钮
  • setSingleChoiceItems单选框
  • setMultiChoiceItems复选框

(3)调用Builder对象的create()方法创建AlertDialog对话框

(4)调用AlertDialogshow()方法来显示对话框

(5)调用AlertDialogdimiss()方法销毁对话框。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
AlertDialog.Builder builder= new AlertDialog.Builder(this).setMessage("是否要修改个人信息")
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
isConfirm = 1;
dialogInterface.dismiss();
Toast.makeText(ChangeMessageActivity.this, "个人信息保存成功!",
Toast.LENGTH_SHORT).show();
}
})
.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
dialogInterface.dismiss();
}
});
AlertDialog alertDialog = builder.create();
alertDialog.show();

2. 列表对话框(供选择)

1
2
3
4
5
6
7
8
9
10
11
12
//setItems形成列表,第一个参数传列表条目的数组进去,第二个参数是监听,监听的第二个参数为选择了第几个条目,点击条目后会自动关闭
String[] s = new String[]{"第一条","第二条","第三条"};
AlertDialog.Builder builder= new AlertDialog.Builder(this)
.setItems(s, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
Toast.makeText(ChangeMessageActivity.this, "选择了第"+i+1+"条消息",
Toast.LENGTH_SHORT).show();
}
});
AlertDialog alertDialog = builder.create();
alertDialog.show();

3. 单选列表对话框

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
//setSingleChoiceItems第一个参数传列表条目数组,第二个参数为默认选择值,第三个为监听
String[] s = new String[]{"第一条","第二条","第三条"};
AlertDialog.Builder builder= new AlertDialog.Builder(this)
.setSingleChoiceItems(s, 0, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
isConfirm = i;
}
})
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
Toast.makeText(ChangeMessageActivity.this, "选择了第"+i+1+"条条目"
, Toast.LENGTH_SHORT).show();
dialogInterface.dismiss();
}
})
.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
dialogInterface.dismiss();
}
});
AlertDialog alertDialog = builder.create();
alertDialog.show();

4. 多选列表对话框

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
//setMultiChoiceItems第一个参数传条目数组,第二个传一个boolean类型数组后续保存是否被选中
String[] s = new String[]{"第一条","第二条","第三条"};
boolean[] isCheck = new boolean[3];
AlertDialog.Builder builder= new AlertDialog.Builder(this)
.setMultiChoiceItems(s, isCheck, new DialogInterface.OnMultiChoiceClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i, boolean b) {
isCheck[i] = b;
}
})
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
for (int j = 0; j < isCheck.length; j++) {
if (isCheck[j]){
back+=j+1;
}
}
Toast.makeText(ChangeMessageActivity.this, "选择了"+back, Toast.LENGTH_SHORT).show();
}
})
.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
dialogInterface.dismiss();
}
});
AlertDialog alertDialog = builder.create();
alertDialog.show();

5. 半自定义消息框

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//setView()设置自定义类型
View view = getLayoutInflater().inflate(R.layout.half_dialog_view, null);
final EditText editText = (EditText) view.findViewById(R.id.dialog_edit);
AlertDialog dialog = new AlertDialog.Builder(this)
.setIcon(R.mipmap.icon)//设置标题的图片
.setTitle("半自定义对话框")//设置对话框的标题
.setView(view)
.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
})
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String content = editText.getText().toString();
Toast.makeText(MainActivity.this, content, Toast.LENGTH_SHORT).show();
dialog.dismiss();
}
}).create();
dialog.show();

6. 全自定义消息框

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
<!--对话框的样式-->
<style name="NormalDialogStyle">
<!--对话框背景 -->
<item name="android:windowBackground">@android:color/transparent</item>
<!--边框 -->
<item name="android:windowFrame">@null</item>
<!--没有标题 -->
<item name="android:windowNoTitle">true</item>
<!-- 是否浮现在Activity之上 -->
<item name="android:windowIsFloating">true</item>
<!--背景透明 -->
<item name="android:windowIsTranslucent">false</item>
<!-- 是否有覆盖 -->
<item name="android:windowContentOverlay">@null</item>
<!--进出的显示动画 -->
<item name="android:windowAnimationStyle">@style/normalDialogAnim</item>
<!--背景变暗-->
<item name="android:backgroundDimEnabled">true</item>
</style>
<!--对话框动画-->
<style name="normalDialogAnim" parent="android:Animation">
<item name="@android:windowEnterAnimation">@anim/normal_dialog_enter</item>
<item name="@android:windowExitAnimation">@anim/normal_dialog_exit</item>
</style>

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
/**
* 自定义对话框
* dialog.setContentView(view);就可以设置我们自己的布局了
*/
private void customDialog() {
final Dialog dialog = new Dialog(this, R.style.NormalDialogStyle);
View view = View.inflate(this, R.layout.dialog_normal, null);
TextView cancel = (TextView) view.findViewById(R.id.cancel);
TextView confirm = (TextView) view.findViewById(R.id.confirm);
dialog.setContentView(view);
//使得点击对话框外部不消失对话框
dialog.setCanceledOnTouchOutside(true);
//设置对话框的大小
view.setMinimumHeight((int) (ScreenSizeUtils.getInstance(this).getScreenHeight() * 0.23f));
Window dialogWindow = dialog.getWindow();
WindowManager.LayoutParams lp = dialogWindow.getAttributes();
lp.width = (int) (ScreenSizeUtils.getInstance(this).getScreenWidth() * 0.75f);
lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
lp.gravity = Gravity.CENTER;
dialogWindow.setAttributes(lp);
cancel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dialog.dismiss();
}
});
confirm.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dialog.dismiss();
}
});
dialog.show();
}

一个常用设计

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
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/transparent"
android:orientation="vertical">

<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/round_corner"
android:text="拍照" />

<TextView
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:background="#ddd" />

<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/round_corner"
android:text="相册" />

<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:background="@drawable/round_corner"
android:text="取消" />

<View
android:layout_width="match_parent"
android:layout_height="15dp" />
</LinearLayout>
1
2
3
4
5
6
7
8
9
10
11
12
Dialog dialog = new Dialog(this, R.style.NormalDialogStyle);
View view = View.inflate(this, R.layout.dialog_bottom, null);
dialog.setContentView(view);
dialog.setCanceledOnTouchOutside(true);
view.setMinimumHeight((int) (ScreenSizeUtils.getInstance(this).getScreenHeight() * 0.23f));
Window dialogWindow = dialog.getWindow();
WindowManager.LayoutParams lp = dialogWindow.getAttributes();
lp.width = (int) (ScreenSizeUtils.getInstance(this).getScreenWidth() * 0.9f);
lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
lp.gravity = Gravity.BOTTOM;
dialogWindow.setAttributes(lp);
dialog.show();

7. 进度条对话框

1
2
3
4
//圆形
ProgressDialog dialog = new ProgressDialog(this);
dialog.setMessage("正在加载中");
dialog.show();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//线性进度条,dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL)设置即可		
final ProgressDialog dialog = new ProgressDialog(this);
dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
dialog.setMessage("正在加载中");
dialog.setMax(100);
final Timer timer = new Timer();
timer.schedule(new TimerTask() {
int progress = 0;

@Override
public void run() {
dialog.setProgress(progress += 5);
if (progress == 100) {
timer.cancel();
}
}
}, 0, 1000);
dialog.show();

8. 日期和时间选择消息框

1
2
3
4
5
6
7
8
9
Calendar calendar = Calendar.getInstance();
new DatePickerDialog(this
, new DatePickerDialog.OnDateSetListener() {
@Override
public void onDateSet(DatePicker view, int year, int month, int dayOfMonth) {

}
}, calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), calendar.get(Calendar.DAY_OF_MONTH))
.show();

TextView

空格及其他占位符

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

```&#160;``` == &nbsp; == &#xA0; == no-break space (普通的英文半角空格但不换行)

```&#12288; ```== 中文全角空格 (一个中文宽度)

```&#8194;``` == &ensp; == en空格 (半个中文宽度)

```&#8195; ```== &emsp; == em空格 (一个中文宽度)

```&#8197;``` == 四分之一em空格 (四分之一中文宽度)

相比平时的空格(&#32;),nbsp拥有不间断(non-breaking)特性。即连续的nbsp会在同一行内显示。即使有100个连续的nbsp,浏览器也不会把它们拆成两行。



```java
//设置颜色
mTextView2.setTextColor(getResources().getColor(R.color.black));
1
2
3
4
//字符串拼接,比直接+号进行拼接效率更高,高很多
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append(year);
stringBuffer.append("111");
1
2
3
4
5
tv = (TextView) findViewById(R.id. text_view ); 
// 中间加横线 , 添加Paint.ANTI_ALIAS_FLAG是线会变得清晰去掉锯齿
tv.getPaint().setFlags(Paint.STRIKE_THRU_TEXT_FLAG | Paint.ANTI_ALIAS_FLAG );
// 底部加横线 , 添加Paint.ANTI_ALIAS_FLAG是线会变得清晰去掉锯齿
tv .getPaint().setFlags(Paint. UNDERLINE_TEXT_FLAG | Paint.ANTI_ALIAS_FLAG );
1
2
android:maxLines="2"
android:ellipsize="end"
1
2
3
4
5
6
7
8
9
10
11
12
13
/*点击×的监听*/
edtTitle.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
// 如果点击的是清除图标区域
if (motionEvent.getX() >= (view.getWidth() - view.getPaddingEnd() - edtTitle.getCompoundDrawables()[2].getBounds().width())) {
// 处理清除图标的点击事件
edtTitle.setText(""); // 清空文本
return true; // 消耗触摸事件,不传递给下层的触摸监听器
}
return false; // 其他情况交给下层的触摸监听器处理
}
});

Edittext

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
//文本类型,多为大写、小写和数字符号。  
android:inputType="none"//输入普通字符
android:inputType="text"//输入普通字符
android:inputType="textCapCharacters"//输入普通字符
android:inputType="textCapWords"//单词首字母大小
android:inputType="textCapSentences"//仅第一个字母大小
android:inputType="textAutoCorrect"//前两个自动完成
android:inputType="textAutoComplete"//前两个自动完成
android:inputType="textMultiLine"//多行输入
android:inputType="textImeMultiLine"//输入法多行(不一定支持)
android:inputType="textNoSuggestions"//不提示
android:inputType="textUri"//URI格式
android:inputType="textEmailAddress"//电子邮件地址格式
android:inputType="textEmailSubject"//邮件主题格式
android:inputType="textShortMessage"//短消息格式
android:inputType="textLongMessage"//长消息格式
android:inputType="textPersonName"//人名格式
android:inputType="textPostalAddress"//邮政格式
android:inputType="textPassword"//密码格式
android:inputType="textVisiblePassword"//密码可见格式
android:inputType="textWebEditText"//作为网页表单的文本格式
android:inputType="textFilter"//文本筛选格式
android:inputType="textPhonetic"//拼音输入格式

//数值类型
android:inputType="number"//数字格式
android:inputType="numberSigned"//有符号数字格式
android:inputType="numberDecimal"//可以带小数点的浮点格式
android:inputType="phone"//拨号键盘
android:inputType="datetime"//日期+时间格式
android:inputType="date"//日期键盘
android:inputType="time"//时间键盘

maxlength控制读入最大长度

maxline控制显示最大行数

去掉下滑线background="@null"

textAlignment设置输入居中

EditText触点问题

setFocusable这个是用键盘是否能获得焦点
setFocusableInTouchMode这个是触摸是否能获得焦点

记事本类似实例,大范围点击即可获取某个输入框的焦点输入,并在键盘收起后失去焦点

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
//先导库
implementation 'net.yslibrary.keyboardvisibilityevent:keyboardvisibilityevent:3.0.0-RC3'
//代码
RelativeLayout relativeLayout = findViewById(R.id.relativelayout);
mEditText = findViewById(R.id.edittext_message);
relativeLayout.setOnClickListener(new View.OnClickListener() {
@SuppressLint("NewApi")
@Override
public void onClick(View view) {
//这三行代码是展开键盘的
mEditText.requestFocus();
InputMethodManager inputMethodManager = (InputMethodManager) mEditText.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
inputMethodManager.showSoftInput(mEditText,0);
}
});
KeyboardVisibilityEvent.setEventListener(GetActivity.this, new KeyboardVisibilityEventListener() {
@Override
public void onVisibilityChanged(boolean b) {
if (!b){
mEditText.clearFocus();
//清除焦点
}
}
});
//Edittext触点问题,现在不在xml和Java文件设置下面两个属性在进入界面时输入框也不会获取焦点,貌似默认都为true
setFocusable()这个是用键盘是否能获得焦点
setFocusableInTouchMode()这个是触摸是否能获得焦点

Edittext监听

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
editText.addTextChangedListener(new TextWatcher() {  

@Override
public void onTextChanged(CharSequence text, int start, int before, int count) {
//text 输入框中改变后的字符串信息
//start 输入框中改变后的字符串的起始位置
//before 输入框中改变前的字符串的位置 默认为0
//count 输入框中改变后的一共输入字符串的数量

}
//在这里可以设置一点击就让提示隐藏什么的,有时候是用一个大文本框居中提示就可以在

@Override
public void beforeTextChanged(CharSequence text, int start, int count,int after) {
//text 输入框中改变前的字符串信息
//start 输入框中改变前的字符串的起始位置
//count 输入框中改变前后的字符串改变数量一般为0
//after 输入框中改变后的字符串与起始位置的偏移量

}

@Override
public void afterTextChanged(Editable edit) {
//edit 输入结束呈现在输入框中的信息

}
});

ImageView

android:scaleType=“center”
(1)当图片大于ImageView的宽高:以图片的中心点和ImageView的中心点为基准,按照图片的原大小居中显示,不缩放,用ImageView的大小截取图片的居中部分。

(2)当图片小于ImageView的宽高:直接居中显示该图片。

2 android:scaleType=“centerCrop”

(1)当图片大于ImageView的宽高:以图片的中心点和ImageView的中心点为基准,按比例缩小图片,直到图片的宽高有一边等于ImageView的宽高,则对于另一边,图片的长度大于或等于ImageView的长度,最后用ImageView的大小居中截取该图片。

(2)当图片小于ImageView的宽高:以图片的中心店和ImageView的中心点为基准,按比例扩大图片,直到图片的宽高大于或等于ImageView的宽高,并按ImageView的大小居中截取该图片。

3 android:scaleType=“centerInside”

(1)当图片大于ImageView的宽高:以图片的中心和ImageView的中心点为基准,按比例缩小图片,使图片宽高等于或者小于ImagevView的宽高,直到将图片的内容完整居中显示。
(2)当图片小于ImageView的宽高:直接居中显示该图片。

4 android:scaleType=“fitCenter”

表示把图片按比例扩大(缩小)到ImageView的宽度,居中显示。
5 android:scaleType=“fitStart”

表示把图片按比例扩大(缩小)到ImageView的宽度,在ImageView的上方显示。

6 android:scaleType=“fitEnd”

表示把图片按比例扩大(缩小)到ImageView的宽度,在ImageView的下方显示。

7 android:scaleType=“fitXY”

表示把图片按指定的大小在ImageView中显示,拉伸或收缩图片,不保持原比例,填满ImageView。
8 android:scaleType=“matrix”
表示不改变原图的大小,从ImageView的左上角开始绘制原图,原图超过ImageView的部分作裁剪处理。 用矩阵来绘制,动态缩小放大图片来显示。

几种Button

1
2
3
4
//让Button中书写的英文不全为大写
android:textAllCaps="false
//取消Button边框
style="?android:attr/borderlessButtonStyle"

RadioButton和RadioGroup(本质是线性布局)

单选选择框,搭配RadioGroup使用,RadioButton通过android:checked属性判断是否选中,一开始都为不选中状态,一旦选中将无法取消,只能选择其他的,但个人感觉可以一开始设计选择第一个,如果用户不选择则默认选择第一个,这样子还可以避免判断是否选择的情况,但不知道交互上哪个更好

RadioGroup中一个按钮被选中后checked属性将为true,其他的button属性为false,android:button = ""属性设为@null则没有圆点了

要根据是否选中改变RadioButton的drawableTop的图片可以新建一个drawable文件设置给这个属性,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">

<item android:drawable="@drawable/home_icon_normal" android:state_checked="false"/>
<item android:drawable="@drawable/home_icon_checked" android:state_checked="true"/>

</selector>
//然后
android:drawableTop="@drawable/btn1_background"
//字体同样,运用即可
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">

<item android:color="@color/black" android:state_checked="false"/>
<item android:color="@color/blue_02C4FF" android:state_checked="true"/>

</selector>

使用方法:

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
46
47
48
49
50
51
52
53
54
55
56
57
//布局   
<RadioGroup
android:id="@+id/radio_group_gender"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_alignStart="@id/edittext_id"
android:layout_below="@id/textview_gander">

<RadioButton
android:id="@+id/radiobutton_gender_man"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="男"
android:textSize="20sp"/>

<RadioButton
android:id="@+id/radiobutton_gender_woman"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="女"
android:textSize="20sp"
android:layout_marginStart="25dp"/>

</RadioGroup>

//Activity中
private int gender;
RadioGroup radioGroup_gander = findViewById(R.id.radio_group_gender);
radioGroup_gender.setOnCheckedChangeListener(this);

@SuppressLint("NonConstantResourceId")
@Override
public void onCheckedChanged(RadioGroup radioGroup, int i) {
switch (i){
case R.id.radiobutton_gender_man:
gender = 0;
break;
case R.id.radiobutton_gender_woman:
gender = 1;
break;
default:
gender = -1;
}
}

//当然也可以不监听,因为监听是为了做更复杂的操作而去建议监听的,借助它只能2选1的特性
private int gender;
RadioButton radioButton_gender_man = findViewById(R.id.radiobutton_gender_man);
RadioButton radioButton_gender_woman = findViewById(R.id.radiobutton_gender_woman);
if (radioButton_gender_man.isChecked()){
gender = 0;
}
else if (radioButton_gender_woman.isChecked()){
gender = 1;
}
else gender = -1;

自定义

一个为灰色边框内部为白色,点击为蓝色打勾

1
2
3
4
5
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_checked="true" android:drawable="@drawable/bg_rbtn_activate_membership_selected"/>
<item android:state_enabled="false" android:drawable="@drawable/bg_rbtn_activate_membership_normal"/>
</selector>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">

<item android:drawable="@drawable/bg_rbtn_activate_membership_normal_bottom"/>

<item>
<shape android:shape="oval">
<stroke
android:color="@color/gray_979797"
android:width="1dp"/>
</shape>
</item>

</layer-list>
1
2
3
4
5
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M12,2C6.47,2 2,6.47 2,12s4.47,10 10,10 10,-4.47 10,-10S17.53,2 12,2z"/>
</vector>
1
2
3
4
5
<vector android:height="24dp" android:tint="#3C85FF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM10,17l-5,-5 1.41,-1.41L10,14.17l7.59,-7.59L19,8l-9,9z"/>
</vector>

Checkbox

支持多选的选择框

万能RecyclerView

GitHub

https://github.com/youlookwhat/ByRecyclerView/wiki

  • ListViewGridView 的加强版

  • 显示大量数据,减少内存占用量

类ListVeiw功能

  1. 先在布局文件添加RecyclerView控件,然后在Activity中拿到控件
  2. 然后准备数据,可以通过活动传给Adapter,也可以直接在Adapter里面直接准备,一般以第一种方式实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//首先要知道准备的是什么类型的数据,创建Bean类和List

//Bean类
public class Item {
private int image;
private String message;

public int getImage() {
return image;
}

public void setImage(int image) {
this.image = image;
}

public String getMessage() {
return message;
}

public void setMessage(String message) {
this.message = message;
}
}
1
2
3
4
5
6
7
8
9
10
//Activity中准备数据
List<Item> list = new ArrayList<>();
for (int i = 0; i < 30; i++) {
Item item = new Item();
item.setImage(R.mipmap.student);
item.setMessage("这是第"+i+"个条目");
list.add(item);
}
//调用自己写好的Adapter里面的方法
mAdapter.getData(list);
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
//然后要把每个条目的视图创建出来,就是一个layout布局文件
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">


<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">

<ImageView
android:id="@+id/imageview_item"
android:layout_width="100dp"
android:layout_height="100dp"
android:scaleType="fitXY"
android:padding="10dp"
android:src="@mipmap/student"/>

<TextView
android:id="@+id/textview_item"
android:layout_marginStart="20dp"
android:layout_width="250dp"
android:layout_height="100dp"
android:textSize="20sp"
android:textStyle="bold"
android:gravity="center"
android:padding="10dp"
android:text="条目"/>

</LinearLayout>

</RelativeLayout>
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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
                
//接下来创建适配器
/*ecyclerView.Adapter要求有ViewHolder,这里一般用内部类RecyclerViewAdapter.ViewHolder
就是创建适配器名.ViewHolder的内部类,这里先一键创建ViewHolder再构造Adapter,然后都是一键自动构造就行*/
public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder> {

private List<Item> list = new ArrayList<>();

/*
* 从外部得到数据
* */
public void getData(List<Item> list){
this.list = list;
}

/*
* 这个方法是用来创建内部的Holder的,就是每个条目的View,每个条目长什么样
* */
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {

ViewHolder viewHolder = new ViewHolder(View.inflate(parent.getContext(), R.layout.item, null));
return viewHolder;

}

/*
* 这个方法是绑定内部Holder的,一般用来设置数据
* */
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
holder.mImageView.setImageResource(list.get(position).getImage());
holder.mTextView.setText(list.get(position).getMessage());
}

/*
* 这个方法是用类设置数据数量的
* */
@Override
public int getItemCount() {
if (list!=null){
return list.size();
}
return 0;
}

public class ViewHolder extends RecyclerView.ViewHolder {

private ImageView mImageView;
private TextView mTextView;

public ViewHolder(@NonNull View itemView) {
super(itemView);

mImageView = itemView.findViewById(R.id.imageview_item);
mTextView = itemView.findViewById(R.id.textview_item);
}
}
}
1
2
3
4
5
6
7
//之后要创建LayoutManager,Adapter并把数据关联起来
RecyclerView recyclerView = findViewById(R.id.recyclerview);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(layoutManager);
mAdapter = new RecyclerViewAdapter();
intiData();//自己封装的准备和设置数据的方法
recyclerView.setAdapter(mAdapter);

DatePicker(时间选择器)

属性 描述和使用
android:datePickerMode="" 时间选择器有spinnercalendar两种模式,这个属性是用来设置模式的,spinner是直接选择日期,没有日历,calendar是带日历模式
android:calendarViewShown="" 这个属性是设置日历是否可见的,有truefalse可选,和模式一起设置成自己想要的,貌似有冲突
android:spinnersShown="" 这个属性是设置直接选择的框是否可见,有truefalse可选,和模式一起设置成自己想要的,貌似有冲突
android:calendarTextColor="" 设置日历列表文字的颜色
android:dayOfWeekTextAppearance 顶部星期几的文字颜色
android:endYear 去年(内容)
android:firstDayOfWeek 设置日历列表以星期几开头
android:headerBackground 整个头部的背景颜色
android:headerDayOfMonthTextAppearance 头部日期字体的颜色
android:headerMonthTextAppearance 头部月份的字体颜色
android:headerYearTextAppearance 头部年的字体颜色
android:maxDate 最大日期显示在这个日历视图mm / dd / yyyy格式
android:minDate 最小日期显示在这个日历视图mm / dd / yyyy格式
android:yearListItemTextAppearance 列表的文本出现在列表中。
android:yearListSelectorColor 年列表选择的颜色
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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=".MainActivity">



<DatePicker
android:id="@+id/datePickerView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:calendarViewShown="true"
android:datePickerMode="calendar"
android:layout_gravity="center_horizontal" />

</android.support.constraint.ConstraintLayout>
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
package com.example.user.datapicker;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.DatePicker;
import android.widget.Toast;

import java.util.Calendar;

public class MainActivity extends AppCompatActivity {
DatePicker datePicker;

int year,mouth,day,hour,min;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

datePicker = (DatePicker)findViewById(R.id.datePickerView);

// get time,可以在oncreate方法中初始化完成成为全局变量再在监听方法里面使用,这个是Calender模式下使用方法
Calendar cal = Calendar.getInstance();
year = cal.get(Calendar.YEAR);
mouth = cal.get(Calendar.MONTH);
day = cal.get(Calendar.DAY_OF_MONTH);
hour = cal.get(Calendar.HOUR);
min = cal.get(Calendar.MINUTE);

datePicker.init(year, mouth, day, new DatePicker.OnDateChangedListener() {
@Override
public void onDateChanged(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
MainActivity.this.year = year;
MainActivity.this.mouth = monthOfYear;
MainActivity.this.day = dayOfMonth;

//显示用户选择的日期
Toast.makeText(MainActivity.this,year + "年" + monthOfYear + "月" + dayOfMonth + "日",Toast.LENGTH_LONG).show();
}
});

}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//Spnner模式,用这个啊,监听蛇皮
DatePicker datePicker = findViewById(R.id.date_picker);
year = datePicker.getYear();
month = datePicker.getMonth();
day = datePicker.getDayOfMonth();


StringBuffer date = new StringBuffer();
date.append(year);
date.append("年");
date.append(month + 1);//月份一开始从0开始,要加1
date.append("月");
date.append(day);
date.append("日");

####ViewPager2(轮播图)

GitHub

https://github.com/zhpanvip/BannerViewPager/wiki/06.快速开始//轮播图

implementation 'com.github.zhpanvip:bannerviewpager:3.5.11//依赖不能按照作者那里写latestVersion,Item的布局必须是”match_parent,嵌套在ScrollView里面会变形

https://github.com/zhpanvip/viewpagerindicator//轮播图指示器

  • 滑动视图或者滑动Fragment
  • 放Fragment继承FragmentStateAdapter,放图片继承RecyclerView.Adapter<ViewHolder>,监听方法registerOnPageChangeCallback

(26条消息) 学不动也要学! ViewPager2新特性_程序员巴士的博客-CSDN博客_viewpager2滑动监听

1
//去掉边缘阴影,上下暂时没找到去掉的方法,可以把theme里面颜色换掉
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
46
47
48
49
50
//首先先导入ViewPager2的库
implementation "androidx.viewpager2:viewpager2:1.0.0"



/*ViewPager2只相当于一个容器,可以放View也可以放Fragment,要放置什么数据需要我们去设置,
这时候就需要我们去写一个Adapter适配器去设置要显示的内容*/
/*ViewPager2是根据RecyclerView改变过来的,这里继承的方法也跟RecyclerView的适配器创建方法类似,
* 这里需要传入一个<>范型,这里设置内部类去构建就可以,构建方法就是最后那个方法*/
public class ViewPagerAdapter extends RecyclerView.Adapter<ViewPagerAdapter.ViewHolder> {

private List<Integer> list = new ArrayList<>();

//在内部设置一个方法用来从Activity获取数据给Adapter,当然也可以直接在Adapter里面设置数据
public void getData(List<Integer> list){
this.list = list;
}

//这个方法是用来获取一个View的,新建一个xml文件获取过来,这样子在下面的内部类ViewHolder就可以获取到View里面的控件
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ViewHolder viewHolder = new ViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_viewpager,parent,false));
return viewHolder;
}

//这个方法是根据position设置view的,这里用position%list。size()实现循环
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
holder.mImageView.setImageResource(list.get(position%list.size()));
}

@Override
public int getItemCount() {
if (list!=null){
return Integer.MAX_VALUE;
}
return 0;
}

public class ViewHolder extends RecyclerView.ViewHolder {

ImageView mImageView;

public ViewHolder(@NonNull View itemView) {
super(itemView);
mImageView = itemView.findViewById(R.id.imageview);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//布局,其中的LinearLayout放置点的
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".SecondActivity">

<androidx.viewpager2.widget.ViewPager2
android:id="@+id/viewpager2"
android:layout_width="300dp"
android:layout_height="200dp"
android:layout_centerInParent="true" />

<LinearLayout
android:id="@+id/linear"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:layout_below="@id/viewpager2"
android:layout_centerHorizontal="true"
android:orientation="horizontal" />

</RelativeLayout>
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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
//Java文件
public class SecondActivity extends AppCompatActivity {

private Handler handler;
private ViewPager2 mViewPager2;
private Runnable mRunnable;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);

initView();
}

@SuppressLint("UseCompatLoadingForDrawables")
private void initView() {

LinearLayout linear = findViewById(R.id.linear);
mViewPager2 = findViewById(R.id.viewpager2);
ViewPagerAdapter viewPagerAdapter = new ViewPagerAdapter();

//设置数据
List<Integer> list = new ArrayList<>();
list.add(R.mipmap.pic1);
list.add(R.mipmap.pic2);
list.add(R.mipmap.pic3);
list.add(R.mipmap.pic4);
list.add(R.mipmap.pic5);

//根据图片个数设置点
for (int i = 0; i < list.size(); i++) {
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(40,40);
View view = new View(this);
view.setBackground(getDrawable(R.drawable.point_viewpager_normal));
view.setLayoutParams(layoutParams);
layoutParams.setMarginStart(20);
linear.addView(view);
}

//给ViewPager2设置监听,可优化,自己优化吧
mViewPager2.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
super.onPageScrolled(position, positionOffset, positionOffsetPixels);
for (int i = 0; i < list.size(); i++) {
if (i==position%list.size()){
linear.getChildAt(i).setBackground(getDrawable(R.drawable.point_viewpager_checked));
}else{
linear.getChildAt(i).setBackground(getDrawable(R.drawable.point_viewpager_normal));
}
}
}
});

//给适配器传数据
viewPagerAdapter.getData(list);
mViewPager2.setAdapter(viewPagerAdapter);
mViewPager2.setCurrentItem(200,false);

//设置不可人为滑动,这样子不用再去写人为滑动时停止轮播了
mViewPager2.setUserInputEnabled(false);

//handler里面的Runable再执行一次Handler实现循环
handler = new Handler();
mRunnable = ()->{
int current = mViewPager2.getCurrentItem();
mViewPager2.setCurrentItem(++current);

handler.postDelayed(mRunnable,2000);
};
}

//界面显示开始轮播
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
handler.postDelayed(mRunnable,1000);
}

//界面进入后台停止轮播
@Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
handler.removeCallbacks(mRunnable);
}
}

TabLayout

属性 用法
app:tabMode="scrollable" **fixed固定的,也就是标题不可滑动,无论放了多少个都平分界面长度, scrollable可滑动的,小于等于5个默认靠左固定,大于5个就可以滑动了,auto**自动选择是否可以滑动,小于等于5个默认居中,大于5个自动滑动
app:tabGravity **start居左,fill平均分配,铺满屏幕宽度center**居中,默认fill,tab填满TabLayout,但tabMode=“fixed”才生效
<attr name="tabTextColor" format="color"/> Tab未选中字体颜色
<attr name="tabSelectedTextColor" format="color"/> Tab选中字体颜色
<attr name="tabMinWidth" format="dimension"/> Tab最小宽度
attr name="tabIndicatorColor" format="color" 指示器颜色
attr name="tabIndicatorHeight" format="dimension" 指示器高度
attr name="tabIndicatorFullWidth" format="boolean" 指示器宽度 true:和tab同宽 false:和tab中的字同宽
attr name="tabBackground" format="reference" 仅是Tab背景,设置TabLayout背景用android:background
attr name="tabContentStart" format="dimension" tabs距TabLayout开始位置的偏移量,但app:tabMode="scrollable"才生效

Java中添加Tab TabLayout.addTab(TabLayout.newTab().setText("222"));,注意addTab里面传的参数是控件名.newTab()

Java中设置某个被选中,其他会自动不被选中,代码为TabLayout.getTabAt(1).select();

ProgressBar

进度条

Spinner(下拉选择框,Android微调器)

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
//标签
<Spinner
//下面这个androidx兼容
<androidx.appcompat.widget.AppCompatSpinner

//使用开源库实现更多功能
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
maven { url "https://jitpack.io" }
}
}//在gradle设置中下面那部分去加,不行把mavenCentral()这个删掉然后同步完再加回来

/*arrowTint设置下拉箭头上的颜色
hideArrow设置是显示还是隐藏下拉箭头
arrowDrawable设置下拉箭头的可绘制对象
textTint设置文本颜色
dropDownListPaddingBottom设置下拉列表的底部填充
backgroundSelector设置下拉列表行的背景选择器
popupTextAlignment设置默认弹出文本的水平对齐方式
entries从字符串数组设置数据源*/

//绑定数据方法
NiceSpinner niceSpinner = (NiceSpinner) findViewById(R.id.nice_spinner);
List<String> dataset = new LinkedList<>(Arrays.asList("One", "Two", "Three", "Four", "Five"));
niceSpinner.attachDataSource(dataset);

//监听
spinner.setOnSpinnerItemSelectedListener(new OnSpinnerItemSelectedListener() {
@Override
public void onItemSelected(NiceSpinner parent, View view, int position, long id) {
// This example uses String, but your type can be any
String item = parent.getItemAtPosition(position);
...
}
});

三种使用方式

第一种:

数据固定的,通过 android:entries 属性引数组来进行填充,在 values 下建一个 arrays.xml ,在这里面填充,或者直接在values包下strings.xml的resources标签下添加,实例在下面代码框

android:spinnerMode=”dialog”表示Spinner的样式是dialogandroid:prompt可以设置dialog的标题(注意必须在string资源下引用,不然程序会崩掉

1
2
3
4
<string-array name="city_name">
<item>湛江</item>
<item>北京</item>
</string-array>

第二种:

通过适配器来填充参数,继承BaseAdapter

Android之Spinner用法详解_Android_脚本之家 (jb51.net)

第三种:

实现SpinnerAdaper接口,其实跟继承BaseAdapter差不多

设置样式

values包下新建style.xml用来专门保存样式,按钮什么的样式也可以集中放到这里来

不知道什么原因,自带spinner生效不了,nice—spinner可以生效

1
2
3
4
5
6
    <style name="CitySpinnerStyle">
<item name="android:divider">#A9A9A9</item>//下划线
<item name="android:dividerHeight">2dp</item>//下划线宽度
</style>

//android:theme="@style/CitySpinnerStyle"在xml进行设置

NestedScrollView和ScrollView

NestedScrollView能够判断滚动视图完成哪个,滑动顺畅,使用这个即可

Activity相关

android:exported = true

true表示可以被其他Activity启动

生命周期相关

1)横竖屏切换对Activiy生命周期影响
横竖屏切换涉及到的是Activity的android:configChanges属性:
android:configChanges可以设置的属性值有:
orientation:消除横竖屏的影响
keyboardHidden:消除键盘的影响
screenSize:消除屏幕大小的影响
【情况1】
1、android:configChanges=orientation
2、android:configChanges=orientation|keyboardHidden
3、不设置android:configChanges
横竖屏切换Activity生命周期变化:
onPause–>onSaveInstanceState–>onStop–>onDestroy–>onCreate–>onStart–>onRestoreInstanceState–>onResume
在进行横竖屏切换的时候在调用onStop之前会调用onSaveInstanceState来进行Activity的状态保存,随后在重新显示该Activity的onResume方法之前会调用onRestoreInstanceState来恢复之前由onSaveInstanceState保存的Activity信息
【情况2】
1、android:configChanges=orientation|screenSize
2、android:configChanges=orientation|screenSize|keyboardHidden
横竖屏切换不会重新加载Activity的各个生命周期,一定要同时出现orientation和screenSize
【情况3】
屏蔽横竖屏切换操作,不会出现切换的过程中Activity生命周期重新加载的情况
方法1:清单文件
android:screenOrientation=“portrait” 始终以竖屏显示
android:screenOrientation=“landscape” 始终以横屏显示
方法2:动态Activity
Activity.this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);以竖屏显示
Activity.this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);以横屏显示

跳转传值

  1. 普通跳转和跳转网页
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//在内部方法中才需要Activity.this,不然.this就好
//直接在onCreate方法中写
//显性跳转自己这个App界面
Intent intent = new Intent(this,MainActivity2.class);
startActivity(intent);


//跳转网页或者其他APP界面(隐式跳转)
//再Manifest文件中设置
Intent intent = new Intent();
intent.setAction();//隐性跳转必须有这个
intent.addCategory();/*隐性跳转可以不设置这个,系统会使用默认值,所以要使匹配一样就必须在Manifest文件中设置这个属性为默认或者你想要的值*/
intent.setData();/*跳转网页用这个,并设置setAction为Intent.ACTION_VIEW,intent.setData(Uri.parse("https://www.baidu.com"));*/


//Manifest设置属性,下面这个复制在哪个Activity,哪个就作为先启动的,属性也在这个属性里面设置
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>

  1. 带值传递不需要处理返回值
1
2
3
4
5
6
7
8
9
10
11
//传值
Intent intent = new Intent(this,MainActivity2.class);
Bundle bundle = new Bundle();
bundle.putInt("result",1);
intent.putExtras(bundle);
startActivity(intent);

//接收值
Intent intent = getIntent();
Bundle bundle = intent.getExtras();
int result = bundle.getInt("result");

Bundle相关

Bundle在Android开发中非常常见,它的作用主要时用于传递数据。Bundle传递的数据包括:string、int、boolean、byte、float、long、double等基本类型或它们对应的数组,也可以是对象或对象数组。当Bundle传递的是对象或对象数组时,必须实现Serialiable或Parcelable接口。
Bundle所保存的数据是以key-value(键值对)的形式保存在ArrayMap中,处理少量简单数据比hashmap快,处理大量数据用hashmap

Bundle使用场景
Activity之onSaveInstanceState
Activity状态数据的保存与恢复,涉及到两个回调:
①void onSaveInstanceState(Bundle outState);② void onCreate(Bundle savedInstanceState);
参考:Android系统之onSaveInstanceState用法及源码分析

Fragment之setArguments
Fragment的setArguments方法:void setArgument(Bundle args);
参考:Android系统之Fragment用法

Handle之setData
消息机制中的Message的setData方法:void setData(Bundle data)。
参考:Android系统线程间通信方式之Handler机制

  1. 带值传递且需要处理返回值(登陆界面跳转注册界面注册成功带账号密码返回登陆界面)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*startActivityForResult方法弃用了,用registerForActivityResult方法,这个方法只能在oncreate方法中先构造,不能在onstart方法后再构造,也不能在注册监听事件的onclick方法中构造*/
private ActivityResultLauncher<Intent> intentActivityResultLauncher;

//以下可以写在一个内部方法中,然后在oncreate方法中调用内部方法,如果跳转活动有传值,那么在返回这个活动时会调用
intentActivityResultLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result1 -> {
int code = result1.getResultCode();
Intent data = result1.getData();
});

//然后在监听事件中调用全局变量跳转。launch方法
Bundle bundle = new Bundle();
bundle.putInt("result", result);
Intent intent = new Intent(LoginActivity.this, RegisterActivity.class);
intent.putExtras(bundle);
intentActivityResultLauncher.launch(intent);

//在跳转的活动中,用setResult方法回传数据和结果码
Intent intent = new Intent();
Bundle bundle = new Bundle();
bundle.putString("username",username);
bundle.putString("password",password);
intent.putExtras(bundle);
setResult(88,intent);
finish();//最后完成所有操作关闭这个界面(如网络请求登陆成功后)

传递对象

Serializable进行对象序列化,这种序列化是通过反射机制从而削弱了性能,这种机制创建了大量的临时对象从而会引起GC频繁回收调用资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//发送数据				
Person person = new Person();
person.setName("chenjy");
person.setAge(18);

Bundle bundle = new Bundle();
bundle.putSerializable("person",person);

Intent intent = new Intent(MainActivity.this, SecondActivity.class);
intent.putExtras(bundle);
startActivity(intent);


//获取数据
Person person = (Person)getIntent().getSerializableExtra("person");

Parcelable是由Android提供的序列化接口,google做了大量的优化

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
46
47
48
49
50
51
52
53
54
55
56
public class Person implements Parcelable {

private String name;
private int age;

public Person() {}

protected Person(Parcel in) {
name = in.readString();
age = in.readInt();
}

public void setName(String name){
this.name = name;
}

public void setAge(int age){
this.age = age;
}

public String getName(){
return name;
}

public int getAge(){
return age;
}


public static final Creator<Person> CREATOR = new Creator<Person>() {
@Override
public Person createFromParcel(Parcel in) {
Person person = new Person();
person.name = in.readString();
person.age = in.readInt();
return person;
}

@Override
public Person[] newArray(int size) {
return new Person[size];
}
};

@Override
public int describeContents() {
return 0;
}

@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeInt(age);
}
}

传输数据量较大的时候Parcelable会出现异常TransactionTooLargeException。只时候就需要用到插件EventBus。

EventBus 使用的是发布 订阅者模型,发布者通过EventBus发布事件,订阅者通过EventBus订阅事件。当发布者发布事件时,订阅该事件的订阅者的事件处理方法将被调用。(后续使用到再看)

Fragment相关

view.findViewById()

####Fragment和其他控件一起使用实现滑动点击切换

构造Fragment实例

构造Fragment实例的时候可以直接new出来,也可以用生成Fragment自带的newInstance()方法,这个方法还可以传值,传的值可以直接在Fragment操作

FragmentContainerView控件绑定Fragment和普通按钮布局实现点击切换

点击改变颜色和放置不同图片什么都没什么,主要是怎么切换fragment,切换fragment就只要在点击按钮时改变传入的Fragment实例化对象即可

1
2
3
4
5
6
7
8
//获取FragmentManager
FragmentManager fragmentManager = getSupportFragmentManager();
//这个方法构造Fragment可以传值
FirstFragment fragment = FirstFragment.newInstance("这是首页","");
//添加Fragment到FragmentManager
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.fragment_container,fragment).commit();
//第一个参数传要绑定到这个Activity界面的FragmentContainerView控件的id,第二个参数传Fragment实例

Fragment和ViewPager2实现滑动切换效果,把Fragment换为颜色去掉底部按键就是滑动

设置可滑动就是微信那种,不可滑动就是QQ那种,注意手势冲突

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
//Java
public class ThirdActivity extends AppCompatActivity implements RadioGroup.OnCheckedChangeListener {

private ViewPager2 mViewPager2;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_third);

initView();
}

private void initView() {
RadioButton btn1 = findViewById(R.id.btn1);
RadioButton btn2 = findViewById(R.id.btn2);
RadioButton btn3 = findViewById(R.id.btn3);
RadioGroup radioGroup = findViewById(R.id.radio_group);
radioGroup.setOnCheckedChangeListener(this);
mViewPager2 = findViewById(R.id.viewpager2);
ViewPagerTopAdapter viewPagerTopAdapter = new ViewPagerTopAdapter(this);
viewPagerTopAdapter.getFragment(new FirstFragment());
viewPagerTopAdapter.getFragment(new SecondFragment());
viewPagerTopAdapter.getFragment(new ThirdFragment());
mViewPager2.setAdapter(viewPagerTopAdapter);
mViewPager2.setCurrentItem(0);

/*如果设置不可滑动就不需要监听ViewPager2了
mViewPager2.setUserInputEnabled(false);*/

//监听滑动为第几项设置哪个按钮被选中
mViewPager2.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
@Override
public void onPageSelected(int position) {
super.onPageSelected(position);
switch (position){
case 0:
btn1.setChecked(true);

break;
case 1:
btn2.setChecked(true);

break;
case 2:
btn3.setChecked(true);

break;
}
}
});


}

//监听按钮选择而设置Fragment
@Override
public void onCheckedChanged(RadioGroup radioGroup, int i) {
switch (i){
case R.id.btn1:
mViewPager2.setCurrentItem(0);
break;
case R.id.btn2:
mViewPager2.setCurrentItem(1);
break;
case R.id.btn3:
mViewPager2.setCurrentItem(2);
break;
}
}
}
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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
//xml布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".ThirdActivity">

<androidx.viewpager2.widget.ViewPager2
android:id="@+id/viewpager2"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />


<View
android:layout_width="match_parent"
android:layout_height="2dp"
android:background="@color/gray_dark" />


<RadioGroup
android:id="@+id/radio_group"
android:layout_width="match_parent"
android:layout_height="80dp"
android:layout_alignParentBottom="true"
android:orientation="horizontal">

<RadioButton
android:id="@+id/btn1"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:button="@null"
android:checked="true"
android:drawableTop="@drawable/btn1_background"
android:gravity="center"
android:text="首页"
android:textColor="@drawable/change_radio_button" />

<RadioButton
android:id="@+id/btn2"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:button="@null"
android:drawableTop="@drawable/btn2_background"
android:gravity="center"
android:text="联系人"
android:textColor="@drawable/change_radio_button" />

<RadioButton
android:id="@+id/btn3"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:button="@null"
android:drawableTop="@drawable/btn3_background"
android:gravity="center"
android:text="设置"
android:textColor="@drawable/change_radio_button" />

</RadioGroup>

</LinearLayout>
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
//Adapter适配器
//放Fragment继承FragmentStateAdapter
public class ViewPagerTopAdapter extends FragmentStateAdapter {

//先记住这种用法吧,先把Fragmen的类获取过来,后面直接newInstance返回出去
private List<Class> list = new ArrayList<>();

public void getFragment(Fragment fragment){
list.add(fragment.getClass());

}

public ViewPagerTopAdapter(@NonNull FragmentActivity fragmentActivity) {
super(fragmentActivity);

}

@NonNull
@Override
public Fragment createFragment(int position) {
try {
return (Fragment) list.get(position).newInstance();
} catch (IllegalAccessException | InstantiationException e) {
e.printStackTrace();
}
return null;
}

@Override
public int getItemCount() {
return list.size();
}
}

TabLayout+ViewPager2

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
//java
package com.example.fragmentstudy;

import android.os.Bundle;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.viewpager2.widget.ViewPager2;

import android.text.style.AlignmentSpan;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import com.example.fragmentstudy.Adapter.ViewPagerTopAdapter;
import com.example.fragmentstudy.Fragment.BlankFragment1;
import com.example.fragmentstudy.Fragment.BlankFragment2;
import com.example.fragmentstudy.Fragment.BlankFragment3;
import com.google.android.material.tabs.TabLayout;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

public class HomeFragment extends Fragment {

private ViewPager2 mViewPager2;
private TabLayout mTabLayout;

public HomeFragment() {
// Required empty public constructor
}

public static HomeFragment newInstance() {
HomeFragment fragment = new HomeFragment();
Bundle args = new Bundle();
fragment.setArguments(args);
return fragment;
}

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_home, container, false);
}

@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mViewPager2 = view.findViewById(R.id.viewpager2);
mTabLayout = view.findViewById(R.id.tab_layout);
initTab();
initData();
}

private void initData() {
//传参在Activity直接this,在Fragment中this.requireActivity()
ViewPagerTopAdapter viewPagerTopAdapter = new ViewPagerTopAdapter(this.requireActivity());
viewPagerTopAdapter.getFragment(new BlankFragment1());
viewPagerTopAdapter.getFragment(new BlankFragment2());
viewPagerTopAdapter.getFragment(new BlankFragment3());
mViewPager2.setAdapter(viewPagerTopAdapter);

//监听ViewPager2,设置Tab选中
mViewPager2.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
@Override
public void onPageSelected(int position) {
super.onPageSelected(position);
mTabLayout.getTabAt(position).select();
}
});
}

//设置TabLayout的Tab
private void initTab() {
String[] name = {"汽车", "美食", "趣闻"};
for (int i = 0; i < 3; i++) {
mTabLayout.addTab(mTabLayout.newTab().setText(name[i]));
}

//监听TabLayout,第一个方法有用而已
mTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
mViewPager2.setCurrentItem(tab.getPosition());
}

@Override
public void onTabUnselected(TabLayout.Tab tab) {

}

@Override
public void onTabReselected(TabLayout.Tab tab) {

}
});
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//xml布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".HomeFragment"
android:orientation="vertical">

<com.google.android.material.tabs.TabLayout
android:id="@+id/tab_layout"
android:layout_width="match_parent"
android:layout_height="50dp"
app:tabMode="auto"/>

<androidx.viewpager2.widget.ViewPager2
android:id="@+id/viewpager2"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>

</LinearLayout>
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
//Adapter适配器
//放Fragment继承FragmentStateAdapter
public class ViewPagerTopAdapter extends FragmentStateAdapter {

//先记住这种用法吧,先把Fragmen的类获取过来,后面直接newInstance返回出去
private List<Class> list = new ArrayList<>();

public void getFragment(Fragment fragment){
list.add(fragment.getClass());

}

public ViewPagerTopAdapter(@NonNull FragmentActivity fragmentActivity) {
super(fragmentActivity);

}

@NonNull
@Override
public Fragment createFragment(int position) {
try {
return (Fragment) list.get(position).newInstance();
} catch (IllegalAccessException | InstantiationException e) {
e.printStackTrace();
}
//记得return null;
return null;
}

@Override
public int getItemCount() {
return list.size();
}
}

玩Android项目开发7—–项目页面(使用ViewPager 2 + TabLayout实现项目页面)_tablayout.mode_auto-CSDN博客

Service服务

这里以一个简单的音乐播放器引入

activity_main.xml

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
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">

<Button
android:id="@+id/btn_play_or_stop"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginStart="40dp"
android:text="播放/暂停"
android:textSize="20sp" />

<SeekBar
android:id="@+id/skb_play"
android:layout_width="match_parent"
android:paddingStart="20dp"
android:paddingEnd="20dp"
android:layout_height="wrap_content"
android:layout_below="@id/btn_play_or_stop"
android:layout_centerHorizontal="true"
android:layout_marginTop="20dp"/>

</RelativeLayout>

在assests资源文件夹下放一首mp3音乐,命名为test_music.mp3

MyTestMusicService

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
public class MyTestMusicService extends Service {
private MediaPlayer mMediaPlayer;

public MyTestMusicService() {
}

@Override
public IBinder onBind(Intent intent) {
//当执行完了onCreate后,就会执行onBind把操作歌曲的方法返回
return new MyBinder();
}

@Override
public void onCreate() {
super.onCreate();
//这里只执行一次,用于准备播放器
mMediaPlayer = new MediaPlayer();
try {
AssetManager assetManager = getAssets();
AssetFileDescriptor assetFileDescriptor = assetManager.openFd("test_music.mp3");
mMediaPlayer.setDataSource(assetFileDescriptor.getFileDescriptor(), assetFileDescriptor.getStartOffset(), assetFileDescriptor.getLength());
//准备资源
mMediaPlayer.prepare();
} catch (IOException e) {
e.printStackTrace();
}
Log.i("TAGG", "准备播放音乐");
}

//该方法包含关于歌曲的操作
public class MyBinder extends Binder {
//判断是否属于播放状态
public boolean isPlaying() {
return mMediaPlayer.isPlaying();
}

//播放或暂停歌曲
public void play() {
if (isPlaying()) {
mMediaPlayer.pause();//暂停
Log.i("TAGG", "音乐暂停播放");
} else {
mMediaPlayer.start();//播放
Log.i("TAGG", "音乐继续播放");
}
}

//返回歌曲的长度,单位为毫秒
public int getDuration() {
return mMediaPlayer.getDuration();
}

//返回歌曲目前的进度,单位为毫秒
public int getCurrentPosition() {
return mMediaPlayer.getCurrentPosition();
}

//设置歌曲播放的进度,单位为毫秒
public void seekTo(int mesc) {
mMediaPlayer.seekTo(mesc);
}
}
}

MainActivity

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
public class MainActivity extends AppCompatActivity {

private Button btnPlayOrStop;
private SeekBar skb;

private ServiceConnection mServiceConnection;

private MyTestMusicService.MyBinder musicController;

private Handler mHandler;//定时更新播放进度

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
initEvent();
}

private void initView() {
btnPlayOrStop = findViewById(R.id.btn_play_or_stop);
btnPlayOrStop.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
play();
}
});
skb = findViewById(R.id.skb_play);
skb.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
if (b){
musicController.seekTo(i);//进度条改变
}
}

@Override
public void onStartTrackingTouch(SeekBar seekBar) {

}

@Override
public void onStopTrackingTouch(SeekBar seekBar) {

}
});
}

private void initEvent() {
mHandler = new Handler(getMainLooper(), new Handler.Callback() {
@Override
public boolean handleMessage(@NonNull Message message) {
if (message.what == 0) {
updatePlayPosition();//更新播放进度
}
return false;
}
});
startMusicService();
}


/**
* 启动服务
*/
private void startMusicService() {
Intent intent = new Intent(this, MyTestMusicService.class);
startService(intent);
Toast.makeText(this, "启动服务", Toast.LENGTH_SHORT).show();
bindMusicService();
}

/**
* 停止服务
*/
private void stopMusicService() {
unBindMusicService();
Intent intent = new Intent(this, MyTestMusicService.class);
stopService(intent);
Toast.makeText(this, "停止服务", Toast.LENGTH_SHORT).show();
}

/**
* 绑定服务
*/
private void bindMusicService() {
Intent intent = new Intent(this, MyTestMusicService.class);
//创建连接对象
if (mServiceConnection == null) {
mServiceConnection = new ServiceConnection() {
//与服务连接上回调
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
Log.i("TAGG", "绑定服务");
musicController = (MyTestMusicService.MyBinder) iBinder;//绑定服务后获取Binder
//更新按钮的文字
btnPlayOrStop.setText("播放");
//设置进度条最大值
skb.setMax(musicController.getDuration());
//设置进度条的进度
skb.setProgress(musicController.getCurrentPosition());
}

//不会执行,不需理会
@Override
public void onServiceDisconnected(ComponentName componentName) {

}
};
//绑定服务
//BIND_AUTO_CREATE创建后自动绑定
bindService(intent, mServiceConnection, BIND_AUTO_CREATE);
} else {
Toast.makeText(this, "已经绑定服务", Toast.LENGTH_SHORT).show();
}
}

/**
* 解绑服务
*/
private void unBindMusicService() {
if (mServiceConnection != null) {
unbindService(mServiceConnection);
mServiceConnection = null;
Log.i("TAGG", "接绑服务");
} else {
Toast.makeText(this, "还未绑定服务", Toast.LENGTH_SHORT).show();
}
}

/**
* 更新按钮的文字
*/
private void updatePlayTest() {
if (musicController.isPlaying()) {
btnPlayOrStop.setText("播放");
mHandler.sendEmptyMessageDelayed(0, 500);
} else {
btnPlayOrStop.setText("暂停");
}
}

/**
* 播放或暂停
*/
private void play(){
updatePlayTest();
musicController.play();
}

/**
* 更新进度条
*/
private void updatePlayPosition() {
skb.setProgress(musicController.getCurrentPosition());
mHandler.sendEmptyMessageDelayed(0, 500);//500ms更新一次进度条
}

/**
* 进入界面后重新更新进度条
*/
@Override
protected void onResume() {
super.onResume();
if (musicController!=null){
mHandler.sendEmptyMessage(0);
}
}

/**
* 停止更新进度条
*/
@Override
protected void onStop() {
super.onStop();
mHandler.removeCallbacksAndMessages(null);
}

/**
* 当绑定服务后,不解绑服务直接退出,会有错误抛出,所以需要在当前Activity页面销毁的时候,执行解绑服务
*/
@Override
protected void onDestroy() {
super.onDestroy();
if (mServiceConnection != null) {
stopMusicService();
}
}
}

SharedPreferences

1
2
3
4
5
6
7
8
9
10
//存数据
SharedPreferences.Editor qq_xml=getSharedPreferences("qq_xml",MODE_PRIVATE).edit();
qq_xml.putString("number",number_qq);
qq_xml.putString("password",pass1_qq);
qq_xml.apply();

//读数据
SharedPreferences nw_qq=getSharedPreferences("qq_xml",MODE_PRIVATE);
String number=nw_qq.getString("number","");
String pasword=nw_qq.getString("password","");
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
46
47
//记住密码
package com.example.databasestudy.Class;

import android.content.Context;
import android.content.SharedPreferences;

import java.util.HashMap;
import java.util.Map;

public class SP {

private SharedPreferences.Editor edit;
private SharedPreferences mSharedPreferences;

//要获取某个界面的sp,传入上下文
public void getSP(Context context){
//getSharedPreferences存在就读取,不存在创建,后面传名字和打开方式
mSharedPreferences = context.getSharedPreferences("data_1", Context.MODE_PRIVATE);
//需要构造editor,用edit方法构造
edit = mSharedPreferences.edit();
}

public void save(String username,String password,String isRememberPassword){
edit.putString("username",username);
edit.putString("password",password);
edit.putString("isRememberPassword",isRememberPassword);
//apply方法是在何时时候放入数据库,速度快,还有edit.commit();,在当时就存储进去
edit.apply();
}

public void clear(String isRememberPassword){
if (isRememberPassword.equals("NO")){
edit.clear();
edit.putString("isRememberPassword", "NO");
edit.apply();
}
}

public Map<String,String> read(){
Map<String,String> data = new HashMap<>();
//读取不需要editor实例
data.put("username",mSharedPreferences.getString("username",""));
data.put("password",mSharedPreferences.getString("password",""));
data.put("isRememberPassword",mSharedPreferences.getString("isRememberPassword","NO"));
return data;
}
}

SQLite

存储类 描述
NULL 值是一个 NULL 值。
INTEGER 值是一个带符号的整数,根据值的大小存储在 1、2、3、4、6 或 8 字节中。
REAL 值是一个浮点值,存储为 8 字节的 IEEE 浮点数字。
TEXT 值是一个文本字符串,使用数据库编码(UTF-8、UTF-16BE 或 UTF-16LE)存储。
BLOB 值是一个 blob 数据,完全根据它的输入存储。

数据库事务:

安全性、高效性

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
//事物的安全性
//beginTransaction();//开启事务,如果事务没有成功,数据库不会改变
//setTransactionSuccessful();//数据库事务成功
//endTransaction();//关闭事物

public long insert(String number, String name, String grade, String gender){

SQLiteDatabase db = getWritableDatabase();
db.beginTransaction();//开启事务,如果事务没有成功,数据库不会改变
try {
ContentValues values = new ContentValues();//其实也是键值对
values.put("number",number);
values.put("name",name);
values.put("grade",grade);
values.put("gender",gender);
long l = db.insert(WE,null,values);//第二个参数表示为空时链接到哪,不知道就先写空
db.setTransactionSuccessful();//数据库事务成功
return l;
}catch (Exception e){
throw new RuntimeException("中途出错!");
}finally {
db.endTransaction();
db.close();
}
}
//事物的安全性
//使用普通方法添加大量数据和使用事务来添加大量的事物来对比时间,以此来看选择谁,
//由于开启事物是把要存储的内容先存储到内存,然后一次性写入,如果是普通方法就是打开数据库,存数据,关闭数据库这样一个操作,非常耗时间
//然后创建数据库时除了本身的文件还额外创建了一个文件,这个文件就是当空间不够或者说文件太大时就会存到这个wenji

构建传参时可以定死名字工厂和版本,就传界面进行构建

1
2
3
4
5
public static String DB_NAME = "MySQLite.db";

public SQLite(Context context){
super(context,DB_NAME,null,1);
}
1
2
3
4
//完整的构造方法,有许多种
public SQLite(@Nullable Context context, @Nullable String name, @Nullable SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
}

建表语句,一个数据库可以有很多个表,下面还有更新方法,没学

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    //onCreate在数据库创建时调用,只调用一次
@Override
public void onCreate(SQLiteDatabase sqLiteDatabase) {
String create = "create table " + WE
+" (" + "id integer primary key autoincrement,"
+"number text,"
+"name text,"
+"grade ,"
+"gender text" + ")";
sqLiteDatabase.execSQL(create);
}
//注意是sqLiteDatabase执行建表语句,而不是getWritableDatabase()对象执行,表名一定不能重复,重复调用语句注意传参
//也就是先写出数据库专门的建表语句,然后执行as自带的数据库建表方法
//数据库专门建表语句: create table 表名 (id integer primary key autoincrement,number text,... )
1
2
3
4
@Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {

}

各种操作

1
2
3
4
5
/*
* insert()增加数据
* delete()删除数据
* update()修改数据
* query()查找数据*/

增加数据

1
2
3
4
5
6
7
8
9
10
11
12
//可以加个Bean类,里面放各种数据,操作和代码都更简单
//insert方法返回值为long类型
public long insert(String number, String name, String grade, String gender){
SQLiteDatabase db = getWritableDatabase();

ContentValues values = new ContentValues();//其实也是键值对
values.put("number",number);
values.put("name",name);
values.put("grade",grade);
values.put("gender",gender);
return db.insert(WE,null,values);//第二个参数表示为空时链接到哪,不知道就先写空
}

删除数据

1
2
3
4
5
6
7
//delete方法返回值为int类型,删除了多少个就返回多少
public int delete(String number){
SQLiteDatabase db = getWritableDatabase();
/*第一个参数为表的名字,第二个参数为判断语句,写条件的,第三个为值,要new一个String数组
,里面填充要传的所有值,问号代表将后面的值依次填充进去问号*/
return db.delete(WE,"number like ?",new String[]{number});
}

修改数据

1
2
3
4
5
6
7
8
9
10
11
12
13
//change方法返回值为int类型,更改了多少个数据就返回多少
public int change(String number, String name, String grade, String gender){
SQLiteDatabase db = getWritableDatabase();
ContentValues values = new ContentValues();//其实也是键值对
values.put("number",number);
values.put("name",name);
values.put("grade",grade);
values.put("gender",gender);
/*
* 传入4个参数,第一个为表名,第二个为修改的完整数据,第三个为判断语句,根据什么来查找修改的
* 第四个为要往判断语句里面填充的值,要为String类型的数组*/
return db.update(WE,values,"number like ?",new String[]{number});
}

查找数据

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
//query方法传回的是指针cursor
@SuppressLint("Range")
public List<data> check(String number){
SQLiteDatabase db = getWritableDatabase();
List<data>list = new ArrayList<>();

/*
* 传入4个参数,第一个参数为表名,第二个参数为数据库语句中的要查询哪些数据,其实也就是条件语句,写要得到哪些列,
* 传入null则代表哪一列都要,或者传入一个String数组,里面写着你要查询的列,
* 第三个和第四个参数合起来为判断语句,根据什么来查找,第三个写判断语句,第四个写传入的参
* 后面几个参数都是数据库中复杂的了*/
Cursor cursor = db.query(WE, new String[]{"number,name,grade"}, "number like ?"
, new String[]{number}, null, null, null);
if (cursor!=null){
//如果能移动到下一个就继续,循环不断得到的数据我们要保存到一个list中,最后返回list即可
while (cursor.moveToNext()){
//getString表示得到当前指针的第几列的值,整型,这个值的类型要我们自己知道再去调用不同的get方法
//getColumnIndex表示通过列名返回此列是第几列
String number_back = cursor.getString(cursor.getColumnIndex("number"));
String name_back = cursor.getString(cursor.getColumnIndex("name"));
String grade_back = cursor.getString(cursor.getColumnIndex("grade"));

data data = new data();
data.setNumber(number_back);
data.setName(name_back);
data.setGrade(grade_back);

list.add(data);
}
//最后要关闭
cursor.close();
}

return list;
}

LitePal开源库

这里只展示简单用法,要了解更多用法可以去看作者博客,中午网址那里就是哦,中文网址也有相关博客教学

GitHub上LitePal开源库介绍网站:https://github.com/guolindev/LitePal

中文网址:https://blog.csdn.net/guolin_blog/category_9262963.html

配置LitePal

导入jar包,添加依赖

原本是可以直接添加依赖的,但不知道为什么我的项目添加不成功,后面知道了,jcenter停用了但LitePal是在jcenter上的导致下载失败,还是直接导入jar包了,添加依赖的操作可以跳过,直接看下面怎么导入jar包的

1
2
3
4
5
6
7
8
9
10
11
//在settings.gradle中找到这个代码块加入jcenter()和下面那句代码
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
//加入下面两句代码
jcenter()
maven { url 'https://jitpack.io' }
}
}
1
2
3
4
//然后再正常导入依赖
dependencies {
implementation 'org.litepal.guolindev:core:3.2.3'
}

配置litepal.xml

  • 首先新建assets资源目录,有的就不用新建了

右击app->New ->Folder->点击Assets Folder->Finish

  • 然后再在assets目录下new File一个litepal.xml文件,接着编辑里面的内容:

配置文件相当简单,<dbname>用于设定数据库的名字,<version>用于设定数据库的版本号,<list>用于设定所有的映射模型,我们稍后就会用到,复制下面的代码到新建的xml文件里面即可

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="utf-8"?>
<litepal>
<dbname value="NewsDemo" />
<version value="1" />
<list>
</list>

</litepal>

配置LitePalApplication(下面方式选择一种即可)

  • 方式一
1
2
3
4
5
6
7
<manifest>
<application android:name="org.litepal.LitePalApplication"//添加此句代码
...
>
...
</application>
</manifest>

或者:

  • 方式二
1
2
3
4
5
6
7
<manifest>
<application android:name="org.litepal.MyOwnApplication"
...
>
...
</application>
</manifest>

这种方式需要继承 LitePalApplication

1
2
3
public class MyOwnApplication extends LitePalApplication {  
...
}

或者:

  • 方式三
1
2
3
4
5
6
7
8
9
public class MyOwnApplication extends AnotherApplication {

@Override
public void onCreate() {
super.onCreate();
LitePal.initialize(this);
}
...
}

使用方法

  • 创建和升级数据库

  • 跟前面使用SQLite数据库一样,我们以一个Student类进行讲解,新建一个Student类,并让这个类继承 LitePalSupport

注:这个类是一个典型的Java Bean类,在这个Student类中我们定义了number、name、gender、grade这些字段,并且生成了getter方法(获取值)和setter方法(赋值),这个类就可以用来建表了,每个字段就是表的一列,这就是对象关系映射最直观的体验

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
public class Student extends LitePalSupport {
//跟之前不同,这里要继承LitePalSupport
String number;
String name;
String gender;
String grade;

public String getNumber() {
return number;
}

public void setNumber(String number) {
this.number = number;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getGender() {
return gender;
}

public void setGender(String gender) {
this.gender = gender;
}

public String getGrade() {
return grade;
}

public void setGrade(String grade) {
this.grade = grade;
}
}
  • 之后我们把Student类添加到映射模型列表当中,修改litepal.xml中的代码

<mapping class = "类"/>,双引号之间的类换成上面讲的包名.类名,如图,这里的<mapping>标签来声明要配置的映射模型类,注意一定要使用完整的类名,不管有多少模型类需要映射,都用同样的方法配置在<list>标签下

  • 创建数据库和访问数据库的方法
1
LitePal.getDatabase();//LitePal创建数据库方法
  • 而要升级数据库如增加列只需要在映射的类那里新加一个数据域即可,如果要加多一张表就只需要新建一个类似的Student类按照同样的方法写即可

  • 增加数据

注意:无论是要对数据进行怎么样的操作都应该要创建数据库,创建好了就是打开数据库,要先调用LitePal.getDatabase();这个方法才能操作数据库,万万记得

1
2
3
4
5
6
7
8
9
10
//litepal插入数据
Student student = new Student();
student.setNumber(number);
student.setName(name);
student.setGrade(grade);
student.setGender("男");

student.save();
//save是保存数据的方法
//新建映射的类的对象,并且修改完成这个对象后,直接调用save()方法即可保存,用起来很简单
  • 删除数据
1
2
3
4
LitePal.deleteAll(Student.class,"number like ?",mEditText_number.getText().toString().trim());
//deleteAll就是删除数据的方法
//deleteAll第一个参数是表的名字,是之前映射类名.class;第二个参数是传一条判断根据什么删除的语句,
//如这里就是名字是?的,而?是什么是跟在后面传的参数

修改数据

1
2
3
4
5
6
7
Student student = new Student();
student.setName(name);
student.setGrade(grade);
student.setGrade("男");
student.updateAll("number like ?",number);
//updateAll就是修改数据的方法
//updateAll第一个参数传入一个限制语句,不传就是修改全部的

查询数据

  • 简单查询
1
2
//find方法按照id查找,第一个参数为表名,第二个为第几条数据
Student student = LitePal.find(Student.class,1);
1
2
//findFirst方法传入表名即可,返回第一条数据对象
Student student = LitePal.findFirst(Student.class);
1
2
//findLast方法传入表名即可,放回最后一条数据对象
Student student = LitePal.findLast(Student.class);
  • 连缀查询
1
2
3
//where方法传入一个限制语句,这里表示number为多少,后面跟要查询的数,find方法传入要在哪个表找
List<Student>list = LitePal.where("number like ?",mEditText_number.getText().toString().trim())
.find(Student.class);

动画

帧动画

  • 在Android中,帧动画的本质是把一组预先准备好的图片循环切换播放,造成一种动画效果,幻灯片快速播放形成电影。

通过xml方式实现

  1. 第一步:先把要系列播放的图片导入到项目中
  2. 第二步:在drawable目录下创建animation_flower.xml文件(文件名随意)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="false">// 是否只播放一次 false 循环播放

<item
android:drawable="@drawable/img01"
android:duration="200" />//动画持续时间
<item
android:drawable="@drawable/img02"
android:duration="200" />
<item
android:drawable="@drawable/img03"
android:duration="200" />
<item
android:drawable="@drawable/img04"
android:duration="200" />
<item
android:drawable="@drawable/img05"
android:duration="200" />

</animation-list>
  1. 第三步:布局和Activity
1
2
3
4
5
//首先在布局中的Image控件把background属性设置为drawable中刚才的drawable文件
//然后在Activity中引用即可
AnimationDrawable animationDrawable = (AnimationDrawable) mImageView.getBackground();//获取动画对象
mAnimationDrawable.start();//播放动画
mAnimationDrawable.stop();//结束动画

通过Java方法形式实现

1
2
3
4
5
6
7
8
9
10
11
12
13
//跟xml实现方式很像        
// 获取动画对象
mAnimationDrawable =new AnimationDrawable();
mAnimationDrawable.addFrame(getResources().getDrawable(R.drawable.img01),200);
mAnimationDrawable.addFrame(getResources().getDrawable(R.drawable.img02),200);
mAnimationDrawable.addFrame(getResources().getDrawable(R.drawable.img03),200);
mAnimationDrawable.addFrame(getResources().getDrawable(R.drawable.img04),200);
mAnimationDrawable.addFrame(getResources().getDrawable(R.drawable.img05),200);
mAnimationDrawable.addFrame(getResources().getDrawable(R.drawable.img06),200);
mAnimationDrawable.setOneShot(false);//设置循环播放
mImageViewShow.setBackground(mAnimationDrawable);
mAnimationDrawable.start();//播放动画
mAnimationDrawable.stop();//结束动画

补间动画

  • 在Android动画中,补间动画一共可以分成四类即透明度动画、缩放动画、旋转动画、位移动画。
  • 其实现方法可以通过xml来配置,也可以通过代码来实现。
  • 透明度动画-AlphaAnimation,缩放动画-ScaleAnimation,位移动画-TranslateAnimation,旋转动画-RotateAnimation

通过xml方式实现

xml实现补间动画,需要将xml放到res下的anim目录,Android工程默认是没有anim文件夹的在读文件前我们先把anim 文件夹以及文件建好

点中工程的res目录 右键New ->Directory-> 弹窗中输入anim
点中刚刚新建的anim目录 右键New -> Animation Resource File
创建好了以后输入如下的布局文件代码

  1. 透明度
1
2
3
4
5
6
<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:fromAlpha="1.0"
android:toAlpha="0.1"
android:duration="2000"/>

注:android:interpolator=""

Interpolator分类如下:

  • AccelerateDecelerateInterpolator,效果是开始和结束的速率比较慢,中间加速;
  • AccelerateInterpolator,效果是开始速率比较慢,后面加速;
  • DecelerateInterpolator,效果是开始速率比较快,后面减速;
  • LinearInterpolator,效果是速率是恒定的;
  • AnticipateInterpolator,效果是开始向后甩,然后向前;
  • AnticipateOvershootInterpolator,效果是开始向后甩,冲到目标值,最后又回到最终值;
  • OvershootInterpolator,效果开始向前甩,冲到目标值,最后又回到了最终值;
  • BounceInterpolator,效果是在结束时反弹;
  • CycleInterpolator,效果是循环播放,速率是正弦曲线;
  • TimeInterpolator,一个接口,可以自定义插值器。

插值器们对应的资源ID:

  • AccelerateDecelerateInterpolator,对应的是@android:anim/accelerate_decelerate_interpolator
  • AccelerateInterpolator,对应的是@android:anim/accelerate_interpolator
  • DecelerateInterpolator,对应的是@android:anim/decelerate_interpolator
  • LinearInterpolator,对应的是@android:anim/linear_interpolator
  • AnticipateInterpolator,对应是@android:anim/anticipate_interpolator
  • AnticipateOvershootInterpolator,对应的是@android:anim/anticipate_overshoot_interpolator
  • OvershootInterpolator,对应的是@android:anim/overshoot_interpolator
  • BounceInterpolator,对应是@android:anim/bounce_interpolator
  • CycleInterpolator,对应是@android:anim/cycle_interpolator
属性值 含义
fromAlpha 起始透明度(透明度的范围为:0-1,完全透明-完全不透明)
toAlpha 结束透明度
duration 持续时间(毫秒)
  1. 缩放
1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="utf-8"?>
<scale xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/accelerate_interpolator"
android:fromXScale="0.2"
android:toXScale="1.5"
android:fromYScale="0.2"
android:toYScale="1.5"
android:pivotX="50%"
android:pivotY="50%"
android:duration="2000"/>
属性值 含义
fromXScale 沿着X轴缩放的起始比例
fromYScale 沿着Y轴缩放的起始比例
toXScale 沿着X轴缩放的结束比例
toYScale 沿着Y轴缩放的结束比例
pivotX 缩放的中轴点X坐标,即距离自身左边缘的位置,比如50%就是以图像的 中心为中轴点
pivotY 缩放的中轴点Y坐标
duration 持续时间

  1. 位移
1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:fromXDelta="0"
android:toXDelta="320"
android:fromYDelta="0"
android:toYDelta="0"
android:duration="2000"/>
属性值 含义
fromXDelta 动画起始位置的X坐标
fromYDelta 动画起始位置的Y坐标
toXDelta 动画结束位置的X坐标
toYDelta 动画结束位置的Y坐标
duration 持续时间
  1. 旋转
1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:fromDegrees="0"
android:toDegrees="360"
android:duration="1000"
android:repeatCount="1"
android:repeatMode="reverse"/>
属性值 含义
fromDegrees/toDegrees 旋转的起始/结束角度
repeatCount 旋转的次数,默认值为0,代表一次,假如是其他值,比如3,则旋转4次 另外,值为-1或者infinite时,表示动画永不停止
repeatMode 设置重复模式,默认restart,但只有当repeatCount大于0或者infinite或-1时 才有效。还可以设置成reverse,表示偶数次显示动画时会做方向相反的运动
duration 持续时间


  1. 组合
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
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/decelerate_interpolator"
android:shareInterpolator="true" >

<scale
android:duration="2000"
android:fromXScale="0.2"
android:fromYScale="0.2"
android:pivotX="50%"
android:pivotY="50%"
android:toXScale="1.5"
android:toYScale="1.5" />

<rotate
android:duration="1000"
android:fromDegrees="0"
android:repeatCount="1"
android:repeatMode="reverse"
android:toDegrees="360" />

<translate
android:duration="2000"
android:fromXDelta="0"
android:fromYDelta="0"
android:toXDelta="320"
android:toYDelta="0" />

<alpha
android:duration="2000"
android:fromAlpha="1.0"
android:toAlpha="0.1" />

</set>

通过Java代码实现

  1. 透明度
1
2
3
4
  //fromAlpha   动画开始的透明度,从0.0 --1.0 ,0.0表示全透明,1.0表示完全不透明
//toAlpha 动画结束时的透明度,也是从0.0 --1.0 ,0.0表示全透明,1.0表示完全不透明
public AlphaAnimation(float fromAlpha, float toAlpha) {
}
1
2
3
animation=  new AlphaAnimation(0, 1);
animation.setDuration(2000);
mImageView.startAnimation(animation);
  1. 缩放
1
2
3
4
public ScaleAnimation(float fromX, float toX, float fromY, float toY,
int pivotXType, float pivotXValue, int pivotYType, float pivotYValue) {
}

1
2
3
4
5
 animation = new ScaleAnimation(0, 1.4f, 0, 1.4f,
ScaleAnimation.RELATIVE_TO_SELF,0.5f,ScaleAnimation.RELATIVE_TO_SELF,0.5f);
animation.setDuration(2000);
mImageView.startAnimation(animation);

重要:

属性值 含义
pivotXType 有2种模式,RELATIVE_TO_SELF(相对于自身)和RELATIVE_TO_PARENT(相对于父布局)pivotx,pivotY的值就应该是0-1的浮点数,分别对应xml中的%(自身)和%p(父布局)
pivotYType 同pivotXType

mRotateAnimation.setFillAfter(true);//旋转完后停止

1
view.requestLayout();//重新计算布局高度,布局修改时diao
  1. 位移

x正向右(小变化到大),y正向下(小变化到大)

1
2
3
4
5
6
7
8
 //fromXDelta     起始点X轴坐标,可以是数值、百分数、百分数p 三种样式,同scale
//fromYDelta 起始点Y轴从标,可以是数值、百分数、百分数p 三种样式
//toXDelta 结束点X轴坐标
//toYDelta 结束点Y轴坐标
// fromYType、toYType同ScaleAnimation,
public TranslateAnimation(int fromXType, float fromXValue, int toXType,
float toXValue,int fromYType, float fromYValue, int toYType, float toYValue) {
}
1
2
3
4
animation= new TranslateAnimation(TranslateAnimation.RELATIVE_TO_SELF, 0, TranslateAnimation.RELATIVE_TO_SELF, 0.5f,
TranslateAnimation.RELATIVE_TO_SELF, 0, TranslateAnimation.RELATIVE_TO_SELF, 0.5f);
animation.setDuration(2000);
mImageView.startAnimation(animation);
  1. 旋转
1
2
3
public RotateAnimation(float fromDegrees, float toDegrees, int pivotXType,
float pivotXValue, int pivotYType, float pivotYValue) {
}
1
2
3
4
animation= new RotateAnimation(0, -720, RotateAnimation.RELATIVE_TO_SELF, 0.5f,
RotateAnimation.RELATIVE_TO_SELF, 0.5f);
animation.setDuration(2000);
mImageView.startAnimation(animation);
  1. 组合
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
Animation rotateAnimation = new RotateAnimation(0, -720, RotateAnimation.RELATIVE_TO_SELF, 0.5f,
RotateAnimation.RELATIVE_TO_SELF, 0.5f);
rotateAnimation.setDuration(2000);

Animation translateAnimation = new TranslateAnimation(TranslateAnimation.RELATIVE_TO_PARENT, 0
, TranslateAnimation.RELATIVE_TO_PARENT, 0.5f,
TranslateAnimation.RELATIVE_TO_PARENT, 0, TranslateAnimation.RELATIVE_TO_PARENT, 0.5f);
translateAnimation.setDuration(2000);

Animation scaleAnimation = new ScaleAnimation(0, 1.4f, 0, 1.4f, ScaleAnimation.RELATIVE_TO_SELF,
0.5f, ScaleAnimation.RELATIVE_TO_SELF, 0.5f);
scaleAnimation.setDuration(2000);

Animation alphaAnimation = new AlphaAnimation(0, 1);
alphaAnimation.setDuration(2000);


AnimationSet animationSet = new AnimationSet(true);
animationSet.addAnimation(rotateAnimation);
animationSet.addAnimation(translateAnimation);
animationSet.addAnimation(scaleAnimation);
animationSet.addAnimation(alphaAnimation);
animationSet.setDuration(4000);
animationSet.setFillAfter(true);
mImageView.startAnimation(animationSet);

属性动画(补间动画优化版)

  • 补间动画作用的对象是View,也就是作用的对象是Android中的控件,如ImageView、Button、TextView等,也可以作用在布局上如LinearLayout、ConstraintLayout、RelativeLayout等,但是对于一些不是View的对象,无法对这些对象进行动画操作。比如我们要对某个控件的某个属性做进行动画操作,如其颜色,这个颜色也可以看成一个对象,但其并不是View对象,补间动画就无法实现,属性动画可以对这个颜色值做动画, 能实现一些更加复杂的动画效果。

  • 补间动画只是改变了View的视觉效果,而不会真正去改变View的属性

  • ObjectAnimator 对象动画 ValueAnimator的子类,允许对指定对象的属性执行动画。

  • ValueAnimator 值动画 计算初始值和结束值的过渡动画。

  • PropertyValueHolder 用于同时执行多个动画

  • TypeEvaluator 估值器

  • AnimatorSet 动画集合 Animator的子类,用于组合多个Animator,制定多个动画的播放次序。

  • Interpolator 差值器

1、属性动画都是通过ValueAnimator 类和ObjectAnimator 类来完成,其中ObjectAnimator类是对对象做动画,ValueAnimator 类是对值做动画。
2、PropertyValueHolder类可以同时执行多个动画,AnimatorSetl类可以将多个动画按一定的秩序先后执行。
3、TypeEvaluator估值器和Interpolator 差值器

  1. 对象动画(ObjectAnimator)
  • ObjectAnimator类是属性动画中非常重要的一个类,可以通过该类对View不仅可以实现一些基本的移、旋转、缩放和透明度四种基本变换动画,还能实现一些其他属性值的变换动画。

Java方法实现

1
2
3
4
5
6
7
8
 public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {
ObjectAnimator anim = new ObjectAnimator(target, propertyName);
anim.setFloatValues(values);
return anim;
}
/*方法中第一个参数Object target 的作用对象通常是View,也就是Android中的控件或布局。
方法中第二个参数String propertyName 通常是需要执行动画的属性,具体值如下表所示
方法中第三个参数float... values 表示属性的变换范围,该参数可以传多个值。*/
属性 值的用法
rotation 以屏幕方向为轴的旋转度数
alpha 透明度
translationX / translationY X/Y方向的位移
scaleX /scaleY X/Y方向的缩放倍数
rotationX / rotationY 以X/Y轴为轴的旋转度数

具体使用

1
2
3
4
ImageView imageView = findViewById(R.id.imageView);
ObjectAnimator animator = ObjectAnimator.ofFloat(imageView, "alpha", 1f, 0f, 1f);
animator.setDuration(5000);
animator.start();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
ImageView imageView = findViewById(R.id.imageView);
ObjectAnimator animator = ObjectAnimator.ofFloat(imageView, "alpha", 1f, 0f, 1f);
animator.setDuration(2000);

//动画延迟500ms执行
animator.setStartDelay(500);

//执行重复次数 +1
animator.setRepeatCount(3);

// 设置动画重复播放模式 RESTART -执行完一遍后重新执行
// REVERSE -执行完一遍后 从末位置往前执行
animator.setRepeatMode(ValueAnimator.RESTART);

//监听值变换
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Log.i("MainActivity","value:" +animation.getAnimatedValue());
}
});
animator.start();

xml方式实现

res目录下新建animator文件夹
animator文件夹下创建动画XML文件,如animator_alpha.xml
往该xml文件中输入如下代码

1
2
3
4
5
6
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:propertyName="alpha"
android:valueFrom="1"
android:valueTo="0"
android:valueType="floatType" />
1
2
3
4
ImageView imageView = findViewById(R.id.imageView);
Animator animator = AnimatorInflater.loadAnimator(Main2Activity.this, R.animator.animator_alpha);
animator.setTarget(imageView);
animator.start();
  1. 值动画(ValueAnimator)
1
2
3
ValueAnimator ofFloat(float... values) -- 浮点型数值
ValueAnimator ofInt(int... values) -- 整型数值
ValueAnimator ofObject(TypeEvaluator evaluator, Object... values) -- 自定义对象类型
1
2
3
4
5
6
7
8
9
10
11
12
final ImageView imageView = findViewById(R.id.imageView);
ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
anim.setDuration(5000);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float currentValue = (float) animation.getAnimatedValue();
Log.d("MainActivity", "cuurent value is " + currentValue);
imageView.setAlpha(currentValue);
}
});
anim.start();
  1. PropertyValueHolder

PropertyValueHolder可以让前面的一些动画同时执行。

1
2
3
4
5
6
7
8
9
10
11
ImageView imageView = findViewById(R.id.imageView);
PropertyValuesHolder alphaProper = PropertyValuesHolder.ofFloat("alpha", 0.5f, 1f);
PropertyValuesHolder scaleXProper = PropertyValuesHolder.ofFloat("scaleX", 0f, 1f);
PropertyValuesHolder scaleYProper = PropertyValuesHolder.ofFloat("scaleY", 0f, 1f);
PropertyValuesHolder translationXProper = PropertyValuesHolder.ofFloat("translationX", -100, 100);
PropertyValuesHolder translationYProper = PropertyValuesHolder.ofFloat("translationY", -100, 100);
PropertyValuesHolder rotationProper = PropertyValuesHolder.ofFloat("rotation", 0, 360);
ValueAnimator animator = ObjectAnimator.ofPropertyValuesHolder(imageView, alphaProper,
scaleXProper, scaleYProper,translationXProper,translationYProper,rotationProper);
animator.setDuration(5000);
animator.start();
  1. 动画组合(AnimatorSet)

前面的PropertyValueHolder类能实现将多个动画同时执行,AnimatorSet类不仅能让多个动画同时执行,还能让多个动画按一定的顺序执行,同时也能穿插多个动画同时执行。
主要的方法如下:

after(Animator anim)将现有动画插入到传入的动画之后执行
after(long delay) 将现有动画延迟指定毫秒后执行
before(Animator anim) 将现有动画插入到传入的动画之前执行
with(Animator anim) 将现有动画和传入的动画同时执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ImageView imageView = findViewById(R.id.imageView);
ObjectAnimator rotate = ObjectAnimator.ofFloat(imageView, "rotation", 0f, 360f);
ObjectAnimator translationX = ObjectAnimator.ofFloat(imageView, "translationX", -100, 100f);
ObjectAnimator translationY = ObjectAnimator.ofFloat(imageView, "translationY", -100, 100f);
ObjectAnimator scaleX = ObjectAnimator.ofFloat(imageView, "scaleX", 0, 1f);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(imageView, "scaleY", 0, 1f);
ObjectAnimator alpha = ObjectAnimator.ofFloat(imageView, "alpha", 1f, 0f, 1f);
AnimatorSet animSet = new AnimatorSet();
animSet.play(rotate)
.with(alpha)
.after(scaleX)
.before(translationX)
.after(1000)
.before(translationY)
.with(scaleY);
animSet.setDuration(5000);
animSet.start();
  1. 差值器(Interpolator)

前面的动画属性的变换都是均匀变换,可以通过差值器(Interpolator)来控制值变化的速率

1
2
3
4
5
6
ImageView imageView = findViewById(R.id.imageView);
ObjectAnimator animator = ObjectAnimator.ofFloat(imageView, "alpha", 0f, 1f);
animator.setDuration(5000);
//加速查值器,参数越大,速度越来越快
animator.setInterpolator(new AccelerateInterpolator(5));
animator.start();

系统提供,也可自定义

动画名称 效果
AccelerateInterpolator 加速查值器,参数越大,速度越来越快
DecelerateInterpolator 减速差值起,和加速查值器相反
AccelerateDecelerateInterpolator 先加速后减速
AnticipateInterpolator 先后退在加速前进
AnticipateOvershootInterpolator 以X/Y轴为轴的旋转度数
BounceInterpolator 弹球效果插值
CycleInterpolator 周期运动插值
LinearInterpolator 匀速插值
OvershootInterpolator 先快速完成动画,再回到结束样式

  1. 估值器(TypeEvaluator)
    在前面的值动画(ValueAnimator)中和对象动画(ObjectAnimator)有一个传对象的方法:
1
2
3
ValueAnimator  ofObject(TypeEvaluator evaluator, Object... values)
ObjectAnimator ofObject(Object target, String propertyName,
TypeEvaluator evaluator, Object... values)

这些方法动都需要传一个TypeEvaluator,我们先来看下这个类的源码

1
2
3
public interface TypeEvaluator<T> {
public T evaluate(float fraction, T startValue, T endValue);
}

从TypeEvaluator估值器的源码可以看出该类的作用就是告诉动画,如何从起始值过度到结束值。
Android源码中有好几个类实现来该接口,也就是系统提供的一些默认估值器, 我们以FloatEvaluator为例看下其实现代码。

1
2
3
4
5
6
public class FloatEvaluator implements TypeEvaluator<Number> {
public Float evaluate(float fraction, Number startValue, Number endValue) {
float startFloat = startValue.floatValue();
return startFloat + fraction * (endValue.floatValue() - startFloat);
}
}

从FloatEvaluator的实现可以看出在evaluate方法中用结束值减去初始值,算出它们之间的差值,然后乘以fraction这个系数,再加上初始值,那么就得到当前动画的值了

我们也可以以该方法为例 实现一个自定义的估值器实现一个背景颜色值的变化

我们先定义一个默认估值器类MyTypeEvaluator,该类自定义了颜色过渡的方式


​ package com.lucashu.animation;

​ import android.animation.TypeEvaluator;

​ public class MyTypeEvaluator implements TypeEvaluator {
​ private int mCurrentRed = -1;
​ private int mCurrentGreen = -1;
​ private int mCurrentBlue = -1;
​ @Override
​ public String evaluate(float fraction, String startValue, String endValue) {
​ int startRed = Integer.parseInt(startValue.substring(1, 3), 16);
​ int startGreen = Integer.parseInt(startValue.substring(3, 5), 16);
​ int startBlue = Integer.parseInt(startValue.substring(5, 7), 16);
​ int endRed = Integer.parseInt(endValue.substring(1, 3), 16);
​ int endGreen = Integer.parseInt(endValue.substring(3, 5), 16);
​ int endBlue = Integer.parseInt(endValue.substring(5, 7), 16);
​ // 初始化颜色的值
​ if (mCurrentRed == -1) {
​ mCurrentRed = startRed;
​ }
​ if (mCurrentGreen == -1) {
​ mCurrentGreen = startGreen;
​ }
​ if (mCurrentBlue == -1) {
​ mCurrentBlue = startBlue;
​ }
​ // 计算初始颜色和结束颜色之间的差值
​ int redDiff = Math.abs(startRed - endRed);
​ int greenDiff = Math.abs(startGreen - endGreen);
​ int blueDiff = Math.abs(startBlue - endBlue);
​ int colorDiff = redDiff + greenDiff + blueDiff;
​ if (mCurrentRed != endRed) {
​ mCurrentRed = getCurrentColor(startRed, endRed, colorDiff, 0,
​ fraction);
​ } else if (mCurrentGreen != endGreen) {
​ mCurrentGreen = getCurrentColor(startGreen, endGreen, colorDiff,
​ redDiff, fraction);
​ } else if (mCurrentBlue != endBlue) {
​ mCurrentBlue = getCurrentColor(startBlue, endBlue, colorDiff,
​ redDiff + greenDiff, fraction);
​ }
​ // 将计算出的当前颜色的值组装返回
​ String currentColor = “#” + getHexString(mCurrentRed)
​ + getHexString(mCurrentGreen) + getHexString(mCurrentBlue);
​ return currentColor;
​ }
​ /**
​ * 根据fraction值来计算当前的颜色。
/
​ private int getCurrentColor(int startColor, int endColor, int colorDiff,
​ int offset, float fraction) {
​ int currentColor;
​ if (startColor > endColor) {
​ currentColor = (int) (startColor - (fraction * colorDiff - offset));
​ if (currentColor < endColor) {
​ currentColor = endColor;
​ }
​ } else {
​ currentColor = (int) (startColor + (fraction * colorDiff - offset));
​ if (currentColor > endColor) {
​ currentColor = endColor;
​ }
​ }
​ return currentColor;
​ }

​ /
*
​ * 将10进制颜色值转换成16进制。
​ */
​ private String getHexString(int value) {
​ String hexString = Integer.toHexString(value);
​ if (hexString.length() == 1) {
​ hexString = “0” + hexString;
​ }
​ return hexString;
​ }
​ }

再自定义一个View,在该类中画一个矩形框

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
public class MyView extends View {
private String color;
private Paint mPaint;
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.WHITE);
}

public String getColor() {
return color;
}

public void setColor(String color) {
this.color = color;
mPaint.setColor(Color.parseColor(color));
invalidate();
}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawRect(0, 0, 500,500, mPaint);
}
}

自顶一个View在布局文件中添加如下

1
2
3
4
5
6
7
8
<com.lucashu.animation.MyView
android:id="@+id/myview"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_marginBottom="100dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />

将在估值器加到动画中,该动画作用在我们自定义的View上

1
2
3
4
5
6
MyView imageView = findViewById(R.id.myview);
ObjectAnimator anim = ObjectAnimator.ofObject(
imageView,"color", new MyTypeEvaluator(),
"#0000FF","#FF0000");
anim.setDuration(5000);
anim.start();

Handler和Runable

启动页

安卓启动app会自带一个页面,这个页面会加载app,之后才会加载自己设置的启动页面,如果启动页面的逻辑很快过去就会显得欢迎页面一闪而过切有滑动显示自带启动页情况

1
2
3
4
5
6
7
8
9
//Handler延时        
Runnable runnable = new Runnable() {
@Override
public void run() {
startActivity(new Intent(WelcomeActivity.this, MainActivity.class));
}
};
Handler handler = new Handler();
handler.postDelayed(runnable,2000);

冷启动背景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<style name="AppTheme.Launcher" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:windowBackground">@drawable/bg_splash</item>
</style>



<activity
android:name=".ui.activity.SplashActivity"
android:exported="true"
android:theme="@style/AppTheme.Launcher">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

Handler消息机制

(28条消息) Android——Handler详解_android handler_Yawn__的博客-CSDN博客

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
/*主线程创建Handler(属于主线程,Looper.myLooper()),之后子线程调用这个Handler传消息*/
mHandler = new Handler(Looper.myLooper()){
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
if (msg.what==0){
Boolean isStart = (Boolean) msg.obj;
if (isStart){
startActivity(new Intent(WelcomeActivity.this,HomeActivity.class));
finish();
}
}
}
};
/*子线程读数据*/
new Thread(new Runnable() {
@Override
public void run() {
Message message = new Message();
message.what = 0;//what表示是哪个子线程发的消息
message.arg1 = 0;//arg1和arg2内置标识用的
message.arg2 = 1;
message.obj = true;//传其他类型就用org这个参数,为Object类,什么都能传
mHandler.sendMessage(message);//还有许多传消息的方法
}
}).start();

Material Design设计语言

Material Design(中文名:材料设计语言),是由Google推出的设计语言,这种设计语言旨在为手机、平板电脑、台式机和“其他平台”提供更一致、更广泛的“外观和感觉”

那为什么谷歌会觉得Material Design可以解决Android平台界面风格不统一的问题呢?

一言蔽之——好看。

Toolbar(顶部导航,常用)

我们平常建立一个项目,原生的顶上标题栏Actionbar都是深紫色的标题栏,而且ActionBar是一个只能位于活动的顶部的标题栏。

ActionBar弃用了,可以用ToolBar实现一样的功能

替代ActionBar

主题改成<style name="Theme.MyApplication" parent="Theme.MaterialComponents.DayNight.NoActionBar.Bridge">,在xml使用Toolbar控件即可,要显示什么标题在AndroidManifest.xml设置android:label="Fruits"

1
2
3
4
5
6
7
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>
1
2
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);

自定义ToolBar菜单

先在res下新建menu资源文件夹,例子如下,item标签表示一个新建选项,icon为图标,title标题,showAction为显示方式,showAsAction 主要有以下几种值可选:always表示永远显示在 Toolbar中,如果屏幕空间不够则不显示;ifRoom表示屏幕空间足够的情况下显示在Toolbar中,不够的话就显示在菜单当中;never 则表示水远显示在菜单当中。在菜单中的项没有图标,即使设置了图标,只显示文字,菜单为收起形式,点击展开

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/backup"
android:icon="@drawable/backup"
android:title="Backup"
app:showAsAction="always"/>

<item
android:id="@+id/delete"
android:icon="@drawable/delete"
android:title="Backup"
app:showAsAction="ifRoom"/>
<item
android:id="@+id/settings"
android:icon="@drawable/settings"
android:title="Backup"
app:showAsAction="never"/>

</menu>

然后在Java中重写两个方法来导入menu和使用,在onOptionsItemMenu()方法中加载了菜单文件,然后onOptionsItemSelected()方法中处理ActionBar各个按钮的点击事件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Override
public boolean onCreateOptionsMenu(Menu menu){
getMenuInflater().inflate(R.menu.toolbar,menu);//在这里加载了toolbar.xml这个菜单文件
return true;
}


@Override
public boolean onOptionsItemSelected(MenuItem item){
switch (item.getItemId()){
case R.id.backup:
Toast.makeText(this,"You clicked Backup ",Toast.LENGTH_SHORT).show();
break;
case R.id.delete:
Toast.makeText(this,"You clicked Delete ",Toast.LENGTH_SHORT).show();
break;
case R.id.settings:
Toast.makeText(this,"You clicked Settings ",Toast.LENGTH_SHORT).show();
break;
default:
}
return true;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/*
* 反射使item属性为never时使icon和title同时可见
* */
@Override
public boolean onMenuOpened(int featureId, Menu menu) {
if (menu != null) {
if (menu.getClass().getSimpleName().equalsIgnoreCase("MenuBuilder")) {
try {
Method method = menu.getClass().getDeclaredMethod("setOptionalIconsVisible", Boolean.TYPE);
method.setAccessible(true);
method.invoke(menu, true);
} catch (Exception e) {
e.printStackTrace();
}
}
}
return super.onMenuOpened(featureId, menu);
}

滑动菜单(右滑显示,如QQ)

DrawerLayout(抽屉,常用)

这个控件中允许有2个子布局(控件),第一个显示在外面,第二个为折叠起来,右滑展开,其中第二个控件中的layout_gravity是必须需要指定的,其中left表示菜单在左边,start表示会根据系统语言进行判断,如果系统语言是从左到右的,则滑动菜单在左边。

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
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout 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:id="@+id/draw_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">

<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">

<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />

</FrameLayout>

<TextView
android:layout_width="300dp"
android:layout_height="match_parent"
android:layout_gravity="start"
android:background="#FFF"
android:text="This is menu"
android:textSize="30sp" />

</androidx.drawerlayout.widget.DrawerLayout>

为了让隐藏菜单更容易被触发,我们在Toolbar上加一个导航按钮,点击它也可以打开滑动菜单(防止用户不知道左滑能打开东西,参考QQ设计)。注:HomeAsUp默认为返回按钮,图标为箭头,这里就把这个按钮替换了图标和功能罢了。GravityCompat.START和xml定义一样,xml布局里面在菜单中的子布局android:layout_gravity="start"属性一样,还有GravityCompat.END

1
2
3
4
5
6
7
8
9
10
11
12
//使用ActionBar自带功能加按钮(上面使用了ToolBar替代了ActionBar,但实体还是ActionBar)      
ActionBar actionBar = getSupportActionBar()//获取对象
if (actionBar!=null){
actionBar.setDisplayHomeAsUpEnabled(true);//导航按钮显示
actionBar.setHomeAsUpIndicator(R.drawable.ic_menu);//设置导航按钮图标
}

//onOptionsItemSelected方法中监听导航按钮,id如下
case android.R.id.home:
mDrawerLayout.openDrawer(GravityCompat.START);
//使得滑动显示出来,传入一个Gravity参数,GravityCompat.START和xml定义一样
break;
1
implementation'com.google.android.material:material:1.0.0'//Design Support库(包含NavigationeView),现在默认生成
1
implementation'de.hdodenhof:circleimageview:3.0.1'//一个开源库,轻松实现图片圆形化

准备两个部分的东西,menu菜单(NavigationView中的选项菜单)和headerLayout(NavigationView中的头部布局)

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
<!--menu-->
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<group android:checkableBehavior="single">
<!-- group 代表一个组,checkableBehavior指定为single表示组内所有菜单项只能单选-->
<item
android:id="@+id/nav_call"
android:icon="@drawable/nav_call"
android:title="Call"/>
<!-- 菜单单项显示的文字-->
<item
android:id="@+id/nav_friend"
android:icon="@drawable/nav_friend"
android:title="Friend"/>
<item
android:id="@+id/nav_location"
android:icon="@drawable/nav_location"
android:title="Location"/>
<item
android:id="@+id/nav_mail"
android:icon="@drawable/nav_mail"
android:title="Mail"/>
<item
android:id="@+id/nav_task"
android:icon="@drawable/nav_task"
android:title="Task"/>

</group>
</menu>
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
//headerLayout
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="180dp"
android:padding="10dp"
android:background="?attr/colorPrimary">

<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/icon_image"
android:layout_width="70dp"
android:layout_height="70dp"
android:src="@drawable/icon"
android:layout_centerInParent="true" />

<TextView
android:id="@+id/username"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:text="wendy"
android:textColor="#FFF"
android:textSize="14sp"/>
<TextView
android:id="@+id/mail"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@id/username"
android:text="1111@qq"
android:textColor="#FFF"
android:textSize="14sp"/>

</RelativeLayout>
1
2
3
4
<com.google.android.material.navigation.NavigationView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"/>

这里layout_gravity一定要有,不然会阻挡整个屏幕,toolbar会不见掉

1
2
3
4
5
6
7
8
9
10
11
      NavigationView navigationView = findViewById(R.id.nav_view);
navigationView.setCheckedItem(R.id.nav_call);//设置默认选中按钮

//监听
navigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
mDrawerLayout.closeDrawers();
return true;
}
});
1
2
3
4
5
6
7
8
9
//点击事件
nav_home = (NavigationView) findViewById(R.id.nav_home);
CardView cardView = nav_home.getHeaderView(0).findViewById(R.id.cv_exit);
cardView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(HomeActivity.this, "ddd", Toast.LENGTH_SHORT).show();
}
});

悬浮按钮和可交互提示

立面设计是Material Design中的一条重要设计思想,而悬浮按钮就是最具有代表性的立面设计了,这种按钮不属于主界面平面的一部分而是位于另外一个维度。同时我们也学习一种可交互式的提示。

FloatingActionButton

android:elevation="100dp"设置悬浮高度,下面有阴影

1
2
3
4
5
6
7
8
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="16dp"
android:elevation="100dp"
android:src="@drawable/ic_done" />

这个控件监听方法同平台button

Snackbar可交互提示

首先明确SnackbarToast有各自的应用场景,Snackbark可以给用户提供了一个可交互按钮,给用户提供一些额外操作。基本同Toast,toast只是为了发出一个提示给用户罢了,有些如删除数据的场景就可以用这种带交互的好,可以允许用户取消删除

1
2
3
4
5
6
7
8
9
10
11
12
floatingActionButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Snackbar.make(v,"toast",Snackbar.LENGTH_SHORT)
.setAction("Undo", new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "取消", Toast.LENGTH_SHORT).show();
}
}).show();
}
});
CoordinatorLayout布局

CoordinatorLayout可以说是加强版的FrameLayout(帧布局),也是Design Support库提供的,它在普通情况下和FrameLayout作用基本一致。不一样的是CoordinatorLayout可以监听其子控件的各种事件,然后自动帮助我们做出最为合理的响应。

举个例子,刚刚弹出来的Snackbar提示把悬浮按钮遮挡住了,这时让CoordinatorLayout监听到Snackbar的弹出事件,它就会自动让FloatingActionButton向上偏移。

直接在代码中将FrameLayout替换一下就可以

卡片式布局(包含Glide库)

CardView(类FrameLayout

CardView是实现卡片上布局的重要控件。它实际上也是一个FrameLayout,只是额外提供了圆角和阴影的效果,看上去会有立体的感觉。

1、cardBackgroundColor 设置背景色

CardView是View的子类,View一般使用Background设置背景色,为什么还要单独提取出一个属性让我们来设置背景色呢?

为了实现阴影效果,内部已经消耗掉了 Background 属性

2、cardCornerRadius 设置圆角半径

3、contentPadding 设置内部padding

View提供了padding设置间距,为什么还要单独提取出一个属性?

相同的原因,内部消耗掉了 padding 属性

4、cardElevation 设置阴影大小

5、cardUseCompatPadding

默认为false,用于5.0及以上,true则添加额外的 padding 绘制阴影

6、cardPreventCornerOverlap

默认为true,用于5.0及以下,添加额外的 padding,防止内容和圆角重叠

Glide是一个超级强大的图片加载库,可以加载本地图片、网络图片、gif图片,甚至本地视频。而且Glide的用法也很简单,只用一行代码我们会在下面进行设置。

1
2
3
4
implementation'com.github.bumptech.glide:glide:4.9.0'

/*直接按照提示用最新的库,库版本过低还未迁移到AndroidX,会有警告,低版本在gradle.properties
加android.enableJetifier=true*/
1
2
3
4
5
Glide.with(context)
.load(path.toString())
.error(R.mipmap.menu_cancellation)//图片加载失败后,显示的图片
.diskCacheStrategy(DiskCacheStrategy.RESULT) //缓存图片
.into(imageView);
AppBarLayout解决CoordinatorLayou默认左上角放置重叠问题

运行后我们发现,先前的Toolbar被遮挡了,这是因为CoordinatorLayou是和FrameLayout一样,在没有进行明确定位时,默认都会摆放在布局的左上角,所以产生了遮挡。那除了对RecyclerView进行偏移,我们还可以借助Design Support库中提供的另外一个工具——AppBarLayout来解决遮挡。**AppBarLayout实际上是一个垂直方向的LinearLayout**,它在内部做了很多滚动事件的封装,并应用了一些Material Design的设计理念。AppBarLayout又必须是CoordinatorLayout的子布局

包住冲突事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<com.google.android.material.appbar.AppBarLayout

android:layout_width="match_parent"
android:layout_height="wrap_content">

<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>

</com.google.android.material.appbar.AppBarLayout>

然后再另外给**RecyclerView**指定一个布局行为

1
app:layout_behavior="@string/appbar_scrolling_view_behavior"

运行看效果

那现在我们只是用AppBarLayout解决了遮挡问题,那应用了一些Material Design的设计理念是怎么体现的呢?其实这时RecyclerView已经将滚动事件通知了AppBarLayout,但是我们还没有进行处理,我们在Toolbar添加以下代码:

1
app:layout_scrollFlags="scroll|enterAlways|snap"

scroll表示当RecyclerView向上滚动时,Toolbar会跟着向上滚动并隐藏;enterAlways表示当RecyclerView向下滚动时,Toolbar会跟着向下滚动并重新显示。snap表示Toolbar还没有完全隐藏或显示的时候,会根据当前滚动距离,自动选择是隐藏还是显示。运行看一下。

下拉刷新

swiperefreshlayout
1
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"

包在RecyclerView外面使用,这时候RecyclerView添加的滚动行为要放到刷新控件中,不然刷新控件不生效,会被挡住

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
mSwipeRefreshLayout = findViewById(R.id.refresh);
mSwipeRefreshLayout.setColorSchemeResources(R.color.black);
mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
runOnUiThread(new Runnable() {
@Override
public void run() {
initFruits();
adapter.notifyDataSetChanged();
mSwipeRefreshLayout.setRefreshing(false);
}
});
}
}).start();
}
});

可折叠标题栏

CollapsingToolbarLayout

顾名思义,CollapsingToolbarLayout是一个作用于Toolbar基础之上的布局,被限定只能作为AppBarLayout的直接子布局来使用,而AppBarLayout又必须是CoordinatorLayout的子布局。

充分利用系统状态栏空间

android:fitsSystemWindows="true"

因为我们现在写的是嵌套结构,所以为了实现这种效果,我们需要分别在CoordinatorLayoutAppBarLayoutCollapsingToolbarLayout设置其android:fitsSystemWindows属性为ture,就表示该控件ImageView会出现在系统状态栏上。除了这些我们还要将状态栏的颜色去设置成透明色才行。具体怎么实现大家有兴趣的可以去看书里的具体做法。

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
Observable.create(new ObservableOnSubscribe<String>() {
@Override
public void subscribe(ObservableEmitter<String> emitter) throws Exception {
emitter.onNext("Hello");
emitter.onComplete();
}
})
.map(new Function<String, String>() {
@Override
public String apply(String s) throws Exception {
// 对发射的字符串进行转换,这里简单地在字符串后面加上 " RxJava"
return s + " RxJava";
}
})
.subscribe(new Observer<String>() {
@Override
public void onSubscribe(Disposable d) {
// 订阅时的操作
}

@Override
public void onNext(String result) {
// 接收到转换后的事件,result 是 "Hello RxJava"
}

@Override
public void onError(Throwable e) {
// 发生错误时的操作
}

@Override
public void onComplete() {
// 完成时的操作
}
});

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
package com.derry.rxjavastudy.simple01;

import androidx.appcompat.app.AppCompatActivity;

import android.app.ProgressDialog;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;

import com.derry.rxjavastudy.R;

import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;

import io.reactivex.Observable;
import io.reactivex.Observer;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.annotations.NonNull;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Consumer;
import io.reactivex.functions.Function;
import io.reactivex.schedulers.Schedulers;

public class MainActivity extends AppCompatActivity {

// 打印logcat日志的标签
private final String TAG = MainActivity.class.getSimpleName();

// 网络图片的链接地址
private final static String PATH = "http://pic1.win4000.com/wallpaper/c/53cdd1f7c1f21.jpg";

// 弹出加载框(正在加载中...)
private ProgressDialog progressDialog;

// ImageView控件,用来显示结果图像
private ImageView image;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

image = findViewById(R.id.image);
}

/**
* 图片显示加载功能
*
* @param view
*/
public void showImageAction(View view) {

/**
* 如果我们采用传统方式 完成此功能,每位开发者的思想都不一样 (思维不同 )
* A同学 线程池
* B同学 new Thread + Handler
* C同学 xxx
* D同学 古老的方式
* ....
*
* 如果采用传统开发方式,我们后面的开发者接手前面开发者的代码,就很痛苦(弊端)
*/


/**
* TODO RX思维
*
* 起点 和 终点
*
* RxJava RXJS RxXXX RX系列框架 为什么把所有函数都成为操作符 因为我们的函数要去操作 从起点 流向 终点7
*
*/

// TODO 第二步
// 起点
Observable.just(PATH)

// TODO 第三步
// 需求:001 图片下载需求 PATH ---》 Bitmap
.map(new Function<String, Bitmap>() {
@NonNull
@Override
public Bitmap apply(@NonNull String path) throws Exception {
try {
// Thread.sleep(2000); // 睡眠2秒钟

URL url = new URL(path);
HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
httpURLConnection.setConnectTimeout(5000); // 设置请求连接时长 5秒
int responseCode = httpURLConnection.getResponseCode(); // 才开始 request 拿到服务器的响应 200成功 404有问题 ...
if (responseCode == HttpURLConnection.HTTP_OK) {
InputStream inputStream = httpURLConnection.getInputStream();
Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
return bitmap;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
})

// 需求:002 加水印
.map(new Function<Bitmap, Bitmap>() {
@NonNull
@Override
public Bitmap apply(@NonNull Bitmap bitmap) throws Exception {
Paint paint = new Paint();
paint.setColor(Color.RED);
paint.setTextSize(88);
Bitmap shuiyingBitmap = drawTextToBitmap(bitmap, "韭菜盖饭", paint, 88, 88);
return shuiyingBitmap;
}
})

// 需求:003 日志记录需求
.map(new Function<Bitmap, Bitmap>() {
@NonNull
@Override
public Bitmap apply(@NonNull Bitmap bitmap) throws Exception {
Log.e(TAG, "什么时候下载了图片 apply: " + System.currentTimeMillis() );
return bitmap;
}
})


// 给上面的分配异步线程(图片下载操作)
.subscribeOn(Schedulers.io())


// 终点分配 Android主线程
.observeOn(AndroidSchedulers.mainThread())

// TODO 导火索 点燃了 开始执行
// 关联:观察者设计模式 关联 起点 和 终点 == 订阅
.subscribe(

// 终点
new Observer<Bitmap>() {

// TODO 第一步
// 订阅成功
@Override
public void onSubscribe(Disposable d) {
// 显示加载框
progressDialog = new ProgressDialog(MainActivity.this);
progressDialog.setTitle("RXJava Derry run 正在加载中..");
progressDialog.show();
}

// TODO 第四步 显示图片 水印的Bitmap
// 上一层给我的响应
@Override
public void onNext(Bitmap bitmap) {
image.setImageBitmap(bitmap); // 显示到控件上
}

// 链条思维发生了异常
@Override
public void onError(Throwable e) {

}

// TODO 第五步 整个链条思维全部结束
// 整个链条全部结束
@Override
public void onComplete() {
// 隐藏加载框
if (progressDialog != null)
progressDialog.dismiss();
}
});

}

// 图片上绘制文字 加水印
private final Bitmap drawTextToBitmap(Bitmap bitmap, String text, Paint paint, int paddingLeft, int paddingTop) {
Bitmap.Config bitmapConfig = bitmap.getConfig();

paint.setDither(true); // 获取跟清晰的图像采样
paint.setFilterBitmap(true);// 过滤一些
if (bitmapConfig == null) {
bitmapConfig = Bitmap.Config.ARGB_8888;
}
bitmap = bitmap.copy(bitmapConfig, true);
Canvas canvas = new Canvas(bitmap);

canvas.drawText(text, paddingLeft, paddingTop, paint);
return bitmap;
}

/**
* 常用操作符
* @param view
*/
public void action(View view) {

String[] strings = {"AAA", "BBB", "CCC"};

// for
/*for (String string : strings) {

}*/

// 起点
Observable.fromArray(strings)

// 订阅:起点 和 终点
.subscribe(new Consumer<String>() {

// 终点
@Override
public void accept(@NonNull String s) throws Exception {
Log.d(TAG, "accept: " + s);
}
});

}
}

RXJAVA从入门到精通-CSDN博客