Android Fragment

fragment 是android3.0中就开始引入的一个碎片功能,这个主要是针对android平板电脑这种大屏幕来使用的,到了android4.0之后也就开始在手机上引入fragment,在之前没有fragment时,就是将UI元素和具体的Activity界面结合在一起,而我们一般是通过不同的Activity之间的跳转来实现不同界面的改变,这样一来不仅UI代码得不到重用而且不停的跳转也会导致一些混乱。fragment的引入正好将一个应用变为一个模块化和可重用的组件。因为每一个fragment有它自己的布局文件,而且不同的Activity可以使用相同的fragment。

 

fragment生命周期

先来看下面的图片

Android Fragment

上面的图片我是别的网站截取过来的,从上图中我们可以很清楚的看到fragment生命周期和Activity是差不多相同的,记住Fragment是不能独立存在的,Fragment必须嵌入的Activty中,所以Fragment的生命周期是会受到Activity的生命周期的影响,当Activity暂停时那么所在的Fragment也会暂停,当Activity销毁时那么所在的Fragment也相应的销毁。但是当Activity运行之后即跑了onResume之后onPause之前,我们就可以单独对Fragment进行添加,删除,替换等一系列的操作。而一个Fragment可以添加在多个Activity中也可以是一个Activity中添加多个Fragment。

 

  如何创建一个简单的Fragment

要想创建一个Fragment就必须创建一个类继承Fragment或Fragment子类,这个Fragment代码写起来很像Activity,因为它们的生命周期都是差不多相同的,先来看一下继承Fragment类的代码:

