安卓开发高级控件学习之Fragment(Java与Kotlin)

Fragment可以看成是一个小型的Activity,设置的初衷是为了适应类似于平板电脑的大屏幕设备。Fragment 不能单独使用,必须嵌套在Activity 中,且Fragment的生命周期会受到宿主Activity的影响。
下图是非常经典一个Fragment分别对应手机与平板间不同情况的处理图:
在这里插入图片描述

1.Fragment生命周期

参考链接(https://www.runoob.com/w3cnote/android-tutorial-fragment-base.html)
在这里插入图片描述
碎片的生命周期包括 onAttach->onCreat->onCreatView->onActivityCreated->onStart->onResume->onPuse->onStop->onDestoryView->onDestory->onDetach。有些方法与Activity的相同,不同的有以下几个:

onAttach:当碎片和活动建立关联时调用
onCreateView:为碎片加载布局的时候调用
onActivityCreated:与碎片相关联的活动一定onCreate之后也就是创建完毕的时候调用
onDestoryView:与碎片相关联的视图被移除时使用
onDetach:当碎片与活动解除关联的时候被调用

将Fragment生命周期与其宿主Activity联合起来考虑的话:
运行程序/打开界面:Activity.onCreate->Fragment.onAttach->Fragment.onCreate->Fragment.onCreateView
->Fragment.onActivityCreated->Activity.onStart->Fragment.onStart->Activity.onResume->Fragment.onResume

按下主屏键/切换到其他应用:Fragment.onPause->Activity.onPause->Fragment.onStop->Activity.onStop

切回本应用/重新打开界面:Activity.onRestart->Activity.onStart->Fragment.onStart->Activity.onResume->Fragment.onResume

退出应用/回退键:Fragment.onPause->Activity.onPause->Fragment.onStop->Activity.onStop->Fragment.onDestoryView->Fragment.onDestory->Fragment.onDetach->Activity.onDestory

2.Fragment与Activity的关系

由Fragment的生命周期可看到Fragment与Activity的异同:
Activity和Fragment的相似点在于,它们都可加载布局、都有自己的生命周期,Fragment可看似迷你Activity。
不同点在于Fragment不能单独存在必须依附于Acticity,因为依附的关系所以也多了些与宿主Activity相关的生命周期方法。例外可以看到,Activity的生命周期方法都是protect属性,而Fragment生命周期方法都是public的,这说明Fragment的生命周期方法是由Activity调用的而非操作系统,Activity调用Fragment的生命周期方法并管理它。

3.Fragment的两种加载方式

一种是静态加载方式:
1.首先定义Fragment的布局, left_fragment.xml:
在这里插入图片描述
right_fragment.xml:
在这里插入图片描述
2.自定义一个Fragment类,需要继承Fragment或者他的子类,重写onCreateView()方法 在该方法中调用:inflater.inflate()方法加载Fragment的布局文件,接着返回加载的view对象
在这里插入图片描述
在这里插入图片描述
3.在需要加载Fragment的Activity对应的布局文件中添加fragment的标签,注意静态方式要添加android:name属性
在这里插入图片描述
效果如下:
在这里插入图片描述
Kotlin 布局文件相同,leftFragment碎片如下,rightFragment相同:

   class leftFragment : Fragment() {
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.left_fragment,container,false);
    }
}

另一种是动态加载方式
新建另一个碎片:布局如下
与another_rightfragment.xml在backgroud上颜色不同
在这里插入图片描述
修改activity_main.xml布局文件,将右侧Fragment替换成一个FrameLayout:
在这里插入图片描述

动态的含义就是动态的往FrameLayout里面添加Fragment,核心代码就是:
在这里插入图片描述
通过getSupportFragmentManager()获得Fragment管理对象,通过管理对象的beginTransaction()方法获得管理事务对象,利用这个事务对象完成增删替换,通过addToBackStack()方法将事务添加到返回栈中,通过commit()方法提交事务。

