Fragment基础(一)

557 阅读9分钟
Fragment基础(一)
  • Fragment的历史

    • 在Android3.0的时候产生

      • 让activity更好的配置界面
      • 使用Fragment配置UI

        • 例如常见应用中的底部导航栏
    • 图示:

      image-20211119120200970

      • 左侧(平板示例),红色大框是一个activity,里面的两个红框为fragment
      • 右侧(手机端的新闻应用),两个界面都是fragment
  • Fragment特点

    • 具有独立生命周期

      • 可以在一个activity中组合多个Fragment,形成多窗口界面

      • 一个Fragment可以在activity中重复使用

        • 可以将Fragment看做activity中的一个模块
        • 可以独立接收输入事件
        • 可以activity正在运行的时候,动态增加或者删除Fragment
        • 可以将Fragment看做子activity
    • 必须有一个activity作为宿主

      • Fragment生命周期受宿主限制

        • 当activity暂定/销毁,里面的Fragment的暂定/销毁
        • 当activity运行时,Fragment可以执行它自己的操作(创建,暂定,销毁)
  • Fragment的简单使用

    • 实现目标:

      • 使用Fragment配合宿主activity,当点击按钮后实现对界面(TextView)进行更新
    • 运行截图

      • 点击前:

        image-20211119153933618

      • 点击后 image.png

    • 实现思路

      • 创建一个宿主activity(MainActivity)

      • 创建BasicFragment(继承Fragment,删除多余代码)

        • 使用解析器解析fragment对应的布局文件
        • 拿到布局中的控件
        • 为响应控件添加监听
      • 在res/layout中添加fragment对应的布局文件

        • 添加控件(指定id,方便监听)
    • 实现细节:

      • 解析布局:

        • activity使用setContentView解析布局
        • fragment使用布局解析器解析布局
      • 采用静态管理时,需要在activity_main.xml中绑定fragment
      • 工程规范:在对控件的text进行赋值的时候,一般不使用明文赋值,而是在res/string.xml中进行处理
    • 代码示例

      • 工程结构(BlankFragment2暂时不用的)

        image-20211119125128718

      • MainActivity.java

         package com.learningtogether.fragmentlearn;
         ​
         import androidx.appcompat.app.AppCompatActivity;
         ​
         import android.os.Bundle;
         ​
         public class MainActivity extends AppCompatActivity {
         ​
             @Override
             protected void onCreate(Bundle savedInstanceState) {
                 super.onCreate(savedInstanceState);
                 setContentView(R.layout.activity_main);//解析xml
             }
         }
        
      • BlankFragment1.java

         package com.learningtogether.fragmentlearn;
         ​
         import android.os.Bundle;
         ​
         import androidx.fragment.app.Fragment;
         ​
         import android.view.LayoutInflater;
         import android.view.View;
         import android.view.ViewGroup;
         import android.widget.Button;
         import android.widget.TextView;
         ​
         public class BlankFragment1 extends Fragment {
         ​
             private View root;
             private TextView textView;
             private Button button;
         ​
             @Override
             public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                      Bundle savedInstanceState) {
         ​
                 //因为onCreateView 是一个生命周期函数,所以在这个地方需要判断一下root是否为空;不为空,就没有必要重新加载了
                 if (root == null) {//这个地方为什么不能写成!root
                     //在Fragment中调用解析器加载xml布局文件
                     root = inflater.inflate(R.layout.fragment_blank1, container, false);
                 }
         ​
                 //从root中拿到这两个控件
                 textView = root.findViewById(R.id.textview);
                 button = root.findViewById(R.id.btn);
         //这个地方可以使用Lamda表达式进行简化
                 button.setOnClickListener(new View.OnClickListener() {
                     @Override
                     public void onClick(View view) {
                         textView.setText("这是按钮点击后的效果");
                     }
                 });
                 return root;
             }
         }
        
      • activity_main.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"
             >
             <!--将fragment与activity通过name属性进行绑定-->
             <fragment
                 android:id="@+id/fragment1"
                 android:name="com.learningtogether.fragmentlearn.BlankFragment1"
                 android:layout_width="match_parent"
                 android:layout_height="match_parent"
                 android:layout_weight="1"
                 ></fragment>    
         </LinearLayout>
        
      • fragment_blank1.xml

         <?xml version="1.0" encoding="utf-8"?>
         <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"
             tools:context=".BlankFragment1">
         ​
             <!-- TODO: Update blank fragment layout -->
             <TextView
                 android:id="@+id/textview"
                 android:layout_width="match_parent"
                 android:layout_height="40dp"
                 android:text="@string/hello_blank_fragment" />
         ​
             <Button
                 android:id="@+id/btn"
                 android:layout_width="match_parent"
                 android:layout_height="40dp"
                 android:text="@string/greeting"
             ></Button>
         ​
         </LinearLayout>
        
  • 在一个activity中添加两个Fragment

    • 将那个fragment1复制一份命名为fragment2,然后在activity_main.xml中再添加一个fragment,并使用name属性将这个fragment2与MainActivity进行绑定
  • Fragment中需要添加id,这个fragment作为一个特殊的组件,如果想在activity中使用的话,就一定要搞一个id

  • 动态替换fragment

    • 实现效果:

      • 主界面展示

        image-20211119133419284

      • 点击CHANGE

        image-20211119133437954

      • 点击REPLACE

        image-20211119133507731

    • 实现思路

      • 在main_activity .xml中添加两个按钮(进行页面切换),一个FragmentLayout(作为容器,用于存放动态添加的fragment)

      • 在MainActivity中

        • onCreate方法中

          • MainActivity实现监听接口

          • 获取控件实例并设置监听

          • 添加点击事件onClick()

            • 使用switch-case根据不同的控件id执行相应的逻辑

              • 执行替换函数replaceFragment,将这个BlankFragment1传进去
              • 执行替换函数replaceFragment,将新创建的ItemFragment传进去
        • onCreate方法外

          • 创建replaceFragment函数(模拟Fragment切换)

            • 实例化FragmentManager
            • 通过FragmentManager获取触发器
            • 使用触发器进行替换操作
            • 提交事件
    • 实现细节

      • 触发器

        • 对应的额事件:hide,remove,add,show
      • fragment有自己的管理栈

        • 入栈:当点击上述两个按钮时,每点击一次,会创建一个Fragment并扔到这栈里面
        • 出栈:点击手机的返回按钮
        • 细节:当两个按钮各点击三次时,有六个fragment在管理栈中,点击六次返回键即可退回至主界面
    • 代码示例

      • 工程结构

        image-20211119141732658

      • MainActivity.java

         package com.learningtogether.fragmentmanagers;
         ​
         import androidx.appcompat.app.AppCompatActivity;
         import androidx.fragment.app.Fragment;
         import androidx.fragment.app.FragmentManager;
         import androidx.fragment.app.FragmentTransaction;
         ​
         import android.os.Bundle;
         import android.view.View;
         import android.widget.Button;
         import android.widget.Toast;
         ​
         public class MainActivity extends AppCompatActivity implements View.OnClickListener {
         ​
             @Override
             protected void onCreate(Bundle savedInstanceState) {
                 super.onCreate(savedInstanceState);
                 setContentView(R.layout.activity_main);
                 
                 //在java代码中创建控件实例并添加监听
                 Button button = findViewById(R.id.btn);
                 button.setOnClickListener(this);
                 Button button1 = findViewById(R.id.btn_2);
                 button1.setOnClickListener(this);
             }
         ​
             @Override
             public void onClick(View view) {
                 switch (view.getId()){
                     case R.id.btn:
                         //将这个绑好信息的Fragment进行传递
                         replaceFragment(new BlankFragment1());
                         break;//switch 一定要搭配这个break;
                     case R.id.btn_2:
                         replaceFragment(new ItemFragment());
                         break;
                 }
             }
             
         //实现动态管理Fragment
             private void replaceFragment(Fragment fragment) {
                 FragmentManager fragmentManager = getSupportFragmentManager();
                 //拿到fragmentManager触发器
                 FragmentTransaction transation = fragmentManager.beginTransaction();
                 transation.replace(R.id.framelayout,fragment);//创建了一个replace事件;将fragment添加到R.id.framelayout这个容器里面
                 transation.addToBackStack(null);//将Fragment放到同一个栈里面
                 transation.commit();//提交事件
             }
         }
        
      • BlankFragment.java(就使用默认的,创建出来就行了)

      • MyItemRecycleViewAdapter.java(就用默认的,创建出来就行了)

      • activity_main.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"
             >
             <Button
                 android:id="@+id/btn"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:text="@string/change"
                 ></Button>
             <Button
                 android:id="@+id/btn_2"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:text="@string/replace"
                 ></Button>
         <!--这个里面放布局-->
             <FrameLayout
                 android:id="@+id/framelayout"
                 android:layout_width="match_parent"
                 android:layout_height="match_parent"
                 android:background="@color/teal_200"
                 ></FrameLayout>
         ​
         </LinearLayout>
        
      • 其余的三个布局就用自带的

  • Activity与fragment的通信

    • 概述:

      • Activity与Fragment是独立的类,但是他们承担相同职责:展示UI

      • 通信分类:

        • Activity与Fragment
        • Fragment与Fragment
        • Activity与Activity
      • 原生方案:Bundle通信
    • 通信实例(以fragment为载体)

      • 步骤:

        • 实例化Bundle对象

          image-20211119155509539

        • 对象调用方法(put函数,以键值对的形式传递数据)

          image-20211119155533832

        • 实例化Fragment

          image-20211119155558391

        • 将数据绑定在Fragment对象上

          image-20211119155625559

        • 调用函数,使用Fragment对象

          image-20211119155637936

      • 实现细节:

        • Bundle对象的put方法:

          image-20211119155757579

        • 当需要将一个java Bean绑定在fragment上进行数据传输时,这个时候就需要调用putParcelable,将java Bean作为一个key进行传输,这个涉及到java序列化
  • 实现Activity向fragment传递信息(使用Bundle通信机制)

    • 实现效果

      • 当用户点击屏幕按钮时,触发相应监听事件,在日志中打印信息
    • 运行截图

      • 主界面

        image-20211119151515399

      • 点击CHANGE后对应的Logcat日志

        image-20211119151818851

    • 实现思路

      • 创建MainActivity,创建Fragment,(使用的是动态添加Fragment,不需要在activity.xml中通过name 属性绑定fragment)

      • 在activity_main.xml中,搞一个按钮(触发监听的),搞一个FragmentLayout(装动态添加进来的Fragment)

      • 在MainActivity中实例化控件并添加监听

        • 监听器:

          • 实例化Bundle
          • 调用put族方法,以键值对传递信息
          • 实例化BlankFragment
          • 将数据绑定在BlankFragment对象上
          • 将BlankFragment对象作为参数传入replaceFragment中,实现动态添加fragment
        • 实现动态添加Fragment

          • 实例化FragmentManager
          • 获取触发器
          • 触发器调用函数,创建事件
          • 触发器提交事件
    • 实现细节

    • 代码示例

      • 工程结构

        image-20211119151900189

      • MainActivity.java

         package com.learningtogether.test;
         ​
         import androidx.appcompat.app.AppCompatActivity;
         import androidx.fragment.app.Fragment;
         import androidx.fragment.app.FragmentManager;
         import androidx.fragment.app.FragmentTransaction;
         ​
         import android.os.Bundle;
         import android.view.View;
         import android.widget.Button;
         import android.widget.Toast;
         ​
         public class MainActivity extends AppCompatActivity implements View.OnClickListener {
             @Override
             protected void onCreate(Bundle savedInstanceState) {
                 super.onCreate(savedInstanceState);
                 setContentView(R.layout.activity_main);
                 //在java代码中实例化控件并添加监听
                 Button button = findViewById(R.id.btn);
                 button.setOnClickListener(this);
             }
            
             @Override
             public void onClick(View view) {
                 switch (view.getId()) {
                     case R.id.btn:
                         //实例化Bundle
                         Bundle bundle = new Bundle();
                         //bundle以键值对的形式传递信息
                         bundle.putString("message", "这个是从MainActivity中传过来的信息");
                         //实例化Fragment
                         BlankFragment1 bf = new BlankFragment1();
                         //将数据绑定在Fragment 对象上
                         bf.setArguments(bundle);
                         //调用函数,实现动态添加Fragment
                         replaceFragment(bf);
                 }
             }
             //实现动态管理Fragment
             private void replaceFragment(Fragment fragment) {
                 //实例化 FragmentManager
                 FragmentManager fragmentManager = getSupportFragmentManager();
                 //实例化FragmentTransaction,拿到fragmentManager触发器
                 FragmentTransaction transation = fragmentManager.beginTransaction();
                 //触发器调用相应函数,创建事件
                 transation.replace(R.id.framelayout, fragment);
                 transation.commit();//提交事件
             }
         }
        
      • BlankFragment1.java

         package com.learningtogether.test;
         ​
         import android.annotation.SuppressLint;
         import android.os.Bundle;
         ​
         import androidx.fragment.app.Fragment;
         ​
         import android.util.Log;
         import android.view.LayoutInflater;
         import android.view.View;
         import android.view.ViewGroup;
         import android.widget.Button;
         import android.widget.Toast;
         ​
         public class BlankFragment1 extends Fragment {
             
             private static final String TAG = "BlankFragment1";
         ​
             @Override
             public void onCreate(Bundle savedInstanceState) {
                 super.onCreate(savedInstanceState);
         ​
                 //获取的是伴随Fragment产生的参数
                 Bundle bundle = this.getArguments();//返回activity中传入的bundle
                 //获取bundle中的数据,需要传入key
                 String str = bundle.getString("message");
                 //打印信息
                 Log.d(TAG,"onCreate "+str);
             }
         ​
         }
        
      • activity_main.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"
             >
             <Button
                 android:id="@+id/btn"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:text="@string/change"
                 ></Button>
         ​
             <!--这个里面放布局-->
             <FrameLayout
                 android:id="@+id/framelayout"
                 android:layout_width="match_parent"
                 android:layout_height="match_parent"
                 android:background="@color/teal_200"
                 ></FrameLayout>
         ​
         </LinearLayout>
        
      • fragment_balnk1.xml

         <?xml version="1.0" encoding="utf-8"?>
         <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=".BlankFragment1">
         ​
             <!-- TODO: Update blank fragment layout -->
             <TextView
                 android:layout_width="match_parent"
                 android:layout_height="match_parent"
                 android:text="@string/hello_blank_fragment" />
         ​
         ​
         </FrameLayout>
        
  • 动态添加Fragment

    • 步骤

      image-20211119153609194

  • 通过接口实现通信(java的类间通信)

    • 实现activity与fragment进行通信

      • 定义一个接口

      • 设计原则(面向接口编程):不应该有多余的接口

        • activity向fragment发送信息
        • fragment向activity发送信息
    • eventBus,LiveData(其他的通信模式,fragment或者activity只关心其感兴趣的方式)

      • 这个里面会有发布订阅模式,LifeCycle模式
  • 工程结构

