中级实训Android学习记录——Activity、Fragment
学习记录 2020/11/24
Activity
- 创建一个Activity所需的三个步骤
创建一个继承Activity的类
- 这个Activity可以是很多不同的类,如可以继承AppCompatActivity
public class TestActivity extends AppCompatActivity {}
把创建好的Activity的类在AndroidManifest中声明
// 在AndroidManifest.xml中 <activity android:name=".TestActivity"></activity>
为创建好的Activity创建layout并在Activity的onCreate中设置
@override protected void onCreate(...){ super.onCreate(...); setContentView(R.layout.activity_test); }
Note:在Android中直接创建Activity会直接帮你做好以上三个步骤
- AndroidManifest.xml
// 导航栏ActionBar的设置
// 在Activity中设置
<activity ... android:label="Test"/> // 设置导航栏的标题为Test
<activity ... android:theme="@style/Theme.AppCompat.Light.NoActionBar"/> // 设置该activity不显示默认导航栏
<application ... android:theme="@style/Theme.AppCompat.Light.NoActionBar"/> // 设置整个应用的activity都不显示默认导航栏
// 设置屏幕方向
<activity ... android:screenOrientation="portrait"/> 竖屏锁定
<activity ... android:screenOrientation="landscape"/> 横屏锁定
// 设置默认启动的activity
// 将以下代码加入activity中即可将选定的activity设置成默认启动的activity
<intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter>
- Activity的生命周期
调用顺序
OnCreate() OnStart() OnResume() OnPuase() OnStop() OnDestroy()
-
Activity的跳转和数据传递
- 跳转的各种方式
- 显式跳转和隐式跳转
// 假设我们目前在AActivity中,想要跳转到BActivity中去 // 显式1(最常用) Intent intent = new Intent(AActivity.this, BActivity.class); startActivity(intent); // 显式2 Intent intent = new Intent(); intent.setClass(AActivity.this, BActivity.class); startActivity(intent); // 显式3 Intent intent = new Intent(); intent.setClassName(AActivity.this, "com.skypan.helloworld.jump.BActivity"); // 第二个参数要给BActivity的绝对路径 startActivity(intent); // 显式4 Intent intent = new Intent(); intent.setComponent(new ComponentName(AActivity.this, "com.skypan.helloworld.jump.BActivity")); // 第二个参数要给BActivity的绝对路径 startActivity(intent); // 隐式 Intent intent = new Intent(); // 在AndroidManif.xml中设置BActivity的action后 // <action android:name="com.skypan.test.BActivity" /> // <category android:name="android.intent.category.DEFAULT" /> // 我们把category的类型改成default,目前还不知道为什么 Intent.setAction("com.skypan.test.BActivity"); startActivity(intent);
- 数据传递方式
- 原理:利用startActivity传入intent来传输数据
// 假设我们目前在AActivity中,想要跳转到BActivity中去 // 我们用显式跳转1进行跳转 // 数据传输步骤 Intent intent = new Intent(AActivity.this, BActivity.class); // 1. 首先建立一个Bundle Bundle bundle = new Bundle(); // 2. 调用方法putString,类似的还有putInt等等,接受的都是两个参数,一个是key(String类型),一个是你要传入的值(可以是各种类型) bundle.putString("name", "天哥"); // 3. 把已经传入数据的bundle放进intent里面 intent.putExtras(bundle); // 4. 调用startActivity的时候传入intent即可 startActivity(intent); // 假设我们现在已经从AActivity中跳转到BActivity中,且已经按上面的代码传入了数据 // 数据接收步骤 // 1. 从intent里面取出带有数据的bundle Bundle bundle = getIntent().getExtras(); // 2. 从bundle中取出key对应的数据value String name = bundle.getString("name"); // 3. 对name进行自定义的使用
- 运用StartActivityForResult();
- 从跳转到的activity中接收数据
// 假设我们要从AActivity跳转到BActivity,并希望在BActivity返回后接收数据 // 步骤1 从AActivity使用StartActivityForResult跳转到BActivity // 1. 使用StartActivityForResult进行跳转 Intent intent = new Intent(AActivity.this, BActivity.class); StartActivityForResult(intent, 0); // 第二个参数是requestCode,用于标记result存放的位置 // 步骤2 从BActivity中进行数据传输并返回到AActivity // 1. 创建intent用以保存bundle Intent intent = new Intent(); // 2. 创建bundle用以保存数据 Bundle bundle = new Bundle(); // 3. 往bundle中输入数据 bundle.putString("title", "我回来了"); // 4. 将bundle加入intent intent.putExtras(bundle); // 5. 将intent加入result中 setResult(Activity.RESULT_OK, intent); // 第一个参数可以认为是固定的 // 6. 调用finish从BActivity中返回到AActivity finish(); // 步骤3 目前BActivity已经返回到AActivity中,我们就可以在AActiviy根据之前传入的requestCode接收数据,重写调用方法OnActivityResult // OnActivityResult方法会在StartActivityForResult跳转到的BActivity返回后自动调用 @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); Toast.makeText(AActivity.this, data.getExtras().getString("title"), Toast.LENGTH_LONG).show(); // 一样是获得bundle后通过get方法获得数据 }
- 跳转的各种方式
-
Activity的四种启动模式
- 设置方式,在AndroidManifest.xml中对activity的标签加入android:launchMode=""即可
<activity android:launchMode="standard"></activity>
- standard:标准模式,默认
Activity由任务栈管理,standard模式下,每启动一个Activity,就会创建一个新实例,放入栈中,每当一个Activity被Destroy,就被拿出栈
每跳转到一个Activity,都会调用Activity的onCreate函数
- singleTop:Task栈顶复用模式
当要启动的目标Activity已经位于同一个任务栈的栈顶时,不会创建新的实例,会复用栈顶的Activity,此时只会调用其onNewIntent方法;而如果要启动的目标Activity不在栈顶时,会创建新的实例,此时调用的是onCreate方法
- singleTask:Task栈内复用模式
当要启动的目标Activity已经在同一个任务栈中,会复用该Activity,调用其onNewIntent方法,并且在栈中,会清楚所有在Activity之上的Activity,即在Activity之后加入栈的Activity都会被弹出栈
任务栈可以通过在AndroidManifest.xml中给activity设置android:taskAffinity来改变,如无设置会采用默认的任务栈
- singleInstance:全局单例模式
当要启动的目标Activity在任何一个任务栈中,就复用,此时一个Activity占有一个任务栈。
Fragment
-
使用的时候注意
-
在使用时getFragmentManager()如出现错误
改用getSupportFragmentManager()
-
-
Fragment有自己的生命周期
-
Fragment依赖于Activity
-
Fragment通过
getActivity()
可以获取所在的Activity;Activity通过FragmentManager的findFragmentById()
或findFragmentByTag()
获取Fragment -
Fragment和Activity是多对多的关系
-
使用Fragment并加入到Activity中
// 1. 建立AFragment继承Fragment
// 2. 重写其onCreateView和onViewCreated函数
public class AFragment extends Fragment {
... onCreateView(..) {}
... onViewCreated(...) {}
}
// 3. 建立对应的layout并在Fragment中在onCreateView中引入布局文件(因为他函数中接收了参数inflater),并在onViewCreated中自定义布局文件的各种属性(因为他函数中接收了view)
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// 1. 引入布局文件
View view = inflater.inflate(R.layout.fragment_a, container, false);
return view;
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// 2. 通过view和findViewById来查找布局文件中的组件并进行自定义
??? = view.findViewById(???);
}
// 4. 建立BFragment继承Fragment,为BFragment重复步骤123
// 5. 建立ContainerActivity,在其布局文件activity_container.xml中声明一个FrameLayout,id为@+id/fl_container
<FrameLayout android:id="@+id/fl_container" .../>
// 6. 在ContainerActivity中,向container这个FrameLayout中加入一个Fragment
public class ContainerActivity extends AppCompatActivity {
private AFragment aFragment;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_container);
// 1. 实例化AFragment
aFragment = new AFragment();
// 2. 把AFragment添加到Activity的container中
getFrameManager().beginTransaction().add(R.id.fl_container, aFragment).commit();
// note: add函数中,第一个参数接收container,第二个参数接收fragment
// 并且要注意最后调用commit,一般在实践中把commit函数改成commiteAllowingStateLoss,意思是可以允许丢失的错误
}
}
- 使用Fragment替换Fragment
假设我们已经有以上的代码,此时我们在
fl_container
中替换一个BFragment可以这样做:bFragment = new BFragment(); getFragmentManager().beginTransaction().replace(R.id.fl_container, bFragment).commitAllowingStateLoss(); // 注意我们把add函数改成了replace函数,并把传入的aFragment改成了bFragment
但是
replace
函数存在一定问题:replace会先remove掉原先的aFragment,再add我们替换的bFragment,所以replace会造成一定的问题,会在之后细说
- 使用Fragment可能存在的问题
- 首先,Fragment和Activity虽然一起使用,但他们的生命周期是不同的,所以就会存在Fragment的任务仍在运行,而他关联的Activity却已经消失的情况,此时在Fragment中调用函数
getActivity()
会造成空指针错误,我们需要避免这种错误,所以我们希望你在调用方法的时候按以下方式调用:if (getActivity() != null) { // do something } else { // do something }
这样就能很好地避免空指针的错误
我们之前在使用Fragment的初始化时,都是直接调用
new Fragment()
,没有传递参数,而我们创建一个非默认初始化函数Fragment(String s)
会报错,此时我们的解决方式可以是// 1. 创建static的newInstance函数 public static AFragment newInstance(String title) { AFragment = new AFragment(); // 同样地,我们利用bundle来帮助我们传递参数 Bundle bundle = new Bundle(); bundle.putString("title", title); // 注意,我们这里需要调用的是fragment的setArguments函数 fragment.setArguments(bundle); return fragment; } // 2. 在实例化时调用newInstance函数 // 原本是 aFragment = new AFragment(); // 我们改成 aFragment = AFragment.newInstance("我是参数"); // 3. 在Fragment的其他函数中调用getArguments来获得bundle来获得传入的参数 if (getArguments() != null) { String s = getAruguments().getString("title"); // do something }
- Fragment回退栈应用
我们希望加入多个Fragment的时候,像加入多个Activity一样,按返回键会返回上一个加入的Fragment中(这在原本是达不到的),我们怎么做呢?
原本,我们按返回键就会直接返回到上一个Activity,而我们希望返回到上一个Fragment,可以这样做:
// 将之前的更换的操作改成这样 // 原本是这样:getFragmentManager().beginTransaction().replace(R.id.fl_container, bFragment).commitAllowingStateLoss(); // 改成这样 getFragmentManager().beginTransaction().replace(R.id.fl_container, bFragment).addToBackStack(null).commitAllowingStateLoss();
即在更换之后(或之前也可以?没试过),在commit之前调用方法
addToBackStack()
传入参数null即可达到目的注意,此时我们之前提过的问题就出现了,如果我们在replace到BFragment之前,更改过AFragment的内容,在replace到BFragment然后返回到AFragment时,AFragment的内容会被重新初始化,这就是因为调用了replace函数,而replace函数是通过先remove掉AFragment,再add一个BFragment达到的替换效果,而我们希望的是AFragment的内容被保存,我们可以通过以下代码达到目的:
// 1. 将之前的add操作更改 // 原本是 getFrameManager().beginTransaction().add(R.id.fl_container, aFragment).commit(); // 改成这样 getFrameManager().beginTransaction().add(R.id.fl_container, aFragment, "a").commit(); // 在add函数中增加了一个string参数,他是识别Fragment的前提,可以加入findFragmentByTag中识别出 // 2. 将之前的replace操作更改 // 原本是 getFragmentManager().beginTransaction().replace(R.id.fl_container, bFragment).addToBackStack(null).commitAllowingStateLoss(); // 改成这样 // 通过findFragmentByTag识别出aFragment Fragment fragment = getFragmentManaget().findFragmentByTag("a"); // 将remove改成hide即可 if (fragment != null) { getFragmentManager().beginTransaction().hide(fragemt).add(R.id.fl_container, bFragment).addToBackStack(null).commitAllowingStateLoss(); } else { getFragmentManager().beginTransaction().replace(R.id.fl_container, bFragment).addToBackStack(null).commitAllowingStateLoss(); }
此时我们就可以保存AFragment的状态而不是重新初始化AFragment
- Fragment和Activity的数据传输
为了实现Fragment和Activity之间的数据传输,我们可以这样做:
首先,在Fragment中声明接口
public interface IOnMessageClick { void onClick(String text); }
然后,让需要通信的Activity实现这个接口
public class ContainerActivity extends AppCompatActivity implements AFragment.IOnMessageClick { ... @Override public void onClick(String text) { // do something } }
然后,在Fragment的onAttach函数中绑定与Activity的接口
public class AFragment extends Fragment { ... private IOnMessageClick listener; @Override public void onAttach(Context context) { super.onAttach(context); try { listener = (IOnMessageClick) context; } catch (ClassCastException e) { throw new ClassCaseException("Activity 没有实现IOnMessageClick接口"); } } }
最后,在Fragment中调用接口函数即可传递数据
... listener.onClick("你好"); ... // 此时Frament将数据为string类型的"你好"传输到Activity中供使用