文章目录
- 布局
- LinearLayout
- RelativeLayout
- TableLayout
- FrameLayout
- ConstraintLayout
- ListView
- 基于ArrayAdapter
- 自定义Adaper
- 提升ListView的运行效率
- RecyclerView
- 基本属性
- 使用案例
- 布局(显示方式)
- 监听事件
- 利用View.onClickListener 和 onLongClickListener
- ViewPager
- 动画
- 帧动画
- 补间动画
- 属性动画
- Activity
- AndroidMainfest.xml
- 生命周期
- onCreate和onStart的区别
- onPause和onStop的区别
- 生命周期的变化
- Activity的启动
- Intent
- Bundle
- Activity携带参数返回
- Activity启动模式
- 任务(task),返回栈(back stack)
- Activity的四种启动模式
- standard(默认模式)
- singleTop
- singleTask
- singleInstance
- Activity中获取View的宽高
- 应用:动态调整ImageView的宽高
- Fragment
- Fragment的优点
- Fragmet的生命周期
- 生命周期变化
- Fragment与Activity不同的生命周期
- 加载和使用Fragmet
- 向Activity中添加Fragment
- 静态加载
- 动态加载
- 管理Fragmet
- Fragment间通信
- Fragment与其附着的Activity之间通信方式
- DialogFragment
布局
安卓常用的布局方式有六种(绝对布局因灵活性太差已经弃用):
- 线性布局
- 相对布局
- 网格布局
- 表格布局
- 帧布局
- 约束布局
LinearLayout
LinearLayout里面可以放置多个view(这里称为子view,子项)。 子view可以是TextView,Button,或者是LinearLayout,RelativeLayout等等。 它们将会按顺序依次排布为一列或一行。
常用属性
- orientation:确定水平或竖直排布子view。 可选值有vertical和horizontal。
- gravity:决定子view的排布方式。gravity有“重力的意思”,引申为子view会向哪个方向靠拢,gravity有几个选项可以选择,我们常用的有start,end,left,right,top,bottom。
- 子view的layout_gravity:gravity是控制自己内部的子元素,layout_gravity是告诉父元素自己的位置。可以设置子view的layout_weight来控制空间占比,设置layout_weight的时候,一般要设置子view的layout_width(水平排布时)或者layout_height(垂直排布时)为0。
- divider:设置divider和showDivider属性,使得子view之间有分割线。
RelativeLayout
RelativeLayout和LinearLayout类似,都是ViewGroup,能“容纳”多个子view。RelativeLayout 是一个以相对位置
显示子视图的视图组。每个视图的位置可以指定为相对于同级元素的位置
(例如,在另一个视图的左侧或下方)或相对于父级
RelativeLayout 区域的位置(例如在底部、左侧或中心对齐)((由 ID 确定)的位置
)。
TableLayout
表格布局,通过设置表格的行列,构建布局。
常用属性
layout_column:显示在第几列
layout_columnSpan:横向跨几列
layout_columnWeight:剩余空间分配方式
layout_gravity:在网格中的显示位置
layout_row:显示在第几行
layout_rowSpan:横向跨几行
layout_rowWeight:纵向剩余空间分配
FrameLayout
帧布局:特点是子view是可以重叠的。
ConstraintLayout
ConstraintLayout 可让您使用扁平视图层次结构(无嵌套视图组)创建复杂的大型布局。它与RelativeLayout 相似,其中所有的视图均根据同级视图与父布局之间的关系进行布局,但其灵活性要高于 RelativeLayout。
注意:约束布局要求每个视图至少有两个约束条件:一个水平约束,一个垂直约束。如果缺少某个方向的约束,比如垂直,那么默认是贴近上边界。
还可以通过设定基准线,指定控件相对于基础线的约束布局。
ListView
ListView,列表视图,能够根据列表中选项个数自适应屏幕显示。ListView本身类似于布局容器,它的子View需要另外定义。在APP运行时,每个列表选项是一个子模块,这个子模块有视图,有对应的数据需要填充到视图,每个子模块还需要绑定对应的事件函数。
安卓开发中,使用了适配器
设计模式来处理ListView的显示流程。
适配器模式的定义为:将一个类的接口转为客户所期待的 另一种接口,从而使得原本接口不匹配而无法工作在一起的两个类,能够在一起工作。
ListView期待的是一个有视图有数据有交互功能的列表子模块,在程序运行中,我们先把数据和视图一起加工处理为listview期待的类型,再交给listview工作。
基于ArrayAdapter
如果列表中每个选项的内容可以由一个简单的基本数据类型表示,那么可以使用ArrayAdapter来实现。
例如下边的列表,只需要显示蓝牙的地址,那么只需要一个String类型作为数据传入。
构建步骤如下:
- 在activity的布局文件中:声明一个ListView。
- 额外编写一个device_name.xml,它是每个选项的视图,对于我们的需要而言,一个TextView足以。
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="18sp"
android:padding="5dp"
/>
- 在activity中:
将device_name.xml构建为ArrayAdpter,然后把适配器交给ListView。当需要向列表中增加选项时,直接调用mPairedDevicesArrayAdapter.add()即可。
//初使化设备适配器存储数组
mPairedDevicesArrayAdapter = new ArrayAdapter<String>(this, R.layout.device_name);
mUnPairedDevicesArrayAdapter = new ArrayAdapter<String>(this, R.layout.device_name);
//设置已配队设备列表
ListView pairedListView = findViewById(R.id.pairedListView);
pairedListView.setAdapter(mPairedDevicesArrayAdapter);
pairedListView.setOnItemClickListener( mDeviceClickListener);
// 设置新查找设备列表
ListView newDevicesListView = findViewById(R.id.unPairedListView);
newDevicesListView.setAdapter(mUnPairedDevicesArrayAdapter);
newDevicesListView.setOnItemClickListener(mDeviceClickListener);
// 得到本地蓝牙句柄
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
// //添加已配对设备到列表并显示
if (pairedDevices.size() > 0) {
findViewById(R.id.pairedListView).setVisibility(View.VISIBLE);
for (BluetoothDevice device : pairedDevices) {
mPairedDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress());
}
} else {
String noDevices = "没有找到已配对的设备。" ;
mPairedDevicesArrayAdapter.add(noDevices);
}
自定义Adaper
只能显示一段文本的listview太单调了,我们现在就来对listview的界面进行定制,让其丰富内容。
构建步骤如下:
- 在activity.xml中声明一个ListView
- 构建每个列表选项中的数据类Fruit:
package com.example.listview2;
public class Fruit {
private int imageID;
private String name;
private String price;
public int getImageID() {
return imageID;
}
public String getName() {
return name;
}
public String getPrice() {
return price;
}
public Fruit(int imageID, String name, String price) {
this.imageID = imageID;
this.name = name;
this.price = price;
}
}
- 构建每个列表选项布局文件fruit_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:orientation="horizontal"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/fruit_image"
android:src="@drawable/apple"
android:layout_width="100dp"
android:layout_height="80dp"/>
<TextView
android:id="@+id/fruit_name"
android:layout_gravity="center_vertical"
android:textSize="30sp"
android:textColor="#000000"
android:text="name"
android:layout_marginLeft="10dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/fruit_price"
android:layout_gravity="center_vertical"
android:textColor="#ff0000"
android:text="price"
android:textSize="30sp"
android:layout_marginLeft="10dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
- 自定义FruitAdpter类继承自ArrayAdpter
编写构造方法(构造方法需要传递上下文,数据内容),重写getView()函数。
package com.example.listview2;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.List;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
//用于将上下文、listview 子项布局的 id 和数据都传递过来
public class FruitAdapter extends ArrayAdapter<Fruit> {
public FruitAdapter(@NonNull Context context, int resource, @NonNull List<Fruit> objects) {
super(context, resource, objects);
}
//每个子项被滚动到屏幕内的时候会被调用
@NonNull
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
Fruit fruit=getItem(position);//得到当前项的 Fruit 实例
//为每一个子项加载设定的布局
View view=LayoutInflater.from(getContext()).inflate(R.layout.fruit_item,parent,false);
//分别获取 image view 和 textview 的实例
ImageView fruitimage =view.findViewById(R.id.fruit_image);
TextView fruitname =view.findViewById(R.id.fruit_name);
TextView fruitprice=view.findViewById(R.id.fruit_price);
// 设置要显示的图片和文字
fruitimage.setImageResource(fruit.getImageID());
fruitname.setText(fruit.getName());
fruitprice.setText(fruit.getPrice());
return view;
}
}
- 在activity中,准备数据,构建ListView并设置Adapter。
package com.example.listview2;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ListView;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
//第一步:定义对象
ListView listView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//第二步:绑定控件
listView = (ListView) findViewById(R.id.list_view);
//第三步:准备数据
List<Fruit> fruitlist = new ArrayList<>();
for (int i = 0; i <2 ; i++) {
Fruit pineapple=new Fruit(R.drawable.pineapple,"菠萝","¥16.9 元/KG");
fruitlist.add(pineapple);
Fruit mango = new Fruit(R.drawable.mango, "芒果","¥29.9 元/kg");
fruitlist.add(mango);
Fruit pomegranate = new Fruit(R.drawable.pomegranate, "石榴","¥15元/kg");
fruitlist.add(pomegranate);
Fruit grape = new Fruit(R.drawable.grape, "葡萄","¥19.9 元/kg");
fruitlist.add(grape);
Fruit apple = new Fruit(R.drawable.apple, "苹果","¥20 元/kg");
fruitlist.add(apple);
Fruit orange = new Fruit(R.drawable.orange, "橙子","¥18.8 元/kg");
fruitlist.add(orange);
Fruit watermelon = new Fruit(R.drawable.watermelon, "西瓜","¥28.8元/kg");
fruitlist.add(watermelon);
}
//第四步:设计每一个列表项的子布局
//第五步:定义适配器 控件 -桥梁-数据
FruitAdapter adapter=new FruitAdapter(MainActivity.this,R.layout.fruit_item,fruitlist);
listView.setAdapter(adapter);
}
}
提升ListView的运行效率
目前我们的ListView的运行效率是很低的,因为在FruitAdapter的getView()方法中,每次都将布局重新加载了一遍,当页面快速滚动的时候,这将成为性能的瓶颈。
优化方法一:
仅在convertView为null时才创建:
优化方法二:
新增内部类ViewHolder对控件实例进行缓存。
public class FruitAdapter extends ArrayAdapter {
public FruitAdapter(@NonNull Context context, int resource, @NonNull List<Fruit> objects) {
super(context, resource, objects);
}
@NonNull
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
ViewHolder viewHolder;
if (convertView == null) {
viewHolder = new ViewHolder();
convertView = LayoutInflater.from(getContext()).inflate(R.layout.item, parent, false);
viewHolder.avatar = convertView.findViewById(R.id.avatar);
viewHolder.name = convertView.findViewById(R.id.fruit_name);
viewHolder.price = convertView.findViewById(R.id.fruit_price);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
Fruit fruit = (Fruit) getItem(position);
viewHolder.price.setText(fruit.getPrice().toString());
viewHolder.name.setText(fruit.getName());
viewHolder.avatar.setText(fruit.getAvatar());
return convertView;
}
private final class ViewHolder {
TextView avatar, name, price;
}
RecyclerView
RecyclerView是ListView的升级版,它更加灵活,使用更加简单。在ListView中我们可以自己实现ViewHolder以及convertView进行优化,但是在RecyclerView中,它直接封装了ViewHolder的回收利用,也就是RecyclerView将ViewHolder标准化,我们不需要面向 view ,而是直接面向 ViewHolder 编写实现我们需要的 Adapter,这样一来,逻辑结构就变得非常清晰。
RecyclerView常常搭配线性布局和网格布局使用。
基本属性
- itemAnimator:增删动画
- itemDecoration:分割线
注意:RecyclerView 本身是不提供点击、长按事件的,而隔壁的 ListView 稳稳支持。对此,可能刚接触 RecyclerView 的同学会疯狂吐槽,怎么作为升级版的 RecyclerView 在这一点上还不如旧版呢?
显然不是。
ListView 中对于点击事件的处理,其实是有很大弊端的,它的 setOnItemClickListener() 方法是为子项注册点击事件,这就导致只能识别到这一整个子项,对于子项中的组件比如按钮就束手无策了。为此,RecyclerView 直接放弃了这个为子项注册点击事件的监听方法,所有点击事件都有具体 View 去注册,好处显而易见,我可以按需为组件注册点击事件,不存在点击不到的组件。
RecyclerView 的核心使用流程如下:
mRecyclerView = findView(R.id.id_recycler_view);
//设置布局管理器
mRecyclerView.setLayoutManager(mLayoutManager);
//设置adapter
mRecyclerView.setAdapter(mAdapter)
//设置Item增加、移除动画
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
//添加分割线
mRecyclerView.addItemDecoration(new DividerItemDecoration(
getActivity(), DividerItemDecoration.HORIZONTAL_LIST));
使用案例
下面就来介绍一下 如何通过 RecyclerView 轻松实现一个普通列表:
MainActivity.java:
public class MainActivity extends AppCompatActivity {
private RecyclerView mRecyclerView;
private MyAdapter mMyAdapter;
private LinearLayoutManager mLayoutManager;
private List<String> list;
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initData();
mRecyclerView = findViewById(R.id.recycler_view);
mMyAdapter = new MyAdapter(list);
mLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(mLayoutManager);
mRecyclerView.setAdapter(mMyAdapter);
}
private void initData() {
list = new ArrayList<>();
for (int i = 0; i <= 20; i++) {
list.add("Item " + i);
}
}
}
MyAdapter.java:
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
//数据源
private List<String> mList;
public MyAdapter(List<String> list) {
mList = list;
}
//返回item个数
@Override
public int getItemCount() {
return mList.size() ;
}
//创建ViewHolder
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new NormalHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item, parent, false));
}
//填充视图
@Override
public void onBindViewHolder(@NonNull final MyAdapter.ViewHolder holder, final int position) {
holder.mView.setText(mList.get(position));
}
public class ViewHolder extends RecyclerView.ViewHolder {
public TextView mView;
public ViewHolder(View itemView) {
super(itemView);
mView = itemView.findViewById(R.id.text_view);
}
}
}
布局(显示方式)
listView默认是垂直布局,而recyclerView则更加灵活,运行我们自己设置它的布局。
可通过LayoutManager(LinearLayoutManager,GridLayoutManager,StaggeredGridLayoutManager )设置线性布局、网格布局、瀑布流布局;
监听事件
RecycylerView 并没有处理点击事件的监听器,所以如果要监听 RecycylerView 的点击事件,我们需要自己写监听器。
下面就简单介绍几种实现方法。
推荐使用方法一和方法三
- 方法一:利用View.onClickListener 和 onLongClickListener
- 方法二:利用RecyclerView.OnItemTouchListener
- 方法三:利用GestureDetector(手势检测类)对方法二优化
利用View.onClickListener 和 onLongClickListener
- 在adapter中新建两个内部接口:
public interface OnItemClickListener {
void onItemClick(View view, int position);
}
public interface OnItemLongClickListener {
void onItemLongClick(View view, int position);
}
- 新建两个私有变量用于保存用户设置的监听器,并公开一个设置监听器的方法:
private OnItemClickListener mOnItemClickListener;
private OnItemLongClickListener mOnItemLongClickListener;
public void setOnItemClickListener(OnItemClickListener mOnItemClickListener) {
this.mOnItemClickListener = mOnItemClickListener;
}
public void setOnItemLongClickListener(OnItemLongClickListener mOnItemLongClickListener) {
this.mOnItemLongClickListener = mOnItemLongClickListener;
}
- 在onBindViewHolder方法内,实现回调:
@Override
public void onBindViewHolder(final MyViewHolder holder, int position) {
holder.tvTest.setText(stringList.get(position));
// ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
// lp.height = (int) (100 + Math.random() * 300);
// holder.itemView.setLayoutParams(lp);
//判断是否设置了监听器
if(mOnItemClickListener != null){
//为ItemView设置监听器
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int position = holder.getLayoutPosition(); // 1
mOnItemClickListener.onItemClick(holder.itemView,position); // 2
}
});
}
if(mOnItemLongClickListener != null){
holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
int position = holder.getLayoutPosition();
mOnItemLongClickListener.onItemLongClick(holder.itemView,position);
//返回true 表示消耗了事件 事件不会继续传递
return true;
}
});
}
}
这里实际上用到了子 Item View 的 onClickListener 和onLongClickListener这两个监听器,如果当前子item view被点击了,会触发点击事件进行回调,然后在内部接口处获取当前点击位置的position值,接着在我们保存的用户设置的监听器处进行再次回调,而这一次的回调是我们自己手动添加的,需要实现上面所述的接口。
修改完 TestAdapter后,我们接着在 MainActivity 中设置监听器,采用匿名内部类的形式实现了 onItemClickListener 、 onItemLongClickListener 接口,这种写法与一般的设置监听器的流程相同:
TestAdapter mTestAdapter = new TestAdapter(getList());
mTestAdapter.setOnItemClickListener(new TestAdapter.OnItemClickListener() {
@Override
public void onItemClick(View view, int position) {
Toast.makeText(MainActivity.this, "click " + getList().get(position), Toast.LENGTH_SHORT).show();
}
});
mTestAdapter.setOnItemLongClickListener(new TestAdapter.OnItemLongClickListener() {
@Override
public void onItemLongClick(View view, int position) {
Toast.makeText(MainActivity.this,"long click "+getList().get(position),Toast.LENGTH_SHORT).show();
}
});
rvTest.setAdapter(mTestAdapter);
ViewPager
一个简单的页面切换组件。
使用案例:
编写三个页面进行切换。
- 首先创建3个xml布局文件
- 在activity的布局文件中声明ViewPager
- 创建Adapter继承PagerAdapter,重写方法:
- getCount():获取viewpager中有多少个view
- instantiateItem():
- 将给定位置的view添加到viewgroup中,创建并显示处理
- 返回一个代表新增页面的Object(key),通常都是直接返回view本身就可以了,当然你也可以自定义自己的key,但是key和每个view要一一对应的关系。
- isViewFromObject()
判断instantiateItem(Viewgroup,int)函数所返回的key与一个页面视图是否是代表的同一个视图(即他俩是否是对应的,对应的表示同一个view),通常我们直接写成return view==object - destroyItem()
移除一个给定位置的页面,适配器有责任从容器中删除这个视图,这是为了确保在finish update(viewgroup)返回时视图能够被移除。
adapter的代码:
package com.example.myviewpager;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.viewpager.widget.PagerAdapter;
import java.util.List;
public class MyAdapter extends PagerAdapter {
private List<View> listview;
public MyAdapter(List<View> listview) {
this.listview = listview;
}
@NonNull
@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
container.addView(listview.get(position),0);
return listview.get(position);
}
@Override
public int getCount() {
return listview.size();
}
@Override
public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
return view==object;
}
@Override
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
container.removeView(listview.get(position));
}
}
activity:
import android.view.LayoutInflater;
import android.view.View;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import androidx.viewpager.widget.ViewPager;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
LayoutInflater lf=getLayoutInflater().from(this);
View view1=lf.inflate(R.layout.layout1,null);
View view2=lf.inflate(R.layout.layout2,null);
View view3=lf.inflate(R.layout.layout3,null);
List<View> viewList=new ArrayList<>();
viewList.add(view1);
viewList.add(view2);
viewList.add(view3);
ViewPager viewPager=findViewById(R.id.vp);//获取viewpager
MyAdapter myAdapter=new MyAdapter(viewList);
viewPager.setAdapter(myAdapter);
}
}
动画
帧动画
Frame Animation
用多张图片来组成动画。一帧帧的播放图片,利用人眼视觉残留原理,给我们带来动画的感觉。它的原理的GIF图片、电影播放原理一样。
我们可以使用AnimationDrawable 来实现动画效果。
补间动画
Tween Animation
补间动画就是我们只需指定开始、结束的“关键帧“,而变化中的其他帧由系统来计算,不必自己一帧帧的去定义。
Android使用Animation代表抽象动画,包括四种子类:AlphaAnimation(透明度动画)、ScaleAnimation(缩放动画)、TranslateAnimation(位移动画)、RotateAnimation(旋转动画)
一般都会采用动画资源文件来定义动画,把界面与逻辑分离
定义好anim文件后,我们可以通过AnimationUtils工具类来加载它们,加载成功后返回一个Animation。然后就可以通过View的startAnimation(anim)开始执行动画了。
属性动画
直接更改我们对象的属性。在上面提到的Tween Animation中,只是更改View的绘画效果而View的真实属性是不改变的
常用 Animator 类,ValueAnimator 等
Animator可加载动画资源文件
ValueAnimator可使用内置估值器,添加监听AnimatorUpdateListener,在每次变化时修改view的属性
Activity
Activity 是一个应用组件,用户可与其提供的屏幕进行交互,以执行拨打电话、拍摄照片、发送电
子邮件或查看地图等操作。 每个 Activity 都会获得一个用于绘制其用户界面的窗口。窗口通常会
充满屏幕,但也可小于屏幕并浮动在其他窗口之上。
AndroidMainfest.xml
也可以简称为「manifest文件」。清单文件非常重要,它告诉系统我们的app有哪些activity,用到了什么权限等等信息。
如果要新建activity,需要在清单中注册。
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
从这个默认的清单文件中我们可以得知,activity是属于application的。application就是我们的应用。
application标签中也指定了各种元素,例如应用的图标,名字,主题等等。
MainActivity是应用启动的第一个activity。可以观察到它设置了action和category属性。
- android.intent.action.MAIN 决定应用程序最先启动的Activity。
- android.intent.category.LAUNCHER 表示可以在手机“桌面”上看到应用图标
生命周期
onCreate和onStart的区别
- onCreate 在系统首次创建 Activity 时触发。Activity会在创建后进入已创建状态。onCreate方法处应该是我们创建视图,准备数据的位置。
- 当 Activity 进入“已开始”状态时,系统会调用此回调。onStart() 调用使 Activity 对用户可见,因为应用会为 Activity 进入前台并支持交互做准备。
- onCreate方法在activity的生命周期中只会执行一次,而onStart方法在activity停止时,执行完onRestart后,会再次执行。
onPause和onStop的区别
- onPause() 执行非常简单,而且不一定要有足够的时间来执行保存操作。== 因此,您不应使用onPause() 来保存应用或用户数据、进行网络调用,或执行数据库事务。因为在该方法完成之前,此类工作可能无法完成。==
- 在 onStop() 方法中,应用应释放或调整应用对用户不可见时的无用资源。例如,应用可以暂停动画效果,或从细粒度位置更新切换到粗粒度位置更新。 使用 onStop() 而非 onPause() 可确保与界面相关的工作继续进行,即使用户在多窗口模式下查看您的 Activity 也能如此。 在 onStop() 关闭CPU 相对密集的操作。
生命周期的变化
- 启动后退出:
onCreate
onStart
onResume
onWindowFocusChanged: hasFocus: true
onWindowFocusChanged: hasFocus: false
onPause
onStop
onDestroy
2.启动后按home键
Act1: onCreate
Act1: onStart
Act1: onResume
Act1: onWindowFocusChanged: hasFocus: true
// 按home键
Act1: onWindowFocusChanged: hasFocus: false
Act1: onPause
Act1: onStop
// 再回来
Act1: onRestart
Act1: onStart
Act1: onResume
Act1: onWindowFocusChanged: hasFocus: true
// 按返回键退出act
Act1: onWindowFocusChanged: hasFocus: false
Act1: onPause
Act1: onStop
Act1: onDestroy
- 旋转手机(横竖屏切换时的生命周期变化)
[Life]: onCreate
[Life]: onStart
[Life]: onResume
[Life]: onWindowFocusChanged: hasFocus: true
// 横屏
[Life]: onPause
[Life]: onStop
[Life]: onDestroy
[Life]: onCreate
[Life]: onStart
[Life]: onResume
[Life]: onWindowFocusChanged: hasFocus: true
// 竖屏
[Life]: onPause
[Life]: onStop
[Life]: onDestroy
[Life]: onCreate
[Life]: onStart
[Life]: onResume
[Life]: onWindowFocusChanged: hasFocus: true
// 返回
[Life]: onWindowFocusChanged: hasFocus: false
[Life]: onPause
[Life]: onStop
[Life]: onDestroy - 两个activity切换
Act1: onCreate
Act1: onStart
Act1: onResume
Act1: onWindowFocusChanged: hasFocus: true
Act1: onPause // 切换到Act2
Act1: onWindowFocusChanged: hasFocus: false
Act2: onCreate
Act2: onStart
Act2: onResume
Act2: onWindowFocusChanged: hasFocus: true
Act1: onStop
Act2: onWindowFocusChanged: hasFocus: false // 再切换回Act1
Act2: onPause
Act1: onRestart
Act1: onStart
Act1: onResume
Act1: onWindowFocusChanged: hasFocus: true
Act2: onStop
Act2: onDestroy
Act1: onWindowFocusChanged: hasFocus: false
Act1: onPause
Act1: onStop
Act1: onDestroy
- 弹出一个AlertDialog
会调用onWindowFocusChanged
onWindowFocusChanged: hasFocus: false
onWindowFocusChanged: hasFocus: true
Activity的启动
Intent
Intent,直译为“意图”。我们把信息包裹在intent对象中,然后执行。
这里用到一个很常见的方法startActivity (Intent intent) 。 startActivity 属于Context类,Activity是Context的子类。
从LoginActivity 跳转到 WaitActivity:
Intent intent = new Intent(LoginActivity.this, WaitActivity.class);
startActivity(intent);
在跳转去下一个页面时,我们可能会想携带一些信息到下一个界面去。例如携带一些文本,数字等等,或者是一个对象。 这些信息我们可以交给Intent,传递到下一个activity去。下一个activity中拿到我们传入的Intent。
Intent intent = new Intent(getApplicationContext(), SendParamsDemo.class);
intent.putExtra(SendParamsDemo.K_INT, 100);
intent.putExtra(SendParamsDemo.K_BOOL, true);
intent.putExtra(SendParamsDemo.K_STR, "Input string");
startActivity(intent);
在另外一个activity中直接通过get_获取相应的参数。
int i = intent.getIntExtra(K_INT, -1);
boolean b = intent.getBooleanExtra(K_BOOL, false);
String str = intent.getStringExtra(K_STR);
Bundle
实际上在安卓开发我们使用Bundle用于传递数据;它保存的数据,是以key-value(键值对)的形式存在的。
经常使用Bundle在Activity之间传递数据,传递的数据可以是boolean、byte、int、long、float、double、string等基本类型或它们对应的数组,也可以是对象或对象数组。当Bundle传递的是对象或对象数组时,必须实现Serializable 或Parcelable接口。
intent其实是调用了bundle相应的put函数,也就是说,intent内部还是用bundle来实现数据传递的,只是封装了一层而已。
Activity携带参数返回
在一个主界面(主Activity)通过意图跳转至多个不同子Activity上去,当子模块的代码执行完毕后再次返回主页面,将子Activity中得到的数据显示在主界面/完成的数据交给主Activity处理。这种带数据的意图跳转需要使用下边三个方法:
- startActivityForResult(Intent intent, int requestCode);
- setResult(int resultCode, Intent data)
- onActivityResult(int requestCode, int resultCode, Intent data)
下边的例子中有2个activity作为示范:ForResultFirstAct 和ForResultSecondAct 。
步骤:
- 主activity:调用
startActivityForResult(Intent intent, int requestCode);
第一个参数:一个Intent对象,用于携带将跳转至下一个界面中使用的数据,使用putExtra(A,B)方法,此处存储的数据类型特别多,基本类型全部支持。
第二个参数:如果> = 0,当Activity结束时requestCode将归还在onActivityResult()中。以便确定返回的数据是从哪个Activity中返回,用来标识目标activity。
private static final int REQ_CODE = 10;
startActivityForResult(new Intent(getApplicationContext(),
ForResultSecondAct.class), REQ_CODE);
- ForResultSecondAct 是第二个activity。它可以设置返回时携带的数据,然后调用
setResult(RESULT_OK, resultIntent);
Intent resultIntent = new Intent();
resultIntent.putExtra(K_TITLE, mTitleEt.getText().toString());
resultIntent.putExtra(K_SUB_TITLE, mSubTitleEt.getText().toString());
setResult(RESULT_OK, resultIntent);
finish();
其中RESULT_OK 是Activity类的静态常量。可用于代表操作的结果,除此外还有其他状态:
/** Standard activity result: operation canceled. */
public static final int RESULT_CANCELED = 0;
/** Standard activity result: operation succeeded. */
public static final int RESULT_OK = -1;
/** Start of user-defined activity results. */
public static final int RESULT_FIRST_USER = 1;
- 在主activity中重写
onActivityResult(int requestCode, int resultCode, Intent data)
,验证requestCode、resultCode后,获取到返回的数据:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case REQ_CODE:
if (resultCode == RESULT_OK) {
if (data != null) {
mTitleTv.setText(data.getStringExtra(ForResultSecondAct.K_TITLE));
mSubTitleTv.setText(data.getStringExtra(ForResultSecondAct.K_SUB_TITLE));
}
} else {
Toast.makeText(getApplicationContext(), "未保存修改",
Toast.LENGTH_SHORT).show();
}
break;
}
}
Activity启动模式
任务(task),返回栈(back stack)
任务
是指在执行特定作业时与用户交互的一系列 Activity
。 这些 Activity 按照各自的打开顺序排列在堆栈(即返回栈
)中。
Activity的四种启动模式
standard(默认模式)
默认模式。如果不指定启动模式,则会使用这个模式。 系统在启动 Activity 的任务中创建 Activity 的新实例并向其传送 Intent。Activity 可以多次实例化,而每个实例均可属于不同的任务,并且一个任务可以拥有多个实例。
singleTop
可以有多个实例,但是不允许多个相同Activity叠加。即,如果Activity在栈顶的时候,启动相同的Activity,不会创建新的实例,而会调用其onNewIntent方法
目标作用是在栈顶添加activity实例或走栈顶activity的onNewIntent() 方法。若目标activity已在栈顶,则不会新建实例。
如果当前任务的顶部已存在 Activity 的一个实例,则系统会通过调用该实例的 onNewIntent() 方法向其传送 Intent,而不是创建 Activity 的新实例。Activity 可以多次实例化,而每个实例均可属于不同的任务,并且一个任务可以拥有多个实例(但前提是位于返回栈顶部的 Activity 并不是 Activity 的现有实例)。
例如,假设任务的返回栈包含根 Activity A 以及 Activity B、C 和位于顶部的 D(堆栈是 A-B-C-D;D 位于顶部)。收到针对 D 类 Activity 的 Intent。如果 D 具有默认的 “standard” 启动模式,则会启动该类的新实例,且堆栈会变成 A-B-C-D-D。但是,如果 D 的启动模式是 “singleTop”,则 D 的现有实例会通过 onNewIntent() 接收 Intent,因为它位于堆栈的顶部;而堆栈仍为 A-B-C-D。但是,如果收到针对 B 类 Activity 的 Intent,则会向堆栈添加 B 的新实例,即便其启动模式为 “singleTop” 也是如此。
singleTask
只有一个实例。在同一个应用程序中启动他的时候,若Activity不存在,则会在当前task创建一个新的实例,若存在,则会把task中在其之上的其它Activity destory掉并调用它的onNewIntent方法。
- 若目标activity已在栈中,则会销毁在目标activity之上的其他实例,此时目标activity来到栈顶。
- 若目标activity是 “MAIN” activity,能被Launcher启动。那么按home键将App退到后台,在桌面上点击App图标。目标activity之上的页面都会被销毁掉,并调用目标activity的onNewIntent()方法。
- 系统创建新任务并实例化位于新任务底部的 Activity。但是,如果该 Activity 的一个实例已存在于一个单独的任务中,则系统会通过调用现有实例的 onNewIntent() 方法向其传送 Intent,而不是创建新实例。一次只能存在 Activity 的一个实例。
注:尽管 Activity 在新任务中启动,但是用户按“返回”按钮仍会返回到前一个 Activity。
singleInstance
只有一个实例,并且这个实例独立运行在一个task中,这个task只有这个实例,不允许有别的Activity存在。
与 “singleTask” 相同,只是系统不会将任何其他 Activity 启动到包含实例的任务中。该 Activity 始终是其任务唯一仅有的成员;由此 Activity 启动的任何 Activity 均在单独的任务中打开。
例如,A,B,C 3个Activity,只有B是以singleInstance模式启动,其他是默认模式。 页面启动顺序是 A -> B -> C,B会自己在一个task中;栈情况如下(方括号表示在前台):
stack | | => | | | | => | | | |
| | | | | | | C | | |
| A | | A | | B | | A | | B |
task id: [1082] 1082 [1083] [1082] 1083
此时屏幕上显示是C的界面,按返回键,C被销毁,显示的是A的界面;再返回,A被销毁,原A和C所在的task结束。此时显示B的界面。
如果在只剩下B的时候,去启动C;由于B是singleInstance模式,B所在的栈只能有一个activity,则会新建一个task来存放C
stack | | => | | | |
| | | | | |
| B | | B | | C |
task id: [1083] 1083 [1084]
这4种启动模式各有特点。在开发中我们根据实际情况选择不同的启动模式。 例如
“根页面”可以考虑用singleTask的模式。
Activity中获取View的宽高
有些时候我们需要获取到View的宽高信息。
在onCreate和onResume中尝试view.getWidth()或是view.getHeiht()时,我们会发现获取到的是0。
Activity视图在创建完成后,各个子view并不一定被加载完成。 获取宽高正确的方法有哪些呢?
- 方法1 - 在Activity的onWindowFocusChanged获取宽高
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
// 在这里我们可以获取到View的真实宽高
Log.d(TAG, "onWindowFocusChanged: mBtn1.getWidth == " + mBtn1.getWidth());
}
- 方法2-使用ViewTreeObserver的OnGlobalLayoutListener回调
获取View的ViewTreeObserver,添加回调
ViewTreeObserver vto = mBtn1.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
int height = mBtn1.getHeight();
int width = mBtn1.getWidth();
Log.d(TAG, "onGlobalLayout: mBtn1 " + width + ", " + height);
mBtn1.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
});
- 方法3-使用View.post(Runnable action)方法
例如我们在onCreate中post一个Runnable
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBtn1 = findViewById(R.id.btn1);
Log.d(TAG, "mBtn1 post runnable");
mBtn1.post(new Runnable() {
@Override
public void run() {
Log.d(TAG, "mBtn1: " + mBtn1.getWidth() + ", " + mBtn1.getHeight());
}
});
}
可以获取到view的宽高。从log的时间上可以看出,在view加载完毕后,执行的Runnable。
06-19 11:54:17.865 28009-28009/com.rustfisher.basic4 D/rustApp: mBtn1 post runnable
06-19 11:54:17.867 28009-28009/com.rustfisher.basic4 D/rustApp: [act2] onResume
06-19 11:54:17.899 28009-28009/com.rustfisher.basic4 D/rustApp: mBtn1: 355, 144
应用:动态调整ImageView的宽高
获取到view的宽高后,我们可以动态地调整ImageView的高度。 假设图片宽高为704 * 440。xml中设置scaleType为fitXY。已知ImageView的宽度是固定的,我们可以调整高度。
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scaleType="fitXY"/>
根据图片真实大小来重设ImageView的高度。
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
resetIntroIvParams();
}
private void resetIntroIvParams() {
int height = mIntroIv.getHeight(); // 704 * 440
int wid = mIntroIv.getWidth();
if (height > 0 && wid > 0) {
ViewGroup.LayoutParams layoutParams = mIntroIv.getLayoutParams();
layoutParams.height = (int) (wid * 440.0 / 704.0);
mIntroIv.setLayoutParams(layoutParams);
}
}
Fragment
Fragment,直译为“碎片”,“片段”。 Fragment 表示 FragmentActivity 中的行为或界面的一部分。您可以在一个 Activity 中组合多个片段,从而构建多窗格界面,并在多个 Activity 中重复使用某个片段。
注意:
- Fragment必须始终托管在 Activity 中,其生命周期直接受宿主 Activity 生命周期的影响。例如,当 Activity 暂停时,Activity 的所有片段也会暂停;当 Activity 被销毁时,所有Fragment也会被销毁。不过,当 Activity 正在运行(处于已恢复生命周期状态)时,您可以独立操纵每个Fragment,如添加或移除Fragment。当执行此类Fragment事务时,您也可将其添加到由 Activity 管理的返回栈 — Activity 中的每个返回栈条目都是一条已发生片段事务的记录。借助返回栈,用户可以通过按返回按钮撤消片段事务(后退)。
- 当您将Fragment作为 Activity 布局的一部分添加时,其位于 Activity 视图层次结构的某个 ViewGroup 中,并且Fragment会定义其自己的视图布局。您可以通过在 Activity 的布局文件中声明Fragment,将其作为 元素插入您的 Activity 布局,或者通过将其添加到某个现有的 ViewGroup,利用应用代码将其插入布局。
Fragment的优点
- Fragment加载灵活,替换方便。定制你的UI,在不同尺寸的屏幕上创建合适的UI,提高用户体验。
- 可复用,页面布局可以使用多个Fragment,不同的控件和内容可以分布在不同的Fragment上。
- 使用Fragment,可以少用一些Activity。一个Activity可以管辖多个Fragment。
Fragmet的生命周期
Fragment 类的代码与 Activity 非常相似。它包含与 Activity 类似的回调方法,如 onCreate()、onStart()、onPause() 和 onStop()。实际上,如果您要将现有 Android 应用转换为使用片段,可能只需将代码从 Activity 的回调方法移入片段相应的回调方法中。
生命周期变化
- Fragmet被创建时:
onAttach()
onCreate()
onCreateView()
onActivityCreated()
- Fragment对用户可见时:
onStart()
onResume()
- Fragmet进入“后台模式”时:
onPause()
onStop()
- Fragmet被销毁或者持有它的activity被销毁时:
onPause()
onStop()
onDestroyView()
onDestroy()
onDetach()
Fragment与Activity不同的生命周期
Fragment的大部分状态都和Activity很相似,但fragment有一些新的状态。
Fragment不同于Activity的生命周期:
- onAttached() —— 当fragment被加入到activity时调用(在这个方法中可以获得所在的activity)。
- onCreateView() —— 当activity要得到fragment的layout时,调用此方法,fragment在其中创建自己的layout(界面)。
- onActivityCreated() —— 当activity的onCreated()方法返回后调用此方法
- onDestroyView() —— 当fragment中的视图被移除的时候,调用这个方法。
- onDetach() —— 当fragment和activity分离的时候,调用这个方法。
一旦activity进入resumed状态(也就是running状态),你就可以自由地添加和删除fragment了。因此,只有当activity在resumed状态时,fragment的生命周期才能独立的运转,其它时候是依赖于activity的生命周期变化的。
对于 Activity 生命周期与片段生命周期而言,二者最显著的差异是在其各自返回栈中的存储方式。
- 默认情况下,Activity 停止时会被放入由系统管理的 Activity 返回栈中
- 而Fragment需要我们在移除Fragment的事务执行期间通过调用
addToBackStack() 显式请求保存实例时,系统才会将片段放入由宿主 Activity 管理的返回栈。
加载和使用Fragmet
向Activity中添加Fragment
静态加载
在 Activity 的布局文件内声明片段。 在本例中,您可以将片段当作视图来为其指定布局属性。例如,以下是拥有两个片段的 Activity 的布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment android:name="com.example.news.ArticleListFragment"
android:id="@+id/list"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="match_parent" />
<fragment android:name="com.example.news.ArticleReaderFragment"
android:id="@+id/viewer"
android:layout_weight="2"
android:layout_width="0dp"
android:layout_height="match_parent" />
</LinearLayout>
<fragment>
中的android:name
属性指定要在布局中进行实例化的 Fragment 类。
创建此 Activity 布局时,系统会将布局中指定的每个片段实例化,并为每个片段调用 onCreateView()
方法,以检索每个片段的布局。系统会直接插入片段返回的 View,从而代替 <fragment>
元素。
注意:每个片段都需要唯一标识符,重启 Activity 时,系统可使用该标识符来恢复片段(您也可以使用该标识符来捕获片段,从而执行某些事务,如将其移除)。
不给fragment指定id会报错!!!
动态加载
通过编程方式将片段添加到某个现有 ViewGroup。 在 Activity 运行期间,您可以随时将片段添加到 Activity 布局中。您只需指定要将片段放入哪个 ViewGroup。
步骤:
-
①准备好Fragment xml布局文件
-
②新建一个类,继承自Fragment;在这个类中找到Fragment布局文件
-
③在Activity中使用FragmentManager来操作Fragment
-
④别忘了commit
准备fragment.xml
新建一个类FirstFragment.java,继承自Fragment。复写onCreateView方法。在onCreateView方法中,可以操作Fragment上的控件。
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_first, container,false);
// fragment_first是自定义好的布局
// 如果此Fragment上放了控件,比如Button,Edittext等。可以在这里定义动作
btn_fragment1_send = (Button) rootView.findViewById(R.id.btn_fragment1_1);
//...
return rootView;
}
准备一个位置给Fragment,比如在activity_main.xml中用Framelayout来占位。
在MainActivity.java里,先获得FragmentManager,得到FragmentTransaction。Fragment的添加删除等操作由FragmentTransaction来完成。
f1 = new FirstFragment(); // 获取实例
f2 = new SecondFragment(); //
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
fragmentTransaction.add(R.id.layout_container1,f1); // 添加
fragmentTransaction.replace(R.id.layout_container1,f1); // 替换
// 或者也可以写成
fragmentTransaction.replace(R.id.layout_container1,new FirstFragment());
// fragmentTransaction.addToBackStack(null); //添加到返回栈,这样按返回键的时候能返回已添加的fragment
fragmentTransaction.commit(); //别忘了commit
// 移除操作 getFragmentManager().beginTransaction().remove(f1).commit();
管理Fragmet
如要管理 Activity 中的片段,您需使用 FragmentManager。如要获取它,请从您的 Activity 调用 getSupportFragmentManager()。
可使用 FragmentManager 执行的操作包括:
- 通过 findFragmentById()(针对在 Activity 布局中提供界面的片段)或 findFragmentByTag()(针对提供或不提供界面的片段)获取 Activity 中存在的片段。
- 通过 popBackStack()(模拟用户发出的返回命令)使片段从返回栈中弹出。
- 通过 addOnBackStackChangedListener() 注册侦听返回栈变化的侦听器。
- 打开一个 FragmentTransaction,通过它来执行某些事务,如添加和移除片段。
如要在您的 Activity 中执行片段事务(如添加、移除或替换片段),则必须使用 FragmentTransaction 中的 API。如下所示,您可以从 FragmentActivity 获取一个 FragmentTransaction 实例:
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
然后,您可以使用 add() 方法添加一个片段,指定要添加的片段以及将其插入哪个视图。例如:
ExampleFragment fragment = new ExampleFragment();
fragmentTransaction.add(R.id.fragment_container, fragment);
fragmentTransaction.commit();
传递到 add()
的第一个参数是 ViewGroup,即应放置片段的位置,由资源 ID 指定
,第二个参数是要添加的片段。 一旦您通过 FragmentTransaction 做出了更改,就必须调用 commit()
以使更改生效。
Fragment间通信
在Fragment的java文件中,可以使用getActivity()来获得调用它的activity,然后再找到另一个Fragment,进行通信。
getActivity().getFragmentManager().findFragmentById(R.id.fragment_list);
但这样做耦合度太高,不方便后续的修改操作。
Fragment与其附着的Activity之间的通信,都应该由Activity来完成;不能是多个Fragment之间直接通信。
Fragment与其附着的Activity之间通信方式
1.在发起事件的Fragment中定义一个接口,接口中声明你的方法。
2.在onAttach方法中要求Activity实现该接口。
3.在Activity中实现该方法。
例如一个activity中布置了2个Fragment,它们之间的通信要依靠activity来完成
代码:ListStoreActivity.java NewItemFragment.java ListStoreFragment.java
布局文件为:liststore.xml new_item_fragment.xml
ListStoreFragment.java 使用前面定义的界面
public class ListStoreFragment extends ListFragment{
/// 继承自ListFragment,已经封装好了listview
/// 不需要自己写ListView了
}
NewItemFragment.java
/**
* 声明一个接口,定义向activity传递的方法
* 绑定的activity必须实现这个方法
*/
public interface OnNewItemAddedListener {
public void newItemAdded(String content);
}
private OnNewItemAddedListener onNewItemAddedListener;
private Button btnAddItem;
/*复写onAttach方法*/
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
onNewItemAddedListener = (OnNewItemAddedListener) activity;
} catch (ClassCastException e){
throw new ClassCastException(activity.toString() + "must implement OnNewItemAddedListener");
}
}
ListStoreActivity.java 加载主视图liststore.xml;
两个Fragment通过ListStoreActivity来通信
在onCreate方法中获取ListStoreFragment的实例;并且复写newItemAdded方法,在里面加上业务逻辑
public class ListStoreActivity extends Activity implements OnNewItemAddedListener{
private ArrayList<String> data;
private ArrayAdapter<String> adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.liststore);
data = new ArrayList<String>();
// 把data装入adapter中
adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, data);
// ListFragment并不需要再定义一个listview
ListStoreFragment listStoreFragment = (ListStoreFragment) getFragmentManager().findFragmentById(R.id.fragment_listview);
listStoreFragment.setListAdapter(adapter);
}
@Override
public void newItemAdded(String content) {
// 复写接口中的方法,业务代码在这里实现
if(!content.equals("")) {
data.add(content);
adapter.notifyDataSetChanged();
}
}
}
Fragment跟Activity的其他通信方式:
- 通过构造器传递信息
在Activity中构造Fragment的时候,可以将需要传递的数据封装在成一个Bundle对象,然后通过setArguments()方法传递参数,例子如下
Activity中设置Bundle:
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
Fragment fragTop = new FrameTop();
fragmentTransaction.replace(R.id.frame1,fragTop);
Bundle bundle = new Bundle();
fragTop.setArguments(bundle);
bundle.putString("name","fragTop");
fragmentTransaction.commit();
Fragment中获取数据:
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
Bundle arguments = getArguments();
String name = arguments.getString("name");
}
-
通过广播
-
通过EventBus
通过EventBus不仅可以实现Activity与Fragment, 还可以实现Fragment与Fragment之间通信,只要是注册了EventBus的并且可见地方都可以收到它发出的消息。
EventBus是一个开源库,它使用的是发布/订阅模式来实现组件之间的通信,相对于广播机制,handler机制等,其所需要的代码更少,耦合度更低,下面是一张官方的图说明其工作方式。
- 通过Activity和Fragment共用ViewModel
DialogFragment
弹窗,是常见的一种提示方式。市面上有多种多样、五彩缤纷的弹窗。
构建步骤:
- 准备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"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="12dp">
<TextView
android:id="@+id/title_tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:textColor="#111111"
android:textSize="16sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/content_tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:gravity="center"
android:textColor="#111111"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/title_tv" />
</androidx.constraintlayout.widget.ConstraintLayout>
- 新建弹窗类,继承自DialogFragmet
新建一个SimpleDialog类继承DialogFragment。
- 在onCreate方法中接收传入的数据。传递数据使用了Bundle。
- 在onCreateView方法中,使用上文建立的layout。
- 在onViewCreated方法中进行ui操作。
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
public class SimpleDialog extends DialogFragment {
public static final String K_TITLE = "k_title"; // 传输数据时用到的key
public static final String K_CONTENT = "k_content";
private String title;
private String content;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle in = getArguments();
if (in != null) {
title = in.getString(K_TITLE);
content = in.getString(K_CONTENT);
}
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.dialog_simple, container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
TextView titleTv = view.findViewById(R.id.title_tv);
TextView contentTv = view.findViewById(R.id.content_tv);
titleTv.setText(title);
contentTv.setText(content);
}
}
把这个窗口弹出来。我们使用DialogFragment.show(@NonNull FragmentManager manager, @Nullable String tag)方法。
private void popSimpleDialog1(String title, String content) {
SimpleDialog dialog = new SimpleDialog();
Bundle bundle = new Bundle();
bundle.putString(SimpleDialog.K_TITLE, title);
bundle.putString(SimpleDialog.K_CONTENT, content);
dialog.setArguments(bundle);
dialog.show(getSupportFragmentManager(), "one-tag");
}
// 调用
popSimpleDialog1("欢迎访问", "欢迎访问https://an.rustfisher.com\n入门的好选择~");