Android Fragment
package com.cheng.fragmentactivty;  import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.app.Activity; import android.app.Fragment; import android.os.Build; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup;  @SuppressLint("NewApi")  public class FragmentA extends Fragment{      @Override     public void onAttach(Activity activity) {         // TODO Auto-generated method stub         super.onAttach(activity);     }      @Override     public void onCreate(Bundle savedInstanceState) {         // TODO Auto-generated method stub         super.onCreate(savedInstanceState);     }      @Override     public View onCreateView(LayoutInflater inflater, ViewGroup container,             Bundle savedInstanceState) {         // TODO Auto-generated method stub         return inflater.inflate(R.layout.fragment_a, container,false);     }      @Override     public void onActivityCreated(Bundle savedInstanceState) {         // TODO Auto-generated method stub         super.onActivityCreated(savedInstanceState);     }      @Override     public void onStart() {         // TODO Auto-generated method stub         super.onStart();     }      @Override     public void onResume() {         // TODO Auto-generated method stub         super.onResume();     }      @Override     public void onPause() {         // TODO Auto-generated method stub         super.onPause();     }      @Override     public void onStop() {         // TODO Auto-generated method stub         super.onStop();     }      @Override     public void onDestroyView() {         // TODO Auto-generated method stub         super.onDestroyView();     }      @Override     public void onDestroy() {         // TODO Auto-generated method stub         super.onDestroy();     } }
Android Fragment

 

上面的FragmentA就是继承自Fragment,实现了许多类似Activity的方法,比如onCreate,onPause,onDestory等,这些方法和Activity都是差不多的,我这里就不再说明了,我们平时在开发的过程中经常使用比较多的方法是:

onAttach()

这个是在刚刚开始添加Fragment与Activity关联的时候系统就会调用这个方法,具体当Activity里面调用setContentView()的方法的时候就调用这个onAttach()方法,主要是可以从这里获取到Activity的实例(注意看它的参数),当然在这里也可以使用getActivity()方法获取到它的activity的实例,这个我们后面会有说明的。

onCreate()

当创建Fragment时调用的方法在实现代码中, 应当初始化想要在fragment中保持的必要组件, 当fragment被暂停或者停止后可以恢复

onCreateView()

Fragment第一次绘制界面的时候系统就会调用这个方法,这个方法返回是一个view布局界面,看我上面贴的代码,onCreateView()方法中返回了

inflater.inflate(R.layout.fragment_a, container,false);这个界面,在onCreateView()方法中参数container指的是存放fragment布局layout中的viewgroup对象,简单点说就是当前fragment界面的父界面。我们来看看R.layout.fragment_a这个布局定义

Android Fragment
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"     android:layout_width="match_parent"     android:layout_height="match_parent" >          <Button          android:id="@+id/btn"         android:layout_width="wrap_content"         android:layout_height="wrap_content"         android:text="set_text"         android:layout_centerInParent="true"         />  </RelativeLayout>
Android Fragment

这个布局我就添加了一个按钮,说明这个fragment界面就是显示一个按钮

onCreate()和onCreayeView()都是activity上调用setContentView()的时候调用的,也就是说我们的Activity中的onCreate()有可能还没有跑完,所以最好不要在两个方法中取操作Activity相关的view,不然可能会出现错误。

onActivityCreated()

这个方法也就是在activity中onCreate()方法完成之后被调用的,我们就可以在这个方法activity的UI操作

onPause()

这个和Activity中的onPause()意思是一样的,这里就不多介绍了

将Fragment添加到指定的Activity中

通过上面的方法一个fragment就简单创建好了,这个fragment只有一个button按钮,但是我们要记住一个fragment是不能单独存在的,它必须嵌入到某一个Activity中,那么如何把已经写好的fragment嵌入到指定的Activity中呢?这里有两种方法可以做到:

(1)在指定的Activity的layout文件中声明fragment

直接来看layout文件:

Android Fragment
<RelativeLayout 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:paddingBottom="@dimen/activity_vertical_margin"     android:paddingLeft="@dimen/activity_horizontal_margin"     android:paddingRight="@dimen/activity_horizontal_margin"     android:paddingTop="@dimen/activity_vertical_margin"     tools:context=".FragmentActivity" >      <fragment          android:name="com.cheng.fragmentactivty.FragmentA"         android:id="@+id/frag_a"         android:layout_width="wrap_content"         android:layout_height="wrap_content"         android:layout_centerHorizontal="true"         />     <TextView         android:id="@+id/txt"         android:layout_below="@id/frag_a"         android:layout_width="wrap_content"         android:layout_height="wrap_content"         android:text="@string/hello_world"         android:layout_centerHorizontal="true"         android:layout_marginTop="20dp"          />  </RelativeLayout>
Android Fragment

在上面我们已经看到<fragment>标签,android:name属性指定在layout中实例化的Fragment类,当系统创建上述中的layout的时候就会实例化fragment标签中android:name指定的fragment,然后就调用onCreateView方法来获取到当前fragment所返回的layout,系统把fragment返回的layout插入<fragment>标签的位置直接替换掉了<fragment>标签。

注意:每一个fragment都需要指定唯一的标识,系统可以通过这个标识来进行一系列的事物操作,比如:添加,删除,替换等等

添加一个指定的标识很简单:

使用android:id属性提供唯一的ID,上面中已经使用

使用android:tag属性提供一个唯一的ID

如果以上的两个方法都没有提供,那么系统默认使用容器的ID,(好像在以后没有提供上面两个跑起来直接就崩溃的)

 

(2)在代码中手动将fragment添加到已经存在的viewGroup中

 先来看看我们Activity中layout布局做的改动:

Android Fragment
<RelativeLayout 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:paddingBottom="@dimen/activity_vertical_margin"     android:paddingLeft="@dimen/activity_horizontal_margin"     android:paddingRight="@dimen/activity_horizontal_margin"     android:paddingTop="@dimen/activity_vertical_margin"     tools:context=".FragmentActivity" >          <FrameLayout         android:id="@+id/content"         android:layout_width="wrap_content"         android:layout_height="wrap_content"         android:layout_centerHorizontal="true" />      <TextView         android:id="@+id/txt"         android:layout_below="@id/content"         android:layout_width="wrap_content"         android:layout_height="wrap_content"         android:text="@string/hello_world"         android:layout_centerHorizontal="true"         android:layout_marginTop="20dp"          />  </RelativeLayout>
Android Fragment

看上面的layout,和之前那个不同的是我把<fragment>标签给去掉了,新添加了一个<FrameLayout>标签,这个标签的id为content,这个有作用的,我们来看下面的代码:

Android Fragment
        FragmentA fragmentA=new FragmentA();         FragmentManager fragmentManager=getFragmentManager();         FragmentTransaction transaction=fragmentManager.beginTransaction();         transaction.add(R.id.content, fragmentA);         transaction.commit();
Android Fragment

 

FragmentManager类是实现在Activity中管理fragment的,我们可以在Activity中通过getFragmentManager()方法得到一个实例,那么我们可以使用FragmentManager做以下事情:

 

使用findFragmentById()或者findFragmentByTag()方法来获取已经在布局中定义的fragment,这个看字面的意思就知道它们和Activity中的findViewById()是一个道理的。

 

使用addOnBackStack()方法从Activity后退栈中弹出fragment,就类似于我们的返回建,这个作用应该不是很大。在后面介绍有关于fragment退回栈的时候在说一下。

 

使用addOnBackStackChangedListerner来注册一个监听器来监听上面所说的后退栈的变化,用处很少

 

使用FragmentManager来打开一个FragmentTransaction事务,这个是经常使用的,很重用,如果学过数据库的同学应该知道什么是事务,我们可以通过FragmentTransaction事务对fragment进行添加,删除,替换等操作命令,如何获取一个FragmentTransaction对象,从上面的代码中可以看到使用fragmentManager.beginTransaction()可以获取一个FragmentTransaction事务,那么transaction.add(R.id.content, fragmentA);这句话就是将已经创建好的fragment添加到Activity中,那么这个添加到哪里呢?这个add第一个参数是R.id.content,这个就是我们上面的定义的<FrameLayout>标签,这句话表示的是将fragmentA添加到FrameLayout布局中,我们利用多次调用add()方法来添加fragment,这样fragment显示的顺序和添加的顺序是一样的,

这里要注意当我们使用FragmentTransaction事务进行想要的操作之后,我们要在最后对事务进行提交,不然你所作的操作就无法看到,如上代码中最后一句话transaction.commit();就是提交所有的操作。

 

在调用commit()之前, 你可能想调用 addToBackStack(),将事务添加到一个fragment事务的back stack. 这个back stack由activity管理, 并允许用户通过按下 BACK 按键返回到前一个fragment状态.这个就是前面所说的addOnBackStack()方法,其实这个理解起来也很简单就是将一个fragment保存到stack中,在用户按返回建的时候就会依次的退回到fragment界面。
 
这里有两点需要注意的:
 
1。我们在调用 commit() 并不立即执行事务.恰恰相反, 它将事务安排排期, 一旦准备好, 就在activity的UI线程上运行(主线程).如果有必要, 无论如何, 你可以从你的UI线程调用 executePendingTransactions() 来立即执行由commit()提交的事务. 但这么做通常不必要, 除非事务是其他线程中的job的一个从属.
 
2。你只能在activity保存它的状态(当用户离开activity)之前使用commit()提交事务,最好是在onResume()方法和onStop()方法之间来提交事务。如果你试图在那个点之后提交, 会抛出一个异常.这是因为如果activity需要被恢复, 提交之后的状态可能会丢失.对于你觉得可以丢失提交的状况, 使用 commitAllowingStateLoss().
 
好了,我们添加好fragment之后来看看界面是如何
 
Android Fragment
 
就是这个,一个按钮和一个文本

 

 添加一个无UI的fragment

之前在onCreateView()方法中返回一个view:

Android Fragment
    @Override     public View onCreateView(LayoutInflater inflater, ViewGroup container,             Bundle savedInstanceState) {         // TODO Auto-generated method stub         return inflater.inflate(R.layout.fragment_a, container,false);     }
Android Fragment

这个返回值也可以直接返回一个null,这个就表示添加了一个无UI的fragment,要把一个无UI的fragment添加到Activity中就不能在layout里面直接添加了,这个必须在Activity代码中使用add添加(为fragment提供一个唯一的字符串"tag", 而不是一个view ID).这么做添加了fragment, 但因为它没有关联到一个activity layout中的一个view, 所以不会接收到onCreateView()调用. 因此不必实现此方法。

 

Fragment交互

先来看一下如何在fragment代码里面获取到自己的view

Android Fragment
    @Override     public void onActivityCreated(Bundle savedInstanceState) {         // TODO Auto-generated method stub         super.onActivityCreated(savedInstanceState);         Button btn=(Button) getView().findViewById(R.id.btn);                   btn.setOnClickListener(new OnClickListener() {                          @Override             public void onClick(View v) {                 // TODO Auto-generated method stub                 Toast.makeText(getActivity(), "OKOKOK", Toast.LENGTH_LONG).show();             }         });     }
Android Fragment

 

看上面通过getView()方法来得到fragment的layout的(我们一般是在onActivityCreated()方法中取操作UI,这个是一个好习惯),这个代码看起来很容易吧,再来看看在fragment中操作activity中的view :

Android Fragment
    @Override     public void onActivityCreated(Bundle savedInstanceState) {         // TODO Auto-generated method stub         super.onActivityCreated(savedInstanceState);         Button btn=(Button) getView().findViewById(R.id.btn);                   btn.setOnClickListener(new OnClickListener() {                          @Override             public void onClick(View v) {                 // TODO Auto-generated method stub                 Toast.makeText(getActivity(), "OKOKOK", Toast.LENGTH_LONG).show();                 setText("OKOK");             }         });     }      private void setText(String text){         TextView textview=(TextView) getActivity().findViewById(R.id.txt);         textview.setText("OKOK");     }
Android Fragment

我这里就是用了TextView textview=(TextView) getActivity().findViewById(R.id.txt);这个就可以获取到Activity中的view,现在我们也苦于反过来来,在Activity中获取fragment中的view

Android Fragment
    private FragmentA fragmentA;     private FragmentManager fragmentManager;     private Button btn;     @Override     protected void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         setContentView(R.layout.activity_fragment);         fragmentManager=getFragmentManager();         fragmentA=(FragmentA) fragmentManager.findFragmentById(R.id.fragment);         btn=(Button) fragmentA.getView().findViewById(R.id.btn);         btn.setOnClickListener(new OnClickListener() {                          @Override             public void onClick(View v) {                 // TODO Auto-generated method stub                 Toast.makeText(FragmentActivity.this, "koko", Toast.LENGTH_LONG).show();             }         });
Android Fragment

 

我们先使用findFragmentById()找到指定的fragment,然后再通过fragment.getView()返回fragment的layout,这个代码很容易吧,不过要注意如果在fragment中设置了按钮的监听事件在Activity中又重新设置了,那么这个时候是以fragment中的为准。

其实在使用的过程中Fragment和Activity中是分开的,各自处理自己的view,那么比如在Fragment中button点击事件是如何响应到Activity中呢?一个必须好的方法就是在Fragment中定义一个接口,在Acticity中需要实现这个接口,

         public interface textListener{         public void textchanger(String text);     }

这个接口就是在Fragment类中定义的,

Android Fragment
    private textListener listener;     @Override     public void onAttach(Activity activity) {         // TODO Auto-generated method stub         super.onAttach(activity);         try{             listener=(textListener) activity;         }catch(ClassCastException e){             throw new ClassCastException(activity.toString() + " must implement textlistener");         }              }
Android Fragment

 

fragmentA的 onAttach() 回调方法(当添加fragment到activity时由系统调用) 通过将作为参数传入onAttach()的Activity做类型转换来实例化一个textListener实例.如果activity没有实现接口, fragment会抛出 ClassCastException 异常. 正常情形下, listener成员会保持一个到activity的textListener实现的引用, 因此fragment A可以通过调用在OnArticleSelectedListener接口中定义的方法分享事件给activity

Android Fragment
public class FragmentActivity extends Activity implements textListener{      private FragmentA fragmentA;     private FragmentManager fragmentManager;     private Button btn;     private TextView textView;     @Override     protected void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         setContentView(R.layout.activity_fragment);                  textView=(TextView) findViewById(R.id.txt);              }     @Override     public void textchanger(String text) {         // TODO Auto-generated method stub         textView.setText(text);     }     
Android Fragment

这个就是Activity,我们可以看到这个Activity实现了textListner的接口,在回调textChanger()方法中我们设置text文本显示,这里应该很容易理解

 

addToBackStack()和replace()方法

之前有简单的介绍过addToBackStack(),这个方法就把移除的Fragment放到Activity的stack栈中,当我们按返回建的时候就会依次的返回到位于在stack栈顶中的fragment,如果没有调用 addToBackStack(), 那么当事务提交后, 那个fragment会被销毁,并且用户不能导航回到它. 有鉴于此, 当移除一个fragment时,如果调用了 addToBackStack(), 那么fragment会被停止, 如果用户导航回来,它将会被恢复。replace()就是一个替换当前的fragment,先来看一下代码:

 

Android Fragment
public class FragmentActivity extends Activity implements textListener{      private FragmentA fragmentA;     private FragmentB fragmentB;     private FragmentManager fragmentManager;     private Button btn;     private TextView textView;     @Override     protected void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         setContentView(R.layout.activity_fragment);                  textView = (TextView) findViewById(R.id.txt);         btn=(Button) findViewById(R.id.setfragment);                  fragmentA = new FragmentA();         fragmentB = new FragmentB();                  fragmentManager = getFragmentManager();                  FragmentTransaction transaction = fragmentManager.beginTransaction();         transaction.add(R.id.content, fragmentA);         transaction.commit();                  btn.setOnClickListener(new OnClickListener() {                          @Override             public void onClick(View v) {                 // TODO Auto-generated method stub                 FragmentTransaction transaction = fragmentManager.beginTransaction();                 transaction.replace(R.id.content, fragmentB);                 transaction.addToBackStack(null);                 transaction.commit();             }         });              }
Android Fragment

 

从代码中我们看到在刚刚开始的时候显示的FragmentA,在点击按钮之后我们使用了replace()把FragmentA换成了FragmentB,这个时候我们还可以看到代码里面在commit之前使用了 transaction.addToBackStack(null);如果我们点击按钮切换到FragmentB的时候再按返回建这个时候又回到FragmentA中而不是直接退出Activity,transaction.replace(R.id.content, fragmentB);大家还记得R.id.content是哪个id么?我这里在贴一次代码吧:

Android Fragment
<RelativeLayout 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:paddingBottom="@dimen/activity_vertical_margin"     android:paddingLeft="@dimen/activity_horizontal_margin"     android:paddingRight="@dimen/activity_horizontal_margin"     android:paddingTop="@dimen/activity_vertical_margin"     tools:context=".FragmentActivity" >                <FrameLayout         android:id="@+id/content"         android:layout_width="wrap_content"         android:layout_height="wrap_content"         android:layout_centerHorizontal="true" />               <TextView         android:id="@+id/txt"         android:layout_below="@id/content"         android:layout_width="wrap_content"         android:layout_height="wrap_content"         android:text="@string/hello_world"         android:layout_centerHorizontal="true"         android:layout_marginTop="20dp"          />          <Button          android:id="@+id/setfragment"         android:layout_width="wrap_content"         android:layout_height="wrap_content"         android:layout_centerHorizontal="true"         android:layout_below="@id/txt"         android:text="replace"         />  </RelativeLayout>
Android Fragment

这个是Activity的layout

 

 在Fragment代码中添加ActionBar

ActionBar我之前有一边文章也介绍过了,那么我们利用在Fragment中添加的ActionBar显示效果和Activity一样显示效果一样,在Fragment中可以通过实现onCreateOptionMenu(),但是要使用这个方法来能调用,我们必须在Fragment中的onCreate()方法中调用setHasOptionsMenu()来指出fragment愿意添加item到选项菜单(否则, fragment将接收不到对 onCreateOptionsMenu()的调用),随后从fragment添加到Option菜单的任何项,都会被追加到现有菜单项的后面.当一个菜单项被选择, fragment也会接收到 对 onOptionsItemSelected() 的回调.也可以在你的fragment layout中通过调用 registerForContextMenu() 注册一个view来提供一个环境菜单.当用户打开环境菜单, fragment接收到一个对 onCreateContextMenu() 的调用.当用户选择一个项目, fragment接收到一个对onContextItemSelected() 的调用.还有一个是我们要注意在Fragment的onOptionsItemSelected()可以接受菜单选项的点击事件但是这个最开始还是在Activity中先接受到,只有Activity中没有做处理时才会调用到Fragment中的onOptionsItemSelected()方法,来看一下代码吧:

 

Android Fragment
package com.cheng.fragmentactivty;   import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.app.ActionBar; import android.app.Activity; import android.app.Fragment; import android.os.Build; import android.os.Bundle; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.Button; import android.widget.TextView; import android.widget.Toast;  @SuppressLint("NewApi")  public class FragmentA extends Fragment{      private textListener listener;          @Override     public void onAttach(Activity activity) {         // TODO Auto-generated method stub         super.onAttach(activity);         try{             listener=(textListener) activity;         }catch(ClassCastException e){             throw new ClassCastException(activity.toString() + " must implement textlistener");         }              }      @Override     public void onCreate(Bundle savedInstanceState) {         // TODO Auto-generated method stub         super.onCreate(savedInstanceState);          setHasOptionsMenu(true);     }      @Override     public View onCreateView(LayoutInflater inflater, ViewGroup container,             Bundle savedInstanceState) {         // TODO Auto-generated method stub         return inflater.inflate(R.layout.fragment_a, container,false);     }      @Override     public void onActivityCreated(Bundle savedInstanceState) {         // TODO Auto-generated method stub         super.onActivityCreated(savedInstanceState);         Button btn=(Button) getView().findViewById(R.id.btn);                   btn.setOnClickListener(new OnClickListener() {                          @Override             public void onClick(View v) {                 // TODO Auto-generated method stub                 Toast.makeText(getActivity(), "OKOKOK", Toast.LENGTH_LONG).show();                                  listener.textchanger("OKOKOK");             }         });     }     @Override     public void onStart() {         // TODO Auto-generated method stub         super.onStart();     }      @Override     public void onResume() {         // TODO Auto-generated method stub         super.onResume();     }      @Override     public void onPause() {         // TODO Auto-generated method stub         super.onPause();     }      @Override     public void onStop() {         // TODO Auto-generated method stub         super.onStop();     }      @Override     public void onDestroyView() {         // TODO Auto-generated method stub         super.onDestroyView();     }      @Override     public void onDestroy() {         // TODO Auto-generated method stub         super.onDestroy();     }          public interface textListener{         public void textchanger(String text);     }      @Override     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {         // TODO Auto-generated method stub         super.onCreateOptionsMenu(menu, inflater); //        menu.add("Menu 1a").setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); //        menu.add("Menu 2a").setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);         inflater.inflate(R.menu.gmail, menu);              }      @Override     public boolean onOptionsItemSelected(MenuItem item) {         // TODO Auto-generated method stub         return super.onOptionsItemSelected(item);     }           }
Android Fragment


上面的代码中看先看onCreate()方法,调用了setHasOptionsMenu(true);如果不调用的话,Fragment中onCreateOptionsMenu()方法就没有调用,再来看看我们的onCreateOptionsMenu()里面使用了inflater.inflate(R.menu.gmail, menu);将布局写进去,同时也可以想注视那两次话这样写,剩下的onOptionsItemSelected()点击事件就很简单了,在这里也不必说了,先来看看图片

Android Fragment

 

代码地址:http://files.cnblogs.com/itchq/FragmentActivty.zip

http://www.cnblogs.com/itchq/p/3807143.html

 

Android Fragment,布布扣,bubuko.com

Android Fragment

上一篇:Algorithms 第一章1.2


下一篇:实现在手机浏览器上面打 电话发 短信 定位的 功能