image.png

  • 实现效果:

    • 从Fragment向Activity发送消息
    • 在Fragment接收Activity的消息
  • 运行截图:

    • 从Fragment向Activity发送消息

      image-20211119233912651

    • 在Fragment接收Activity的消息

image.png

  • 代码实现:

    • BlankFragment1.java

       package com.learningtogether.fragmentmanagers;
       ​
       import android.annotation.SuppressLint;
       import android.nfc.Tag;
       import android.os.Bundle;
       ​
       import androidx.fragment.app.Fragment;
       ​
       import android.util.Log;
       import android.view.LayoutInflater;
       import android.view.View;
       import android.view.ViewGroup;
       import android.widget.Button;
       import android.widget.Toast;
       ​
       public class BlankFragment1 extends Fragment implements View.OnClickListener{
           private static final String TAG = "BlankFragment1";
           /*
               优化思路1:
               * 在解析fragment布局时为什么要使用全局变量?
               *   因为可能出现BalankFragment中的onCreate方法调用多次的可能
               *   如果使用局部变量,那么每调用一次onCreate方法时就会重新解析一遍fragment布局文件
               * 使用全局变量+判空即可实现,只在首次调用onCreate方法的时候会进行布局文件的解析*/
           private View rootView;
       ​
           public BlankFragment1() {
               // Required empty public constructor
           }
       ​
           /*为什么要在Fragment中去声明接口对象?
               结合这个接口中的方法名,要想实现activity与fragment双向通信等价于
                   实现fragment向activity发送数据
                   实现fragment可以从activity拿数据*/
           private IFragmentCallBack fragmentCallBack;
       ​
           /*
           承接来自Activity中的匿名内部类--->就是这个接口实例
               在Fragment中声明这个方法,即可将其绑定在Fragment上,
               在Activity中仅需实例化Fragment对象即可调用这个方法,
               配合接口的匿名内部类即可在activity中重写接口方法
           此时就形成了一个很牛逼的现象,就是在Fragment与Activity中都可以处理这个接口对象; */
           public void setFragmentCallBack(IFragmentCallBack callBack) {
               fragmentCallBack=callBack;
           }
       ​
           @SuppressLint("LongLogTag")
           @Override
           public void onCreate(Bundle savedInstanceState) {
               super.onCreate(savedInstanceState);
               //获取的是伴随Fragment产生的参数
               Bundle bundle = this.getArguments();//返回activity中传入的参数
               bundle.getString("message");
           }
       ​
           @Override
           public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                    Bundle savedInstanceState) {
       ​
               if (rootView == null){
                   rootView = inflater.inflate(R.layout.fragment_blank1, container, false);
               }
               //实例化fragment布局文件中的控件,并添加监听
               Button btn = rootView.findViewById(R.id.btn_getMsgFromActivity);
               btn.setOnClickListener(this);
               Button btn1 = rootView.findViewById(R.id.btn_sendMsgToActivity);
               btn1.setOnClickListener(BlankFragment1.this);
               return rootView;
           }
       ​
           @Override
           public void onClick(View view) {
               switch (view.getId()){
                   case R.id.btn_getMsgFromActivity:
                       String msg = fragmentCallBack.getMsgFromActivity("null");
                       Toast.makeText(BlankFragment1.this.getContext(),msg,Toast.LENGTH_SHORT).show();
                       break;
                   case R.id.btn_sendMsgToActivity:
                       fragmentCallBack.sendMsgToActivity("hello,I am from fragment");
                       break;
               }
           }
       //一般都是在onResume函数中去启动Button或者在onCreateView后面弄
           @Override
           public void onResume() {
               super.onResume();
           }
       ​
       }
      
    • IFragmentCallBack.java

       package com.learningtogether.fragmentmanagers;
       /*
       * 设计原则:
       * 面向接口编程;用得到才搞接口;
       * 接口的默认修饰符,通过接口实现弱关联,实现通信*/
       public interface IFragmentCallBack {
           void sendMsgToActivity(String string);
           String getMsgFromActivity(String msg);
       }
      
    • MainActivity.java

       package com.learningtogether.fragmentmanagers;
       ​
       import androidx.appcompat.app.AppCompatActivity;
       import androidx.fragment.app.Fragment;
       import androidx.fragment.app.FragmentManager;
       import androidx.fragment.app.FragmentTransaction;
       ​
       import android.os.Bundle;
       import android.view.View;
       import android.widget.Button;
       import android.widget.Toast;
       ​
       public class MainActivity extends AppCompatActivity implements View.OnClickListener {
       ​
           @Override
           protected void onCreate(Bundle savedInstanceState) {
               super.onCreate(savedInstanceState);
               setContentView(R.layout.activity_main);
               //在java代码中创建控件实例并添加监听
               Button button = findViewById(R.id.btn);
               button.setOnClickListener(this);
           }
       ​
           @Override
           public void onClick(View view) {
               switch (view.getId()){
                   case R.id.btn:
                       //实例化Bundle对象
                       Bundle bundle = new Bundle();
                       //bundle以键值对的形式传递信息
                       bundle.putString("message","I LOVE YOU");
                       //实例化Fragment
                       BlankFragment1 bf = new BlankFragment1();
                       //将数据绑定在Fragment上
                       bf.setArguments(bundle);
                       //在activity中实例化了一个fragment对象,因为fragment中有接口对象的实例和方法,等效于activity也有了
                       //在activity中对接口对象赋值,配合接口匿名内部类;
                       bf.setFragmentCallBack(new IFragmentCallBack() {
                           @Override
                           public void sendMsgToActivity(String msg) {
                               //这里第一个参数写成this会出现问题的,this,代表的是这个匿名内部类了
                               Toast.makeText(MainActivity.this,msg,Toast.LENGTH_LONG).show();
                           }
       ​
                           @Override
                           public String getMsgFromActivity(String msg) {
                               return "hello,I`m from Activity ";
                           }
                       });
       ​
                       //将这个绑好信息的Fragment进行传递
                       replaceFragment(bf);
                       break;//switch 一定要搭配这个break;
       ​
               }
           }
       ​
       //实现动态管理Fragment,好好研究一下fragment的触发器
           private void replaceFragment(Fragment fragment) {
               FragmentManager fragmentManager = getSupportFragmentManager();
               //拿到fragmentManager触发器
               FragmentTransaction transation = fragmentManager.beginTransaction();
               transation.replace(R.id.framelayout,fragment);//创建了一个replace事件
               transation.addToBackStack(null);//将Fragment添加到同一任务栈中
               transation.commit();//提交事件
           }
       }
      
    • activity_main.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"
           >
           <Button
               android:id="@+id/btn"
               android:layout_width="match_parent"
               android:layout_height="wrap_content"
               android:text="@string/change"
               ></Button>
       ​
           <!--这个里面放布局-->
           <FrameLayout
               android:id="@+id/framelayout"
               android:layout_width="match_parent"
               android:layout_height="match_parent"
               android:background="@color/teal_200"
               ></FrameLayout>
       ​
       </LinearLayout>
      
    • fragment_blank1.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"
           >
           <Button
               android:id="@+id/btn"
               android:layout_width="match_parent"
               android:layout_height="wrap_content"
               android:text="@string/change"
               ></Button>
       ​
           <!--这个里面放布局-->
           <FrameLayout
               android:id="@+id/framelayout"
               android:layout_width="match_parent"
               android:layout_height="match_parent"
               android:background="@color/teal_200"
               ></FrameLayout>
       ​
       </LinearLayout>
      

\

  • as使用

    • 恢复误删除的文件(红色框指定的包下的文件)

      image-20211119141958042

      1. 选中需要恢复的包

      2. file->LocalHistory->show history

        image-20211119142201577

      3. 选中删除事件,点击左上角撤销即可恢复对应的删除事件(一个一个恢复)

        image-20211119142353240

      4. 恢复后的工程结构

        image-20211119142420024