Android阶段性总结🦄
阶段性总结一下基本控件、常用布局还有Menu、Notifaction、Resource以及Activity、Fragment
基本控件🌈
OverView概览
TextView:文本框控件
EditText:编辑框控件
TextInputLayout:文本输入布局
Button:按钮控件
ImageView:图片控件
CheckBox:选择框控件
RadioButton:单选框控件
Snackbar:消息框控件
Notifaction:通知消息
Toast:吐司消息
SnackBar:底部提示消息
Menu:菜单控件
Spinner:下拉框控件
View基类
最基础的构件,所有widget类的基类。
重要属性 | 属性描述 |
---|---|
android:id | 设置一个在当前layout中的唯一编号 |
android:background | 设置背景色/背景图片 |
android:visibility | 设置是否显示View(visible,invisible,gone) |
继承自View的常用组件在LayoutParams下的常用属性
android:gravity 内部内容对齐
layout_gravity 控件相对于其所在容器的位置
padding相关属性 内边距
margin相关属性 外边距
TextView文本控件
TextView继承自View,用于向用户展示文本内容,但不允许编辑
重要属性描述
重要属性 | 属性描述 |
---|---|
android:text | 文本内容 |
android:textColor | 文本颜色 |
android:textSize | 设置文本大小 |
android:maxLength | 文本最大长度,超出此长度的文本不显示 |
android:ems | 设置文本的字符个数 |
android:lines | 设置文本的行数,超出此行数的文本不显示 |
android:textAppearance | 设置文字外观 |
android:ellipsize | 设置当文本超出TextView规定的范围的显示方式 |
android:textStyle | 设置文本样式,如:bold(粗体)、italic(斜体)、normal(正常) |
常用方法
tv.setText(String);
tv.append(String);
textView.addTextChangedListener(new TextWatcher() { //文本更改监听器
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
//文字改变后
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
//文本改变时
}
@Override
public void afterTextChanged(Editable s) {
//文本改变之前
}
});
EditText编辑框控件
编辑框控件,TextView的子类,用户可在此控件中输入信息。一般与TextInputLayout搭配使用。
重要属性描述
重要属性 | 重要描述 |
---|---|
android:inputType | 输入类型设置(textPassword,phone) |
android:hint | 编辑框内容为空时显示的提示信息 |
android:maxLines | 设置文本的最大行数 |
android:ems | 设置控件的宽度为N个字符的宽度 |
android:digits | 设置允许输入哪些字符 |
常用方法
//获取编辑框内容
editText.getText().toString();
editText.setText(String);
//监听输入变化
editText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void afterTextChanged(Editable editable) {
}
});
TextInputLayout容器
EditText容器,只能包含一个EditText,可以实现多种多种动画、提示信息等等。
示例布局代码
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/inputLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:id="@+id/et_email"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:saveEnabled="false"
android:maxLength="24"
android:hint="输入email"/>
</com.google.android.material.textfield.TextInputLayout>
重要属性描述
重要属性 | 属性描述 |
---|---|
app:counterEnabled | 是否显示计数器,默认false |
app:counterMaxLength | 设置计数器的最大值,与counterEnabled同时使用 |
app:errorEnabled | 是否显示错误信息 |
app:errorTextAppearance | 错误信息的字体样式 |
app:hintTextAppearance | 设置hint的文字样式 |
app:hintAnimationEnabled | 是否显示hint的动画,默认true |
常用方法
inputLayout.setError("请输入正确的邮箱地址");
Button按钮控件
按钮,继承自TextView,既可以显示文本,也可以显示图片,允许用户通过点击执行操作。常用子类:CheckBox、RadioButton、ToggleButton。
常用方法
//Button点击事件1
//xml设置onClick属性
android:onClick="showToast"
//在Activity类实现这个事件处理方法
public void showToast(View view) {
String msg = "Hello Toast!";
Toast.makeText(this, msg,duration).show();
}
//Button点击事件2
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// 添加逻辑代码
}
});
//Button点击事件3
//Activity实现OnClickListener接口
public class <? extends AppCompatActivity> implements View.OnClickListener
@Override
public void onClick(View v) {
// 添加逻辑代码
}
ImageView图片控件
用于展示图片,继承自View
重要属性描述
重要属性 | 属性描述 |
---|---|
android:src | 设置展示图片的资源路径 |
android:scaleType | 设置图片的填充方式 |
android:adjustViewBounds | 是否保持宽高比,需要与maxWidth、maxHeight一起使用 |
android:cropToPadding | 是否截取指定区域用空白代替,需要与scrollY一起使用 |
android:tint | 将图片渲染成指定的颜色 |
android:maxHeight | 置View最大高度,需要与setAdjustViewBounds一起使用 |
android:maxWidth | 设置View最大宽度,需要与setAdjustViewBounds一起使用 |
缩放种类scaleType
fitXY:图片拉伸或收缩填满ImageView,不保持比例,最常用;
fitStart:图片按比例扩大、缩小至ImageView宽度,在ImageView上方显示;
fitEnd:图片按比例扩大、缩小至ImageView宽度,在ImageView下方显示;
fitCenter:图片按比例扩大、缩小至ImageView宽度,在ImageView居中显示;
center:以图片和ImageView的中心点居中显示图片原尺寸,裁剪超出ImageView的部分;
centerCrop:以图片和ImageView的中心点居中显示按比例扩大或缩小的图片,图片的宽高有一边等于ImageView的宽高,长出的部分被裁剪;centerInside:以图片和ImageView的中心点居中显示按比例缩小图片,使图片的宽高小于等于ImageView的宽高,直到将图片的内容完整居中显示;
matrix:不改变原图大小,从ImageView左上角开始绘制,超过ImageView的部分被裁剪
常用方法
imageView.setImageResource(@ResId);
CheckBox复选框控件
CheckBox是复选框,继承自CompoundButton类。用于多项选择的场景
重要属性描述
重要属性 | 属性描述 |
---|---|
android:text | CheckBox控件提示文字 |
android:checked | 设置此标签的初始状态为选中 |
常用方法
//判断按钮是否处于被选中状态
cb.isChecked();
//设置按钮是否选中
cb.setChecked(Boolean flag);
//设置多选组件的触摸监听器
cb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
}
});
RadioButton
RadioButton为单选按钮,继承自CompoundButton,主要用于单项选择的场景,需要与RadioGroup控件一起使用,可以实现单选效果。RadioGroup是单选组合框,可容纳多个RadioButton,用于将RadioButton组合成选择互斥的选项。
示例布局代码
<RadioGroup
android:id="@+id/rg_gender"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<RadioButton
android:id="@+id/man"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="true"
android:text="男" />
<RadioButton
android:id="@+id/woman"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="女" />
</RadioGroup>
示例方法
rgGender.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
Boolean isMan = checkedId == R.id.man;
}
});
轻量消息提醒组件
Toast吐司消息
Toast.makeText(<Context>, <String>, Toast.LENGTH_LONG).show();
Snacker底部弹出消息
Android5.0推出的Material Design控件,用于替代Toast,在界面底部弹出提示信息
//gradle
implementation 'com.google.android.material:material:1.2.1'
//usage in activity
Snackbar.make(<View>, <String>, Snackbar.LENGTH_LONG).setAction("确定", new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(<Context>,"信息已确认", Toast.LENGTH_SHORT).show();
}
}).show();
Menu菜单组件
Menu菜单组件,定义于/res/menu,可以在Activity中设置为溢出菜单,也可以设置为ContextMenu弹窗菜单,也可以设置为Navigation组件菜单。app:showAsAction属性设置菜单项的显示动作。
<?xml version="1.0" encoding="utf-8"?>
<!--define in xml-->
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_call"
android:orderInCategory="100"
android:title="Devloper"
android:icon="@drawable/ic_baseline_search_24"
app:showAsAction="always" />
<item
android:id="@+id/action_settings"
android:orderInCategory="100"
android:title="About"
app:showAsAction="never" />
</menu>
溢出菜单Menu
//usage in activity
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_action, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_call:
//action
return true;
case R.id.action_settings:
//action
return true;
...
}
return super.onOptionsItemSelected(item);
}
上下文菜单ContextMenu
//usage in activity
registerForContextMenu(<View>);
@Override
public boolean onContextItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.action_call:
//todo
return true;
case R.id.action_settings:
//todo
return true;
}
return super.onContextItemSelected(item);
}
Spinner下拉菜单
Spinner下拉菜单提供了从一个数据集合中快速选择一项值的办法。默认情况下Spinner显示的是当前选择的值,点击Spinner会弹出一个包含所有可选值的dropdown菜单,从该菜单中可以为Spinner选择一个新值。
在res/values
中添加str_array_services.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="services_array">
<item>@string/str_select_service</item>
<item>@string/str_health_self</item>
<item>@string/str_hs_project</item>
<item>@string/str_com_care</item>
<item>@string/str_find_people</item>
<item>@string/str_dormitory</item>
</string-array>
</resources>
public class LoginActivity extends AppCompatActivity implements AdapterView.OnItemSelectedListener {
private Spinner mSpinner;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
mSpinner = findViewById(R.id.spinner_service);
mSpinner.setOnItemSelectedListener(this);
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this,
R.array.services_array, android.R.layout.simple_spinner_item);
adapter.setDropDownViewResource(android.R.layout.simple_dropdown_item_1line);
mSpinner.setAdapter(adapter);
}
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
// Another interface callback
}
}
Notifaction通知
Android使用Notification实现消息提醒,提供通知栏和通知抽屉查看通知及通知详情。从Android 8.0开始,必须为所有通知分配一个或多个渠道,否则通知将不会显示。Android 8.0及以上版本通过渠道的importance属性设置重要程度,Android 8.0以下版本每条通知通过调用set Priority ()方法进行设置
①小图标,必须提供,调用setSmallIcon()设置;
②应用名称,由系统提供;
③时间戳,由系统提供,可以调用setWhen()替换或者调用setShowWhen(false)隐藏;
④大图标,可选内容,调用setLargeIcon()进行设置;
⑤消息标题,可选内容,调用setContentTitle()设置;
⑥消息文本,可选内容,调用setContentText()设置。
//安卓8以上必须创建通知渠道
@TargetApi(Build.VERSION_CODES.O)
private void createNotificationChannel(String channelId, String channelName, int importance) {
NotificationChannel channel = new NotificationChannel(channelId, channelName, importance);
NotificationManager notificationManager = (NotificationManager) getSystemService(
NOTIFICATION_SERVICE);
notificationManager.createNotificationChannel(channel);
}
//usage
String channelId = "chat";
String channelName = "聊天消息";
int importance = NotificationManager.IMPORTANCE_HIGH;
createNotificationChannel(channelId, channelName, importance);
channelId = "subscribe";
channelName = "订阅消息";
importance = NotificationManager.IMPORTANCE_DEFAULT;
createNotificationChannel(channelId, channelName, importance);
Intent intent1 = new Intent(this, BmiActivity.class);
intent1.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = manager.getNotificationChannel("chat");
if (channel.getImportance() == NotificationManager.IMPORTANCE_NONE) {
Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS);
intent.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName());
intent.putExtra(Settings.EXTRA_CHANNEL_ID, channel.getId());
startActivity(intent);
Toast.makeText(this, "请手动将通知打开", Toast.LENGTH_SHORT).show();
}
}
Notification notification = new NotificationCompat.Builder(this, "chat")
.setContentTitle("收到一条聊天消息")
.setContentText("今天中午吃什么?")
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.nicklogo))
.setAutoCancel(true)
.setContentIntent(PendingIntent.getActivity(this, 1, intent1, PendingIntent.FLAG_UPDATE_CURRENT))
.build();
manager.notify(1, notification);
常用布局🍀
OverView概览
LinearLayout:线性布局,子View水平或垂直方向进行排列
FrameLayout:帧布局,子View以左上角为起点堆叠在一起
RelativeLayout:相对布局,子View之间的相对定位进行排列
GridLayout:网格布局,子View以行、列方式进行排列
ConstraintLayout:约束布局
ViewGroup
常用布局直接或者间接继承自ViewGroup。ViewGroup提供了对其子View的管理功能,包括布局、动画等。子组件可以是View、也可以是ViewGroup。
LinearLayout
线性布局,内部View以水平或垂直方式排列
- orientation属性
vertical(竖直)、horizontal(水平)
layout_weight权重属性
子view的layout_weight属性可以与父布局android:weightSum组合使用
等于0:默认值,指定多大空间就占据多大空间
大于0:将父视图中的可用空间进行分割,值越大权重越大,占据的比例就越大
<Button android:text="权重为2" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight ="2"/>
- gravity属性
布局中内容对齐
layout_gravity属性
布局自身对齐
top、bottom、start、end、center
FrameLayout
帧布局会在屏幕上创建一块空白区域,添加到该区域中的每个子控件占一帧,这些帧会一个一个叠加在一起,后加入的控件会叠加在上一个控件上层,默认显示在屏幕左上角。案例参照
RelativeLayout
相对布局,控制子View以相对定位的方式进行布局显示,即以其它控件或父容器为参照物,摆放控件位置。非常灵活但个人觉得完全可以抛弃他用ConstraintLayout约束布局代替。案例参照
相对父容器位置的属性
属性名称 | 属性描述 |
---|---|
android:layout_centerInParent | 在父视图中正中心 |
android:layout_centerHorizontal | 在父视图的水平中心线 |
android:layout_centerVertical | 在父视图的垂直中心线 |
android:layout_alignParentTop | 紧贴父视图顶部 |
android:layout_alignParentBottom | 紧贴父视图底部 |
android:layout_alignParentLeft | 紧贴父视图左部 |
android:layout_alignParentRight | 紧贴父视图右部 |
使用id描述相对其它子View的属性
属性名称 | 属性描述 |
---|---|
android:layout_alignTop | 与指定视图顶部对齐 |
android:layout_alignBottom | 与指定视图底部对齐 |
android:layout_alignLeft | 与指定视图左部对齐 |
android:layout_alignRight | 与指定视图右部对齐 |
android:layout_above | 在指定视图上方 |
android:layout_below | 在指定视图下方 |
android:layout_toLeftOf | 在指定视图左方 |
android:layout_toRightOf | 在指定视图右方 |
相对其它子View的具体像素值的属性
属性名称 | 属性描述 |
---|---|
android:layout_marginBottom | 离某元素底边缘的距离 |
android:layout_marginLeft | 离某元素左边缘的距离 |
android:layout_marginRight | 离某元素右边缘的距离 |
android:layout_marginTop | 离某元素上边缘的距离 |
GridLayout
网格布局,Android 4.0以上版本新增的布局。使用虚细线将布局划分为行、列和单元格,可以同时在x、y轴方向进行控件的对齐,也分为垂直和水平(默认)布局。案例参照
自身属性
属性 | 描述 | 取值说明 |
---|---|---|
android:alignmentMode | 当设置alignMargins,使视图的外边界之间进行校准 | alignBounds–对齐子视图边界 alignMargins–对齐子视距内容 |
android:columnCount | 最大列数 | 值为4表示每行有4列 |
android:rowCount | 最大行数 | 值为5表示总共有5行 |
android:orientation | 所含子元素的布局方向 | 水平:Horizontal垂直:vertical |
子元素属性
属性 | 描述 | 取值说明 |
---|---|---|
android:layout_column | 显示该子元素所在的列 | 值为0表示在第1列 |
android:layout_row | 显示该子元素所在的行 | 值为2表示在第3行 |
android:layout_columnSpan | 该子元素所占的列数 | 值为2表示占两列 |
android:layout_rowSpan | 该子元素所占的行数 | 值为2表示占两行 |
android:layout_columnWeight | 该子元素的列权重 | 若一行有2列,值为1表示平分 |
android:layout_rowWeight | 该子元素的行权重 | 若共有3行,值为1表示占1/3 |
include重用布局
在开发布局的时候会碰见一些布局重复利用的情况。比如两个不相干的的类,但是他们的布局有部分是一样的,那时候可以使用include标签来重复利用相同的layout,来优化我们的xml文档,提高可读性。也能节省代码冗余。
<?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:orientation="vertical" >
...
<include layout="@layout/demo_layout"/>
...
</LinearLayout>
如果打算用RelateLayout或Linearlayout作为界面根布局,<include>
导入的布局可以考虑用<merge>
作为根节点,而<merge>
根节点内的控件布局取决于<include>
这个布局的父布局是哪个布局。
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_gravity="center_horizontal"
android:text="第一个标签" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_gravity="center_horizontal"
android:text="第二个标签" />
</merge>
ViewBinding⚡
View Binding是Android Studio 3.6推出的新特性,目的是为了替代findViewById(内部实现还是使用findViewById)。在启动视图绑定后,系统会为改模块中的每个xml文件生成一个绑定类,绑定类的实例包含对在相应布局中具有 ID 的所有视图的直接引用。还可以针对某布局关闭ViewBinding。
- Null 安全:由于视图绑定会创建对视图的直接引用,因此不存在因视图 ID 无效而引发 Null 指针异常的风险。此外,如果视图仅出现在布局的某些配置中,则绑定类中包含其引用的字段会使用
@Nullable
标记。 - 类型安全:每个绑定类中的字段均具有与它们在 XML 文件中引用的视图相匹配的类型。这意味着不存在发生类转换异常的风险。
Usage
android {
...
viewBinding {
enabled = true
}
}
}
//activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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">
<Button
android:id="@+id/button"
android:layout_width="100dp"
android:layout_height="40dp"
android:text="这是按钮"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView" />
<TextView
android:id="@+id/textView"
android:layout_width="100dp"
android:layout_height="40dp"
android:gravity="center"
android:text="Hello World!"
app:layout_constraintBottom_toTopOf="@+id/button"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
//MainActivity.java
public class MainActivity extends AppCompatActivity {
private ActivityMainBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//关键代码
binding = ActivityMainBinding.inflate(getLayoutInflater());
View rootView = binding.getRoot();
setContentView(rootView);
//如何使用
binding.textView.setText("这是修改的");
binding.button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Log.d("Main","点击了按钮");
}
});
}
}
Activity基类封装ViewBinding
public class BaseActivity<V extends ViewBinding> extends AppCompatActivity {
protected V v;
protected Activity mContext;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mContext = this;
ParameterizedType type = (ParameterizedType) getClass().getGenericSuperclass();
Class cls = (Class) type.getActualTypeArguments()[0];
try {
Method inflate = cls.getDeclaredMethod("inflate", LayoutInflater.class);
v = (V) inflate.invoke(null, getLayoutInflater());
setContentView(v.getRoot());
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
}
Fragment基类封装ViewBinding
public class BaseFragment<T extends ViewBinding> extends Fragment {
protected T v;
protected Context mContext;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
mContext = getActivity();
ParameterizedType type = (ParameterizedType) getClass().getGenericSuperclass();
Class cls = (Class) type.getActualTypeArguments()[0];
try {
Method inflate = cls.getDeclaredMethod("inflate", LayoutInflater.class, ViewGroup.class, boolean.class);
v = (T) inflate.invoke(null, inflater, container, false);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
return v.getRoot();
}
@Override
public void onDestroyView() {
if (v != null) v = null;
super.onDestroyView();
}
@Override
public void onDestroy() {
super.onDestroy();
v = null;
}
}
MVP架构中的封装了ViewBinding的Activity基类
public abstract class BaseActivityCompat<P extends BasePresenter, V extends ViewBinding> extends AppCompatActivity implements BaseView, Toolbar.OnMenuItemClickListener {
Handler mainHandler;
protected P mPresenter;
protected V v;
protected Activity mContext;
private Bundle savedInstance;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
savedInstance = savedInstanceState;
mContext = this;
beforeInitLayout();
ParameterizedType type = (ParameterizedType) getClass().getGenericSuperclass();
Class cls = (Class) type.getActualTypeArguments()[1];
try {
Method inflate = cls.getDeclaredMethod("inflate", LayoutInflater.class);
v = (V) inflate.invoke(null, getLayoutInflater());
setContentView(v.getRoot());
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
createPresenter();
if (mPresenter != null) mPresenter.attachView(this);
initViews();
initParams();
}
public Bundle getSavedInstance() {
return savedInstance;
}
protected abstract void beforeInitLayout();
protected abstract void initViews(); //初始化控件
protected abstract void initParams(); //初始化参数
private void createPresenter(){
ParameterizedType type = (ParameterizedType) getClass().getGenericSuperclass();
Class cls = (Class) type.getActualTypeArguments()[0];
try {
Constructor constructor = cls.getConstructor(Activity.class);
mPresenter = (P) constructor.newInstance(mContext);
} catch (IllegalAccessException e) {
//e.printStackTrace();
} catch (InstantiationException e) {
//e.printStackTrace();
} catch (NoSuchMethodException e) {
//e.printStackTrace();
try {
mPresenter = (P) cls.newInstance();
LOGE("重新创建了无参Presenter");
} catch (IllegalAccessException illegalAccessException) {
illegalAccessException.printStackTrace();
} catch (InstantiationException instantiationException) {
instantiationException.printStackTrace();
}
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
@Override
public boolean onMenuItemClick(MenuItem item) {
return false;
}
@Override
protected void onDestroy() {
if (mPresenter != null) mPresenter.detachView();
super.onDestroy();
}
@Override
public void jumpPage(Class<?> activity) {
mContext.startActivity(new Intent(this, activity));
}
@Override
public void finishself() {
finish();
}
public void runOnUI(Runnable runnable) {
mainHandler = new Handler(Looper.getMainLooper());
mainHandler.post(runnable);
}
public void toast(String message) {
Toast toast = Toast.makeText(mContext, message, Toast.LENGTH_SHORT);
runOnUI(toast::show);
}
public void LOGE(String log) {
Log.e("==" + this.getClass().getName() + "==>", log);
}
public void LOGW(String log) {
Log.w("==" + this.getClass().getName() + "==>", log);
}
@Override
public Snackbar showSnackBar(String message) {
Snackbar snackbar = Snackbar.make(v.getRoot(), message, Snackbar.LENGTH_SHORT);
runOnUI(snackbar::show);
return snackbar;
}
@Override
public void showToast(String message) {
runOnUI(() -> {
Toast.makeText(mContext, message, Toast.LENGTH_SHORT).show();
});
}
}
MVP架构中的封装了ViewBinding的Fragment基类
public abstract class BaseFragmentCompat<T extends BasePresenter,V extends ViewBinding> extends Fragment implements BaseView {
Handler mainHandler;
protected T mPresenter;
protected V v;
protected FragmentActivity mContext;
private View mRootView;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
ParameterizedType type = (ParameterizedType) getClass().getGenericSuperclass();
Class cls = (Class) type.getActualTypeArguments()[1];
try {
Method inflate = cls.getDeclaredMethod("inflate", LayoutInflater.class, ViewGroup.class, boolean.class);
v = (V) inflate.invoke(null, inflater, container, false);
} catch (NoSuchMethodException | IllegalAccessException| InvocationTargetException e) {
e.printStackTrace();
}
mRootView = v.getRoot();
mContext = getActivity();
createPresenter();
if (mPresenter != null) mPresenter.attachView(this);
initParams();
initViews();
return mRootView;
}
protected abstract void initViews();
protected abstract void initParams();
protected abstract void createPresenter();
@Override
public void onDestroyView() {
if (mPresenter != null) mPresenter.detachView();
if (v != null) v = null;
super.onDestroyView();
}
@Override
public void onDestroy() {
super.onDestroy();
v = null;
}
protected void runOnUI(Runnable runnable) {
mainHandler = new Handler(Looper.getMainLooper());
mainHandler.post(runnable);
}
@Override
public Snackbar showSnackBar(String message) {
Snackbar snackbar = Snackbar.make(v.getRoot(), message, Snackbar.LENGTH_SHORT);
runOnUI(snackbar::show);
return snackbar;
}
}
Resource🌵
Android资源目录一般分为两大部分。一是主要的资源文件,是放在res文件夹里面的,他会由aapt检查资源合法性并自动生成R class。二是assets文件夹是存放不进行编译加工的原生文件。
资源目录及解释
目录 | 资源类型Resource Types |
---|---|
res/animator/ | 用于定义属性动画的 XML 文件。 |
res/anim/ | 用于定义渐变动画的 XML 文件。(属性动画也可保存在此目录中,但为了区分这两种类型,属性动画首选 animator/ 目录。) |
res/drawable/ | 位图文件(.png 、.9.png 、.jpg 、.gif )或编译为可绘制对象资源子类型的 XML 文件。 |
res/mipmap/ | 适用于不同启动器图标密度的可绘制对象文件。 |
res/color/ | 用于定义颜色状态列表的 XML 文件 |
res/layout/ | 用于定义用户界面布局的 XML 文件 |
res/values/ | 包含字符串、整型数和颜色等简单值的 XML 文件。其他 res/ 子目录中的 XML 资源文件会根据 XML 文件名定义单个资源,而 values/ 目录中的文件可描述多个资源。对于此目录中的文件,<resources> 元素的每个子元素均会定义一个资源。例如,<string> 元素会创建 R.string 资源,<color> 元素会创建 R.color 资源。 |
res/xml/ | 可在运行时通过调用 Resources.getXML() 读取的任意 XML 文件。各种 XML 配置文件(如可搜索配置)都必须保存在此处。 |
res/raw/ | 由于每个资源均使用自己的 XML 元素进行定义,因此您可以随意命名文件,并在某个文件中放入不同的资源类型。但是,您可能需要将独特的资源类型放在不同的文件中,使其一目了然。例如,对于可在此目录中创建的资源,下面给出了相应的文件名约定:需以原始形式保存的任意文件。如要使用原始 InputStream 打开这些资源,请使用资源 ID(即 R.raw.*filename* )调用 Resources.openRawResource() 。 |
res/font/ | 带有扩展名的字体文件(如 .ttf 、.otf 或 .ttc ),或包含 <font-family> 元素的 XML 文件。 |
assests | 资源打包到应用程序中的静态文件,这些文件不会被编译,最终会直接部署到目标设备中;另外,不能直接通过 R 资源类读取,只能使用流的形式读取。 |
代码读取res文件夹下资源
Color Value语法: #color_value 可以保存在res/values/colors.xml (文件名可以任意)。
xml引用:android:textColor="@color/color_name"
Java引用: int color = Resources.getColor(R.color.color_name)
Drawables语法: Drawable 可以保存在res/drawable/some_file。
xml引用:android:background="@drawable/name"
java引用:Drawable redDrawable = Resources.getDrawable(R.drawable.name)
dimension语法: dimen_value单位 一般保存为res/values/dimen.xml。
XML: android:textSize="@dimen/some_name"
Java: float dimen = Resources.getDimension(R.dimen.some_name)
Context.getResources();
<Resources>.openRawResource(R.raw.somefilename);
<Resources>.getXml(R.xml.xx);
<Resources>.getString(R.)或者Resources.getText()获取String。
<Resources>.getStringArray(R.array.category);
代码读取res/raw和assets文件夹资源
InputStream is = getResources().openRawResource(R.raw.filename);
InputStream is = getResources().getAssets().open("filename");
public String getFromRaw(){
try {
InputStreamReader inputReader = new InputStreamReader( getResources().openRawResource(R.raw.test1));
BufferedReader bufReader = new BufferedReader(inputReader);
String line="";
String Result="";
while((line = bufReader.readLine()) != null)
Result += line;
return Result;
}
catch (Exception e) {
e.printStackTrace();
}
}
public String getFromAssets(String fileName){
try {
InputStreamReader inputReader = new InputStreamReader( getResources().getAssets().open(fileName) );
BufferedReader bufReader = new BufferedReader(inputReader);
String line="";
String Result="";
while((line = bufReader.readLine()) != null)
Result += line;
return Result;
}
catch (Exception e) {
e.printStackTrace();
}
}
private void copyFileFromAssets(Context context, String fileName) {
InputStream in = null;
FileOutputStream out = null;
String path = context.getFilesDir().getAbsolutePath();
File file = new File(path + fileName);
File filePath = new File(path);
if (!filePath.exists())
filePath.mkdirs();
if (file.exists())
return;
try {
in = context.getAssets().open(fileName);
out = new FileOutputStream(file);
int length = -1;
byte[] buf = new byte[1024];
while ((length = in.read(buf)) != -1) {
out.write(buf, 0, length);
}
out.flush();
}
catch (Exception e) {
e.printStackTrace();
}
finally {
try {
if (in != null) in.close();
if (out != null) out.close();
}
catch (IOException e1) {
e1.printStackTrace();
}
}
}
四大组件之Activity🌊
Activity是表示一个视图的层次结构的窗口,是应用程序的一个组件,Activity需要在AndroidManifest配置文件中注册。一般继承自AppCompatActivity,拥有一个布局文件,并且在创建时使用setContentView初始化页面布局。
- 入口Activity(Launcher显示图标)
<activity android:name=".ui.single.SplashPage">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
- 接收隐式Intent,scheme跳转
<activity
android:name=".ui.mvp.MainFrame.MainFrame"
android:theme="@style/AppTheme.MD.NoActionBar"
android:windowSoftInputMode="adjustPan">
<intent-filter tools:ignore="AppLinkUrlError">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="freya"
android:host="fastassist" />
</intent-filter>
</activity>
- 其他属性
android:windowSoftInputMode 表示Activity如何与包含屏幕软键盘的窗口交互。该属性会影响软键盘的状态,对Activity主窗口的调整。
android:label 描述该Activity的一个标签,通常是随着Activity图标一起显示出来的
android:icon 代表Activity和图标。在Activity被显示的时候,就用该图标显示给用户
android:launchMode 描述了该Activity应该如何被启动。standard,singleTop,singleTask和singleInstance
android:noHistory 设置在用户离开该Activity,并且它在屏幕上不再可见的时候,是否应该从Activity的堆栈中删除
android:excludeFromRecents 表示应用程序是否应该将Activity从最近运行的应用程序列表排除
android:screenOrientation 表示Activity显示的方向(比如纵向,横向)landscape,portrait
android:hardwareAccelerated 是否应为该Activity启动硬件加速
android:exported 表示Activity是否可以由其他应用程序中的组件来启动
Activity生命周期
方法名称 | 功能描述 |
---|---|
onCreate | Activity实例被Android系统创建时调用,通常在该方法中完成初始化操作,如:加载布局、初始化数据、初始化控件、绑定控件的响应事件等 |
onStart | Activity由不可见变为可见时调用,但还不能与用户交互 |
onResume | Activity初始化完成后,与用户交互时调用,此时Activity位于栈顶,并且处于运行状态 |
onPause | Activity由可见变为不可见时调用,新Activity的onResume()必须在这个方法执行完才能调用,所以该方法只能执行一些耗时少的操作,如:保存一些关键数据 |
onStop | Activity完全不可见时调用,它与onPause()的区别在于:onPause()只在启动一个对话框式的Activity时才会调用 |
onDestroy | Activity被销毁之前调用,完成回收和资源释放等操作 |
onRestart | Activity由不可见返回可见状态时调用,如:Home键切换界面,重新回到界面的过程 |
生命周期中各个方法的含义和作用
onCreate:create
表示创建,这是Activity生命周期的第一个方法,也是我们在android开发中接触的最多的生命周期方法。它本身的作用是进行Activity的一些初始化工作,比如使用setContentView加载布局,对一些控件和变量进行初始化等。但也有很多人将很多与初始化无关的代码放在这,其实这是不规范的。此时Activity还在后台,不可见。所以动画不应该在这里初始化,因为看不到……
onStart:start
表示启动,这是Activity生命周期的第二个方法。此时Activity已经可见了,但是还没出现在前台,我们还看不到,无法与Activity交互。其实将Activity的初始化工作放在这也没有什么问题,放在onCreate中是由于官方推荐的以及我们开发的习惯。
onResume:resume
表示继续、重新开始,这名字和它的职责也相同。此时Activity经过前两个阶段的初始化已经蓄势待发。Activity在这个阶段已经出现在前台并且可见了。这个阶段可以打开独占设备
onPause:pause
表示暂停,当Activity要跳到另一个Activity或应用正常退出时都会执行这个方法。此时Activity在前台并可见,我们可以进行一些轻量级的存储数据和去初始化的工作,不能太耗时,因为在跳转Activity时只有当一个Activity执行完了onPause方法后另一个Activity才会启动,而且android中指定如果onPause在500ms即0.5秒内没有执行完毕的话就会强制关闭Activity。从生命周期图中发现可以在这快速重启,但这种情况其实很罕见,比如用户切到下一个Activity的途中按back键快速得切回来。
onStop:stop
表示停止,此时Activity已经不可见了,但是Activity对象还在内存中,没有被销毁。这个阶段的主要工作也是做一些资源的回收工作。
onDestroy:destroy
表示毁灭,这个阶段Activity被销毁,不可见,我们可以将还没释放的资源释放,以及进行一些回收工作。
onRestart:restart
表示重新开始,Activity在这时可见,当用户按Home键切换到桌面后又切回来或者从后一个Activity切回前一个Activity就会触发这个方法。这里一般不做什么操作。
常见问题
1、第一次运行程序时调用的生命周期方法为:onCreate() —> onStart() —> onResume()
2、退出时调用的生命周期方法为:onPause() —> onStop() —> onDestory()
3、当它在屏幕前台时,它是激活或运行状态(Running),能响应用户操作
4、当它上面有另外一个Activity,使它失去了焦点但仍然对用户可见时,处于暂停状态(Pause),在它之上的Activity没有完全覆盖屏幕,或者是透明的,被暂停的Activity仍然对用户可见,并且是存活状态,如果系统处于内存不足时会杀死这个Activity
5、当它完全被另一个Activity覆盖时则处于停止状态(stop),它仍然保留所有的状态和成员信息,然而对用户是不可见的,所以它的窗口将被隐藏,如果其它地方需要内存,则系统经常会杀死这个Activity
避免横竖屏切换重建Activity
设置configChanges属性
当进行横竖屏切换时不会再执行其它的生命周期方法,如果希望某一个界面一直处于竖屏或者横屏状态,不随手机的晃动而改变,可以在清单文件中通过设置Activity的screenOrientation属性完成
<activity android:name=".MainActivity"
android:configChanges="orientation|keyboardHidden|screenSize">
设置Activity的screenOrientation属性
如果希望某一个界面一直处于竖屏或者横屏状态,不随手机的晃动而改变,可以在清单文件中通过设置Activity的screenOrientation属性完成
竖屏:android:screenOrientation="portrait"
横屏:android:screenOrientation="landscape"
Activity启动模式
standard模式
默认模式,每启动一个Activity就会在栈顶创建一个新的实例,ctivity的onCreate、onStart、onResume方法都会被调用,并放入Back Stack中
singleTop模式
会判断要启动的Activity实例是否位于栈顶,如果位于栈顶则直接复用,否则创建新的实例。适用于接收到通知启动的内容显示界面,如:QQ接收到消息后弹出的界面、新闻推送打开的界面
singleTask模式
每次启动该Activity时,系统首先会检查栈中是否存在当前Activity实例,如果存在则直接使用,并把当前Activity之上的所有实例全部出栈。适合作为程序的入口点,例如:浏览器的主界面
singleInstance模式
启动一个新的任务栈来管理Activity实例,无论从哪个任务栈中启动该Activity,该实例在整个系统中只有一个。其效果相当于多个应用共享同一个应用。适合需要与程序分离开的页面,例如:闹铃提醒,将闹铃提醒与闹铃设置分离
启动模式的设置
启动模式有2种设置方式:在AndroidMainifest
设置、通过Intent
设置标志位。Intent设置方式的优先级> Manifest设置方式
Manifest设置方式无法设定FLAG_ACTIVITY_CLEAR_TOP;Intent设置方式无法设置单例模式(SingleInstance)
在AndroidMainifest设置
<activity
android:name="com.demo.Main4Activity"
//通过android:launchMode属性设置
android:launchMode="singleTask"/>
通过Intent
设置标志位
Intent inten = new Intent (ActivityA.this,ActivityB.class);
//通过Intent的Flag设置
intent,addFlags(Intent,FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
标记位属性
标记位属性 | 含义 |
---|---|
FLAG_ACTIVITY_SINGLE_TOP | 指定启动模式为栈顶复用模式(SingleTop ) |
FLAG_ACTIVITY_NEW_TASK | 指定启动模式为栈内复用模式(SingleTask ) |
FLAG_ACTIVITY_CLEAR_TOP | 所有位于其上层的Activity都要移除,SingleTask 模式默认具有此标记效果 |
FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS | 具有该标记的Activity不会出现在历史Activity的列表中, 即无法通过历史列表回到该Activity上 |
Activity事件处理
Activity使用Intent
显示意图
显式意图可以直接通过名称开启指定的目标组件
Intent intent = new Intent(this, SecondActivity.class);
startActivity(intent);
隐式意图
隐式意图通过指定action和category等属性,系统根据这些信息寻找目标目标组件,如:打电话、发短信等
<activity android:name=".SecondActivity">
<intent-filter>
<action android:name="com.kirito666.invite" />
<category android:name="android.intent.category.DEFAULT" />
</ intent-filter>
</ activity>
Intent intent = new Intent();
intent.setAction("com.kirito666.invite");
intent.addCategory(Intent.CATEGORY_DEFAULT);
startActivity(intent);
系统提供的常用隐式Intent
Intent类的系统动作常量名 | 说明 |
---|---|
ACTION_MAIN | App启动时的入口 |
ACTION_VIEW | 显示数据 |
ACTION_EDIT | 显示可编辑的数据 |
ACTION_SEND | 分享内容 |
ACTION_CALL | 直接拨号 |
ACTION_DIAL | 准备拨号 |
ACTION_SENDTO | 发送短信 |
ACTION_ANSWER | 接听电话 |
ACTION_SEARCH | 导航栏SearchView的搜索动作 |
public void openBaidu() {
Intent intent = new Intent();
intent.setData(Uri.parse("http://baidu.com"));
intent.setAction(Intent.ACTION_VIEW);
this.startActivity(intent); //启动浏览器
}
Intent传递数据
使用setData()方法传递Uri类型的数据
intent.setData(Uri.parse("http://www.baidu.com"));
intent.setData(Uri.fromFile(new File("/sdcard/sample.jpg")));
//接收数据
Uri locationUri = intent.getData();
使用putExtra()传递Key-Value数据对
intent.putExtra(String name, int value);
intent.putExtra(String name, String[] value);
intent.putExtras(Bundle bundle);
//接收数据
int level = intent.getIntExtra("level", 0);
Bundle bundle = intent.getExtras();
数据回传
onActivityResult
① 调用startActivityForResult()启动第2个Activity
② 在第2个Activity返回数据创建一个Intent对象
调用putExtra()传递回传的数据
设置返回结果为Activity_RESULT_OK或Activity_CANCELED
调用finish()关闭这个Activity
③ 在第1个Activity实现onActivityResult()方法接收回传的数据
Activity Results
在androidx中的Activity中使用Activity Results API替代onActivityResult
//gradle
implementation "androidx.activity:activity:lastversion"
implementation "androidx.fragment:fragment:lastversion
public class JumpActivity extends BaseActivity<ActivityJumpBinding> {
private final ActivityResultLauncher<String> launcher = registerForActivityResult(new ResultContract(), new ActivityResultCallback<String>() {
@Override
public void onActivityResult(String result) {
Toast.makeText(JumpActivity.this, result, Toast.LENGTH_SHORT).show();
}
});
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_jump);
v.button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(JumpActivity.this,Jump2Activity.class);
User user = new User();
user.name = "唐钱进";
user.age = 22;
//intent.putExtra("user",user);
Bundle bundle = new Bundle();
bundle.putSerializable("user",user);
intent.putExtras(bundle);
startActivity(intent);
launcher.launch("传递字符串");
}
});
}
static class ResultContract extends ActivityResultContract<String, String> {
@NonNull
@Override
public Intent createIntent(@NonNull Context context, String input) {
return null;
}
@Override
public String parseResult(int resultCode, @Nullable Intent intent) {
return intent.getStringExtra("s");
}
}
}
Activity常用方法
Context
Context环境上下文其实就是指与android服务(AMS,WMS)交互的通信接口。
Activity、Service、Application间接的继承Context。应用程序进程中Context的数量为Activity的数量加Service的数量加Application的数量。一个应用程序进程的Application只有一个。
- ContextImpl
继承自Context,是Context的实现类
- ContextWrapper
继承自Context,是ContextImpl的包装类,内部将ContextImpl对象保存在类型为Context的成员变量mBase,大部分方法通过调用ContextImpl的相应方法实现。
- ContextThemeWrapper,Service,Application
继承自ContextWrapper,通过ContextImpl对象调用Context方法,在ContextWrapper的基础上添加了不同的功能。
- Activity
继承自ContextThemeWrapper,ContextThemeWrapper中包含与主题相关的方法,因此Activity继承ContextThemeWrapper。
Context的应用场景
Activity Context
Activity Context 的创建可以定位到 Activity 启动过程,在 ActivityThread 中。
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
ContextImpl appContext = ContextImpl 作为参数
关联ContextImpl(r);
Activity activity = null;
try {
java.lang.ClassLoader cl = appContext.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
...
appContext.setOuterContext(activity);
activity.attach(appContext, this, getInstrumentation(), r.token,r.ident, app, r.intent, r.activityInfo, title, r.parent,r.embeddedID, r.lastNonConfigurationInstances, config,r.referrer, r.voiceInteractor, window, r.configCallback);
}
这里分为 4 步来看: - 创建 ContextImpl 对象 - 创建 Activity 实例 - ContextImpl 关联 Activity - Activity 关联 ContextImpl
STEP1:创建的是对应的 Activity 的 ContextImpl
private ContextImpl createBaseContextForActivity(ActivityClientRecord r) {
...
ContextImpl appContext = ContextImpl.createActivityContext(
this, r.loadedApk, r.activityInfo, r.token, displayId, r.overrideConfig);
...
static ContextImpl createActivityContext(ActivityThread mainThread,
...
ContextImpl context = new ContextImpl(null, mainThread, loadedApk, activityInfo.splitName,
activityToken, null, 0, classLoader);
...
STEP2:接着实例化 Activity,通过类加载创建 Activity
public Activity newActivity(ClassLoader cl, String className,
Intent intent)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
return (Activity)cl.loadClass(className).newInstance();
}
STEP3/4:通过 ContextImpl 和 Activity 的相互关联,Activity Context 的创建也就完成了,以后 Activity 的任务大多就是通过 ContextImpl 实现。
//第三步,在ContextImpl 中
final void setOuterContext(Context context) {
mOuterContext = context;
}
//第四步,在 Activity中
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
Window window, ActivityConfigCallback activityConfigCallback) {
...
attachBaseContext(context);
...
}
@Override
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(newBase);
...
}
//在 ContextWrapper 中
protected void attachBaseContext(Context base) {
if (mBase != null) {
throw new IllegalStateException("Base context already set");
}
mBase = base;
}
Activity Context 的使用
Activity Context 的实现就是 Activity ,因此如果对 Context 使用不当的话很容易发生内存泄漏,比如下面这两段代码
public class A
{
private static Context mContext;
public static void setContext(Context context){
mContext = context;
}
}
public class B
{
private static B sInstance;
private Context mContext;
private B(Context context)
{
this.mContext = context;
}
public static synchronized B getInstance(Context context)
{
if (sInstance == null)
{
sInstance = new B(context);
}
return sInstance;
}
}
在第一段代码中有一个静态的 Context ,在第二段是一个单例模式,一个静态实例拥有一个 Context 变量。在这两种情况下,Context 的周期就和应用程序一样,这是如果赋值时 Activity就会使得 Activity 在退出后不能正常被回收,因为还有 Context 引用。因此建议尽量 Application Context ,因为 Application Context 的周期就是整个应用程序,所以不用担心内存泄漏。但是在某些情况比如创建 Dialog, 或者启动组建的时候就只能使用 Activity Context 或者 Service Context ,这个时候就要注意内存泄漏问题了。
Fragment
Fragment是嵌在Activity中使用的UI片段 ,一 个Activity中可以组合多个Fragment,多 个Activity可重复使用某个Fragment。Fragment可以视为Activity的模块化组成部分,它具有自己的生命周期,能处理用户事件,并能在Activity运行时动态添加或移除。
创建Fragment
public class ProfileFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_profile, container, false);
}
}
Fragment生命周期
Activity创建时,与之关联的Fragment也被创建,并处于启动状态。Activity调用Fragment的onAttach()方法使得Fragment附属于Activity,然 后Activity通过调用onAttachFragment()方法接收到Fragment的引用。
Fragment调用onCreate()方法进行创建,接着调用onCreateView()方法构建界面视图,当Activity包含多个Fragment时,这个过程就会进行多遍,确保Fragment都被创建。
当Activity处于运行状态时,可以动态添加、删除或替换Fragment,当Fragment被添加时处于启动状态,被删除时则处于销毁状态。当Activity暂停时,该Activity关联的所有Fragment都会被暂停,当Activity被销毁时,Activity中所有的Fragment也会被销毁。
-Fragment有与Activity相同的生命周期方法,但比Activity多出几个方法,说明如下:
-onAttach():Fragment与Activity关联时调用;
-onCreateView():Fragment创建界面视图时调用;可能被多次执行
-onActivityCreated():Fragment关联的Activity已经创建完成时调用;
-onDestroyView():Fragment的视图被销毁时调用;
-onDetach():Fragment与Activity解除关联时调用;
Fragment与Activity数据传递
使用实例方法
通过FragmentManager实例的findFragmentById()方法获取Fragment实例,从而调用Fragment public的方法
//Activity向Fragment传数据
TitleFragment titleFragment = (TitleFragment) getSupportFragmentManager()
.findFragmentById(R.id.fragment_title);
通过Fragment实例的getActivity()方法获取关联的Activity对象,从而调用Activity public的方法。getActivity()还可以获取Fragment的上下文。
//Fragment向Activity传数据
MainActivity activity = (MainActivity) getActivity();
使用Bundle传递数据
通过初始化Fragment时调用setArgument()方法将Bundle对象传递给Fragment
// 创建Fragment对象
public static ContentFragment newInstance(String param) {
ContentFragment fragment = new ContentFragment();
// 使用Bundle对象装载数据
Bundle args = new Bundle();
args.putString("param", param);
// 传递bundle对象
fragment.setArguments(args);
return fragment;
}
使用Listener监听器
首先在Fragment类中定义事件监听的监听器接口,Activity类实现 该监听器。Fragment 在其onAttach() 生命周期方法中捕获接口实现,然后调用接口方法,触发监听方法与 Activity 通信。
Fragment使用FragmentResultOwner接口互传数据
从 Fragment 1.3.0-alpha04开始,每一个FragmentManager实现了FragmentResultOwner接口。这意味着FragmentManager可以像一个贮存库作为Fragment间通讯的媒介。
implementation "androidx.fragment:fragment:1.3.0-rc02"
- 首先给fragmentA设置监听器
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getParentFragmentManager().setFragmentResultListener("key", this, new FragmentResultListener() {
@Override
public void onFragmentResult(@NonNull String key, @NonNull Bundle bundle) {
// 这里使用的是String,但是任何其他能够被放在Bundle中的数据类型都是支持的
String result = bundle.getString("bundleKey");
//做一些其他事情
}
});
}
- 在FragmentB中,生产结果,需要注意的是,FragmentB必须使用和FragmentA一样的FragmentManager,使用相同的
requestKey
一旦FragmentA处于STARTED
状态,它将会接受到结果并且执行监听回调。
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Bundle result = new Bundle();
result.putString("bundleKey", "result");
getParentFragmentManager().setFragmentResult("requestKey", result);
}
});
哼哼哼
叼茂SEO.bfbikes.com
看的我热血沸腾啊