Kotlin: 和Java逻辑相同

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        replaceFragment(rightFragment())
        button.setOnClickListener {
            replaceFragment(anotherrightFragment())
        }
    }
    fun replaceFragment(fragment:Fragment){
        val fragmentManager = supportFragmentManager
        val fragmentTransition = fragmentManager.beginTransaction()
        fragmentTransition.apply {
              replace(R.id.rightlayout,fragment)
              addToBackStack(null)
              commit()
        }
    }

}

4.碎片与活动、活动与碎片、碎片与碎片通信

这里提到的通信主要是指组件获取,当然还有数据通信,这个以后用到再学习。

   活动中调用碎片里的方法:活动中通过getFragmentManager的findFragmentById获取碎片实例,这样就能调用碎片中方法。
   碎片中调用活动里的方法:碎片中通过(MainActivity)getActivity()获取活动实例,自然也可以调用活动里方法。
   碎片与碎片通信:在碎片中获得与之相关联的活动实例,通过活动实例又获取另一个碎片的实例,就可以实现碎片之间的通信。

5.Demo

这里采用ListView和Fragment来实现一个简易的新闻应用,实现方式和文章刚开始时的第二张图相同。
新闻列表使用ListView实现,首先先分别定义新闻列表页面的布局和新闻内容页的布局,新闻列表页面newsitem_fragment.xml:
在这里插入图片描述
新闻内容页布局newscontent_fragment.xml:
在这里插入图片描述
activity_main布局:
在这里插入图片描述
然后list_item.xml:ListView子项布局
在这里插入图片描述
接着定义News类:
在这里插入图片描述
定义ListView的适配器MyAdppter:
在这里插入图片描述
因为ListView是在Fragment中嵌入的,前面说过Fragment相当于迷你Activity,那么ListView的数据绑定工作就在这个Fragment中完成,并且ListView的点击事件也在这个迷你Activity里完成,点击事件为点击新闻列表后切换新闻内容,MainActivity中主要完成的是数据的动态加载以及这个newslist_fragment碎片的动态加载。基于这种设置思想,首先新建两个碎片的类:
newscontentfragment类,重写onCreateView方法:这里先不考虑数据的传递,等大致框架写好之后再进行数据的接收与发送:
在这里插入图片描述
newslistfragment类,同样要重写onCreateView方法并且完成数据绑定以及点击事件动态加载的工作,之前也提到了是通过FragmentManager来完成的:
avF在这里插入图片描述 修改newscontentfragment类以取到Bundle数据:
在这里插入图片描述
MainActivity中完成数据动态加载以及列表碎片的动态加载:
在这里插入图片描述 这里之后就大致完成了这个小Demo,但是在测试的时候发现点击某新闻列表回退后出现了这种情况: 在这里插入图片描述

修改newslistfragment类中的onCreatView方法,前面我们知道onCreatView方法是为碎片加载布局的时候调用,因此在加载布局初始化时设置新闻title:
在这里插入图片描述
现在点击进去再回退,发现变为了新闻列表:
在这里插入图片描述
新闻内容页:
在这里插入图片描述

Kotlin和Java版本的有一些不同,首先listview改用recyclerview,并且这里标题与内容两个碎片分别放在了不同的Activity中,不像Java版本那样通过动态加载的方式,而是通过启动活动的方式完成加载,这样也更简洁。

首先看内容碎片的布局文件:

<?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">
    <LinearLayout
        android:id="@+id/newcontentlayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        <TextView
            android:id="@+id/newscontenttitle"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:textSize="20sp"/>
        <View
            android:layout_width="match_parent"
            android:layout_height="1dp"
            android:background="#000"/>

        <TextView
            android:id="@+id/newscontent"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:textSize="18sp"/>

    </LinearLayout>

</RelativeLayout>

然后和静态加载的方式一样,创建NewsContentFragment继承Fragment重写onCreateView方法为碎片加载布局文件,并且增加refresh方法对布局文件中的控件内容进行刷新:

class NewsContentFragment:Fragment() {
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.news_content_frag,container,false)
    }

    fun refresh(title:String,content:String){
        newscontenttitle.text = title
        newscontent.text = content
    }

}

