碎片和活动之间的通信
Activity向Fragment
通过方法传递
构造方法
将碎片动态地加载到活动当中,先得到一个碎片,再将其放到活动当中。就想到碎片的替代方法,将我们所要传输的数据直接放到新创建的碎片里面,替换到原来的碎片。
- 首先使其自动创建一个Fragment,修改对应的布局文件,加上id,并设置一个文本框,用来显示所接收到的数据:
<FrameLayout 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=".datapass1.fragment.DataPassFragment">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textSize="25sp"
android:gravity="center"
android:id="@+id/tv_content"
android:text="@string/hello_blank_fragment" />
</FrameLayout>
- 我们要将所传输的数据显示在文本框,而在Fragment类中只有一个无参构造,因此在其中加上一个有参构造,当我们创建碎片的时候,就直接把数据传进去了。加入有参构造,注意,此时碎片收到了数据,但是文本框并没有修改,修改所对应的
onViewCreated()
碎片与视图建立联系的时候调用的方法,先得到视图里的TextView的id,将文本内容设置为活动所传输的数据,代码如下:
public class DataPassFragment extends Fragment {
private static final String ARG_PARAM1 = "param1";
private static final String ARG_PARAM2 = "param2";
private String mParam1;
private String mParam2;
//新建要得到才对其中的内容进行修改
private TextView mTextView;
//加入的构造方法
public DataPassFragment(String data) {
mParam1 = data;
}
public DataPassFragment() {
;
}
public static DataPassFragment newInstance(String param1, String param2) {
DataPassFragment fragment = new DataPassFragment();
Bundle args = new Bundle();
args.putString(ARG_PARAM1, param1);
args.putString(ARG_PARAM2, param2);
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mParam1 = getArguments().getString(ARG_PARAM1);
mParam2 = getArguments().getString(ARG_PARAM2);
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_data_pass, container, false);
}
//将传输的数据放到文本框当中
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mTextView = view.findViewById(R.id.tv_content);
if (!TextUtils.isEmpty(mParam1)) { //判断字符串不是空
mTextView.setText(mParam1);
}
}
}
注意:我们需要在设置文字之前判断字符串是不是空的,否则无论如何都会执行修改文本的操作,当我们无参构造的时候,就会出现空白的情况。碎片就修改完毕了,就需要对主活动的按钮注册点击事件了,触发数据的传输。
- 创建一个活动来存放你所要放的碎片以及点击按钮,并为点击按钮注册点击事件
<LinearLayout 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/main"
android:layout_width="match_parent"
android:orientation="vertical"
android:layout_height="match_parent"
tools:context=".datapass1.DataPassActivity">
<androidx.fragment.app.FragmentContainerView
android:layout_width="match_parent"
android:layout_height="300dp"
android:id="@+id/fcv"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:id="@+id/construct"
android:text="通过构造方法传递数据"/>
</LinearLayout>
public class DataPassActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_data_pass);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
//添加Fragment到布局当中
getSupportFragmentManager().beginTransaction()
.replace(R.id.fcv, DataPassFragment.class, null).commit();
Button buttonconstruct = (Button) findViewById(R.id.construct);
buttonconstruct.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
passDataByConstruct(DataPassActivity.this.getCurrentFocus());
}
});
}
//通过构造方法传递数据
public void passDataByConstruct (View view) {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
DataPassFragment dataPassFragment = new DataPassFragment("这是构造方法传递的数据");
fragmentTransaction.replace(R.id.fcv, dataPassFragment).commit();
}
}
此时页面为一开始自动创建的碎片:
此时运行程序,按下按钮,文本内容转换成了我们所传入的数据,对碎片进行了替换。
运行程序:
public方法
碎片放在活动的布局当中,我们就可以根据碎片容器的ID得到此时的碎片,从而调用里面的方法对碎片的内容进行修改。
- 先通过查找方法找到此时的碎片,再通过调用碎片里的为字符串进行修改的方法,对其进行修改,先在主活动中注册点击事件:
Button buttoncommon = (Button) findViewById(R.id.common);
buttoncommon.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) { passDataByCommon(DataPassActivity.this.getCurrentFocus());
}
});
public void passDataByCommon (View view) {
FragmentManager fragmentManager = getSupportFragmentManager();
Fragment fragment = fragmentManager.findFragmentById(R.id.fcv);
if (fragment != null) {
((DataPassFragment)fragment).setParam1("这是普通public传递的数据");
}
}
- 在碎片类里加入方法
setParam1()
,用来将所收到的数据展示在文本框当中,此时需要将碎片进行强转,对应的碎片里的代码进行修改,加入以下内容:
public void setParam1 (String s) {
this.mParam1 = s;
if (!TextUtils.isEmpty(mParam1)) { //判断字符串不是空
mTextView.setText(mParam1);
}
}
之后运行程序,得到的结果为:
通过Argument
这是Android本身为我们提供的向Fragment传递数据的方式,可用来一次性传递复杂、大量数据。就是将你所要传入的数据进行打包
- 同样的在主活动中为按钮注册点击事件
public void passDataByargument1 (View view) {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
DataPassFragment dataPassFragment = new DataPassFragment("这是argument传递的数据");
Bundle bundle = new Bundle();
bundle.putString("data", "这是argument传递的数据");
bundle.putInt("int_data", 100);
dataPassFragment.setArguments(bundle);
fragmentTransaction.replace(R.id.fcv, dataPassFragment).commit();
}
- 我们使用bundle对所传入的数据进行打包,以键值对的形式,使用
setArguments()
将数据发送过去,此时在碎片类里面本身就有对应的getArguments()
方法,我们根据这个与键值对对其进行数据的接收,此时需要修改对应的代码:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mParam1 = getArguments().getString(ARG_PARAM1);
mParam2 = getArguments().getString(ARG_PARAM2);
mData = getArguments().getString("data");
mintData = getArguments().getInt("int_data");
}
}
- 得到了所要传输的数据,数据是在第一次被创建的时候得到,之后会经历
onViewCreated()
碎片与视图建立联系的时候调用,因此要将代码放在onViewCreated()
里面:
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mTextView = view.findViewById(R.id.tv_content);
if (!TextUtils.isEmpty(mData)) {
mTextView.setText(mData + mintData);
}
}
按下通过argument传递数据的按钮,此时就接收到数据了:
通过接口
接口传递是一种基于编程语言自身性质的数据通信方式。当数据由A传到B的时候,A就为被观察者,B为观察者,此时我们想有一个角色可以将A所发生的变化告诉B,这个角色就被接口所承担。一般情况下,谁被观察就写在谁的内部。
- 同样地我们在主活动的界面对按钮进行点击事件的注册
public void passDataByInterface (View view) {
mDataChangeListener.onDataChange("这是通过接口传递的数据");
}
private onDataChangeListener mDataChangeListener;
public void setmDataChangeListener(onDataChangeListener DataChangeListener) {
mDataChangeListener = DataChangeListener;
}
//这个就是数据传输的接口
public interface onDataChangeListener {
void onDataChange(String data);
}
定义接口(Interface): onDataChangeListener
是一个接口,它定义了一个方法 onDataChange(String data)
。
接口成员变量: mDataChangeListener
是一个接口类型的成员变量,用于存储实现了 onDataChangeListener
接口的对象的引用。
设置接口的引用: setmDataChangeListener
是一个公共方法,它接受一个实现了 onDataChangeListener
接口的对象作为参数,并将这个对象赋值给 mDataChangeListener
变量。
调用接口方法: passDataByInterface
方法通过调用 mDataChangeListener
的 onDataChange
方法来传递数据。
- 碎片类的代码修改:
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mTextView = view.findViewById(R.id.tv_content);
((DataPassActivity)getActivity()).setmDataChangeListener(new DataPassActivity.onDataChangeListener() {
@Override
public void onDataChange(String data) {
if (!TextUtils.isEmpty(data)) {
mTextView.setText(data);
}
}
});
}
在碎片当中就有自己的方法得到所在的活动getActivity()
,我们要用活动里的其他方法,因此先对其进行强转,之后的操作与Button按钮注册点击事件一样,重写里面所对应的方法。
此时运行程序:
Fragment向Activity
通过getActivity
每个Fragment都可以通过getActivity()
方法获取承载它的活动对象,从而调用活动的方法向碎片传递数据。因此我们可以在碎片里面获取到活动,从而使用活动里的方法为活动传入数据,1. 在活动当中设置一个TextView,用来显示传入的数据:
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAllCaps="false"
android:textSize="20sp"
android:text="还没有传入数据!!!"
android:gravity="center"
android:id="@+id/tv_reveive"/>
主活动当中写为TextView设置文本内容的方法:
public void setReceive (String data) {
tvreceive.setText(data);
}
- 接下来就需要在碎片里面设置一个按钮用来触发发送数据,并获取活动使用活动的
setReceive()
方法对他的内容进行修改:
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:id="@+id/Bygetactivity"
android:text="通过getActivity传递数据"/>
buttongetactivity = view.findViewById(R.id.Bygetactivity);
buttongetactivity.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
((DataPassActivity) getActivity()).setReceive("这是Fragment向活动传入的数据");
}
});
重新运行程序,一开始的界面为:
从之前的布局可以知道,上面是一个碎片,给碎片加入了一个点击按钮用来触发通过getActivity()
向活动传入数据,下面即为活动的TextView控件,当我们点击按钮:
此时就将数据传输到了活动,并显示在TextView当中。
通过接口
上面提到了通过接口从Activity向Fragment传递数据,从Fragment向Activity传递数据也是类似的,Fragment成为了被观察者,我们需要在Fragment里面写接口。
- 我们先在Fragment里设置发送数据的按钮,的代码:
<Button
android:id="@+id/ByinterfaceActivity"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="50dp"
android:text="通过接口传递数据" />
Fragment的点击事件的注册:
//在onViewCreated()方法里面进行按钮的实现
buttonbyinterface = (Button) view.findViewById(R.id.ByinterfaceActivity);
buttonbyinterface.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (monFragmentDataChangeListener != null) {
monFragmentDataChangeListener.onFragmentDataChange("这是Fragment通过接口向activity传入的数据");
}
}
});
- 碎片A的接口定义:
private OnFragmentDataChangeListener monFragmentDataChangeListener;
public void setFragmentDataChangeListener (OnFragmentDataChangeListener monFragmentDataChangeListener) {
this.monFragmentDataChangeListener = monFragmentDataChangeListener;
}
public interface OnFragmentDataChangeListener {
void onFragmentDataChange (String data);
}
在承载碎片的活动对接口的实现:
Fragment fra = fragmentManager.findFragmentById(R.id.fcv);
if (fra != null) {
((DataPassFragment)fra).setFragmentDataChangeListener(new DataPassFragment.OnFragmentDataChangeListener() {
@Override
public void onFragmentDataChange(String data) {
tvreceive.setText(data);
}
);
}
我们运行程序发现按下按钮并没有显示出我们传递的数据,将提交事务改变为commitNow()
//添加Fragment到布局当中
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.fcv, DataPassFragment.class, null).commitNow();
此时运行程序,数据就传输并显示出来了:
commitNow()
与commit()
方法的区别:
- 异步与同步:
commit()
: 这个方法是异步的。当你调用commit()
时,它会将事务放入一个队列中,然后返回。这意味着事务的执行是延迟的,不会立即执行。这有助于提高应用的响应性,因为它允许其他UI操作在事务执行之前继续进行。commitNow()
: 这个方法是同步的。当你调用commitNow()
时,它会立即执行事务,而不是将其放入队列中。这意味着事务会立即完成,不会等待其他UI操作。- 执行时机:
commit()
: 由于是异步执行,事务会在稍后的时间点执行,通常是在下一个UI绘制循环中。这使得在事务提交后立即进行其他UI操作成为可能,而不会导致阻塞。commitNow()
: 事务会立即执行,因此在执行期间,UI会暂时冻结,直到事务完成。这可能会导致用户界面在事务执行期间变得无响应。- 适用场景:
commit()
: 通常用于需要提高应用响应性的场景,尤其是在事务执行期间需要进行其他UI操作的情况下。例如,在一个复杂的用户界面中,你可能需要同时进行多个UI操作,使用commit()
可以避免阻塞UI。commitNow()
: 通常用于需要立即执行事务的场景,尤其是在事务执行后需要立即进行某些操作的情况下。例如,如果你需要在事务执行后立即检查某些条件或执行某些操作,使用commitNow()
可以确保这些操作在事务完成后立即执行。
Fragment之间传递数据
此时我们是让两个碎片之间进行数据传递,新设立两个碎片,各自的碎片当中都设立一个TextView来展现所接收的数据,设置一个按钮用来发送数据,碎片A的布局文件为:
<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"
android:background="#f0a1a8"
tools:context=".datapass1.fragment.FragmentPassA">
<TextView
android:paddingTop="20dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="25sp"
android:text="此时还未进行数据传递"
android:id="@+id/tv_a_receive"
android:layout_gravity="center"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="通过活动传递数据给碎片B"
android:id="@+id/passDataByActivityA"/>
</LinearLayout>
我们将碎片A的背景设置为粉色,碎片B与碎片A的布局是相同的,为了区分将碎片B的背景颜色设置为蓝色,将两个碎片放在一个活动当中进行数据的传递,承载碎片的活动的布局文件与将碎片放进活动当中的代码为:
<LinearLayout 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/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".datapass1.fragmentPassBetween">
<androidx.fragment.app.FragmentContainerView
android:layout_width="match_parent"
android:layout_height="200dp"
android:id="@+id/fcv_a"/>
<View
android:layout_width="match_parent"
android:layout_height="2dp"
android:background="@color/black"/>
<androidx.fragment.app.FragmentContainerView
android:layout_width="match_parent"
android:layout_height="200dp"
android:id="@+id/fcv_b"/>
</LinearLayout>
public class fragmentPassBetween extends AppCompatActivity {
private FragmentPassA fragmentPassA;
private FragmentPassB fragmentPassB;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_fragment_pass_between);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
fragmentPassA = new FragmentPassA();
fragmentPassB = new FragmentPassB();
getSupportFragmentManager().beginTransaction().replace(R.id.fcv_a, fragmentPassA).commit();
getSupportFragmentManager().beginTransaction().replace(R.id.fcv_b, fragmentPassB).commit();
}
}
此时运行程序活动界面为:
准备工作就完成了,先来看看从碎片A传数据到碎片B吧!
通过Activity中转
在前面的学习当中我们了解到根据碎片可以得到它所在的活动,进一步我们就可以根据所得到的活动来获取它的另一个碎片,即我们所要将数据传递的接收者碎片B,调用碎片B里的方法就可以对碎片B的TextView进行文本的重新修改,显示出我们所要传递的数据。因此在碎片A与B当中我们需要先获取它们的按钮与TextView,在碎片B当中写将接收到的数据展示在文本框:
public void setmDataB (String data) {
mData = data;
textViewB.setText(mData);
}
对于碎片A需要对按钮注册点击事件,上面已经提到了碎片A向B的传递思路,代码如下:
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
textViewA = view.findViewById(R.id.tv_a_receive);
buttonpassA = view.findViewById(R.id.passDataByActivityA);
buttonpassA.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//向碎片B传递数据
Fragment fragmentB = ((fragmentPassBetween)getActivity())
.getSupportFragmentManager()
.findFragmentById(R.id.fcv_b);
if (fragmentB != null) {
((FragmentPassB)fragmentB).setmDataB("这是FragmentA传来的数据");
}
}
});
}
此时运行程序,按下碎片A里的按钮:
通过接口
在两个碎片当中都各自添加一个按钮,它们的点击事件就为通过接口实现数据传递,与此同时我们需要对两个碎片的按钮进行加载并注册点击事件。还是给大家展示从碎片A传递数据给碎片B吧,此时A就为被观察者,在A的内部定义接口:
private OnFragmentAChangeLiatener monFragmentAChangeLiatener;
public void setOnFragmentAChangeLiatener(OnFragmentAChangeLiatener onFragmentAChangeLiatener) {
monFragmentAChangeLiatener = onFragmentAChangeLiatener;
}
public interface OnFragmentAChangeLiatener {
void onFragementAChange(String data);
}
碎片A的点击事件:
buttoninterfacaA = (Button) view.findViewById(R.id.passDataByInterfacaA);
buttoninterfacaA.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (monFragmentAChangeLiatener != null) {
monFragmentAChangeLiatener.onFragementAChange("这是通过接口来自FragmentA的数据");
}
}
});
在碎片B当中实现这个接口:
//先获取FragmentPassA
FragmentPassA fragmentPassA = (FragmentPassA) ((fragmentPassBetween)getActivity())
.getSupportFragmentManager()
.findFragmentById(R.id.fcv_a);
//当它不为空的时候即找到了FragmentPassA,并调用FragmentPassA的、setOnFragmentAChangeLiatener方法并实现接口
if (fragmentPassA != null) {
fragmentPassA.setOnFragmentAChangeLiatener(new FragmentPassA.OnFragmentAChangeLiatener() {
@Override
public void onFragementAChange(String data) {
textViewB.setText(data);
}
});
}
此时运行程序,按下按钮碎片B就接收到了来自碎片A的数据:
到这里就结束了!