中级实训Android学习记录——Activity、Fragment

学习记录 2020/11/24

Activity

  • 创建一个Activity所需的三个步骤
  1. 创建一个继承Activity的类

    • 这个Activity可以是很多不同的类,如可以继承AppCompatActivity
    public class TestActivity extends AppCompatActivity {}
    
  2. 把创建好的Activity的类在AndroidManifest中声明

    // 在AndroidManifest.xml中
    <activity android:name=".TestActivity"></activity>
    
  3. 为创建好的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可能存在的问题
  1. 首先,Fragment和Activity虽然一起使用,但他们的生命周期是不同的,所以就会存在Fragment的任务仍在运行,而他关联的Activity却已经消失的情况,此时在Fragment中调用函数getActivity()会造成空指针错误,我们需要避免这种错误,所以我们希望你在调用方法的时候按以下方式调用:
if (getActivity() != null) {
	// do something
} else {
	// do something
}

这样就能很好地避免空指针的错误

  1. 我们之前在使用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中供使用
posted @ 2020-11-28 20:38  沐锋丶  阅读(96)  评论(0编辑  收藏  举报