既然内容碎片是在单独的Activity中,因此也要创建这个Activity的布局文件activity_news_content.xml,只有一个fragment控件并通过name属性显示绑定碎片类:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <fragment
        android:id="@+id/newscontentFrag"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:name="com.demo.news.NewsContentFragment"/>

</LinearLayout>

接着创建内容碎片所在的的Activity NewsContentActivity,在这个NewsContentActivity中加载activity_news_content.xml布局并得到点击标题页面所传进来的新闻标题与内容,再调用NewsContentFragment的refresh方法刷新,调用的过程相当于就是活动与碎片通信,Kotlin提供了简单的通过碎片id就能得到碎片实例的方式,如下面代码中的var fragment = newscontentFrag as NewsContentFragment,得到碎片实例就能调用碎片中的方法了:

class NewsContentActivity:AppCompatActivity() {
     companion object{
         fun actionStart(context:Context,title:String,content:String){
             val intent = Intent(context,NewsContentActivity::class.java)
             intent.apply {
                 putExtra("title",title)
                 putExtra("content",content)
             }
             context.startActivity(intent)
         }

     }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_news_content)
        val title = intent.getStringExtra("title")
        val content = intent.getStringExtra("content")
        if(title!=null&&content!=null){
            var fragment = newscontentFrag as NewsContentFragment//活动与碎片通信
            fragment.refresh(title,content)
        }
    }

}

内容页面看完之后,就开始标题页面,和内容碎片一样也是通过静态加载碎片的方式:

首先新建标题碎片的布局方式new_title_frag.xml,如下所示,碎片的布局里面是通过RecyclerView显示标题的:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:orientation="vertical"
    android:layout_height="match_parent">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</LinearLayout>

因为是Recyclerview的缘故,我们还得创建子项布局,由TextView和间隔线组成:

<?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="wrap_content"
    android:orientation="vertical">

    <TextView
        android:id="@+id/news_title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:maxLines="1"
        android:textSize="20sp"
        android:ellipsize="end"
        android:padding="10dp"
        android:textColor="#000000"/>
    <View
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:alpha="0.1"
        android:background="#000"/>

</LinearLayout>

然后和静态加载的方式一样,新建碎片NewsTitleFragment继承Fragment重写onCreateView方法为碎片加载布局文件,并重写onActivityCreated方法为recyclerview添加适配器,如果直接在onCreateView中添加适配器会报错:recyclerview must not be null,原因参考:这个时候recyclerview可能并没有返回。

class NewsTitleFragment:Fragment() {
    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        val layoutManager = LinearLayoutManager(activity)
        recyclerview.layoutManager = layoutManager
        val adapter = NewsAdapter(getNews())
        recyclerview.adapter = adapter
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.new_title_frag,container,false)
    }

    fun getNews():List<News>{
        val news = ArrayList<News>()
        for(i in 1..15){
            news.add(News("我是标题$i","我是内容$i"))
        }
        return news
    }

}

在activity_main.xml中,显示生命添加的碎片:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <fragment
        android:id="@+id/newtitlefrag"
        android:name="com.demo.news.NewsTitleFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</RelativeLayout>

最后完成适配器NewsAdapter,并在onCreateViewHolder中添加点击事件,启动NewsContentActivity,并传入相应的title与content:

class NewsAdapter(val news:List<News>):RecyclerView.Adapter<NewsAdapter.ViewHolder>() {
    inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val newstitle:TextView = itemView.findViewById(R.id.news_title)
    }
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.new_title_item,parent,false)
        val viewHolder = ViewHolder(view)
        viewHolder.newstitle.setOnClickListener {
            val position = viewHolder.adapterPosition
            val news = news[position]
            NewsContentActivity.actionStart(parent.context,news.title,news.content)
        }
        return  viewHolder
    }

    override fun getItemCount(): Int = news.size

    override fun onBindViewHolder(holder: NewsAdapter.ViewHolder, position: Int) {
        val news = news[position]
        holder.newstitle.text = news.title
    }

}

效果如下:
在这里插入图片描述
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_42011443/article/details/106583089