一,碎片的简单用法
在一个活动中添加两个碎片,并且让这两个碎片平分活动空间。
第一步:首先在layout中创建 left_fragment.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="button"/>
</LinearLayout>
和right_fragment:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:background="#00ff00"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:textSize="20sp"
android:text="文本"></TextView>
</LinearLayout>
第二步:创建leftFragment.class和rightFragment.class类
public class LeftFragment extends Fragment {
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState){
View view=inflater.inflate(R.layout.left_fragment,container,false);
return view;
}
}
和:
public class RightFragment extends Fragment {
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view=inflater.inflate(R.layout.left_fragment,container,false);
return view;
}
}
第三步:修改mainActivity.xml文件,通过name引入使用
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:id="@+id/left_fragment"
android:name="com.example.fragmenttest.LeftFragment"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="match_parent"
/>
<fragment
android:id="@+id/right_fragment"
android:name="com.example.fragmenttest.RightFragment"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="match_parent"
/>
</LinearLayout>
实现的效果:
这看起来和前端中的组件并无甚不同。
二,动态地添加碎片
1,第一步:创建待添加的碎片实例another_right_fragment.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:background="#ffff00"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="另一个碎片"
android:textSize="20sp"/>
</LinearLayout>
2,第二步:修改mainActivity.xml
就是在LinearLayout布局下嵌套FrameLayout布局:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:id="@+id/left_fragment"
android:name="com.example.fragmenttest.LeftFragment"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="match_parent"
/>
<FrameLayout
android:id="@+id/right_layout"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="match_parent"
/>
</LinearLayout>
3,在mainActivity.class主活动中修改
也就是默认在右边的碎片中先渲染一个碎片,然后通过点击按钮切换FrameLayout容器中的碎片。
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//右侧活动初始展示的碎片
replaceFragment(new RightFragment());
//点击左侧的按钮
Button button =(Button) findViewById(R.id.button);
button.setOnClickListener(this);
}
@Override
public void onClick(View view) {
switch(view.getId()){
case R.id.button:
replaceFragment(new AnotherRightFragment());
break;
default:
break;
}
}
private void replaceFragment(Fragment fragment) {
//1,创建新的碎片实例new AnotherRightFragment()
//2,获取FragmentManager
FragmentManager fragmentManager =getSupportFragmentManager();
//3,通过beginTransaction()开启一个事务
FragmentTransaction transaction=fragmentManager.beginTransaction();
//4,向容器内添加事务,(容器的id,待添加的碎片实例)
transaction.replace(R.id.right_layout,fragment);
//提交事务才能完成
transaction.commit();
}
}
4,在碎片中模拟返回栈
想实现的效果是,点back键的时候,碎片能返回,而不是直接返回上一个活动栈。
修改MainActivity.class文件:
private void replaceFragment(Fragment fragment) {
//1,创建新的碎片实例new AnotherRightFragment()
//2,获取FragmentManager
FragmentManager fragmentManager =getSupportFragmentManager();
//3,通过beginTransaction()开启一个事务
FragmentTransaction transaction=fragmentManager.beginTransaction();
//4,向容器内添加事务,(容器的id,待添加的碎片实例)
transaction.replace(R.id.right_layout,fragment);
//将事务添加到返回栈中,一般传入null就行,这样碎片中的活动就会和已经加入返回栈中一样
transaction.addToBackStack(null);
//提交事务才能完成
transaction.commit();
}
这样一来,就好像碎片内的活动都在返回栈中似的。
5,碎片和活动之间进行通信
虽然碎片嵌套于活动中,可是实际上他们的关系并没有那么亲密。可以看到碎片和活动都是各自存在于一个独立的类当中,它们之间并没有那么明显的方式来直接进行通信。
那如果现在我想要在活动中调用碎片中的方法,或者碎片中调用活动的方法。应该如何实现?
FragmentManager 提供了一个类似于 findViewById()的方法,专门用于从布局文件中获取碎片的实例,代码如下所示:
RightFragment rightFragment = (RightFragment) getFragmentManager().findFragmentById(R.id.right fragment);
调用FragmentManager的 findFragmentById()方法,可以在活动中得到相应碎片的实例然后就能轻松地调用碎片里的方法了。
那碎片中又如何获取活动的实例呢?
MainActivity activity = (MainActivity) getActivity();
三,碎片的生命周期
四,动态加载布局的技巧
1,使用限定符
使用限定符,可以让程序判断当前程序运行的环境,从而使用单页模式还是双页模式。
例如:但我们手机要显示一个页面,而宽屏的平板需要显示两个页面平铺时,就可以:
首先在layout/activity_main.xml中书写正常的单页模式的布局。
然后res下新建文件夹:layout-large,新建同名文件activity_main.xml。书写平板的双屏布局。
等程序运行之后,就会判断环境,平板时读取layout-large中的代码,否则读取layout中的代码。
2,使用最小宽度限定符
如果我们想准确地控制媒体查询。
在res下新建layout-sw660dp文件夹,然后新建同名activity_main.xml文件写布局就行。这样一来当程序运行在宽度大于600dp的设备上的时候,会加载layout-sw600dp/activity_main布局,否则加载默认的layout/activity_main布局。
五,碎片的最佳应用
如果像上文那样,每个设备都写一套代码,工作量巨大后续维护也会困难。
第一步:在build.gradle引入recycleview
dependencies {
implementation 'androidx.recyclerview:recyclerview:1.1.0'
}
第二步:准备一个新闻实体类News:
public class News {
public String title;
public String content;
public String getTitle(){
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
第三步:新建布局文件news_content_frag.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/visibility_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:visibility="invisible">
<TextView
android:id="@+id/news_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:padding="10dp"
android:textSize="20sp"/>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#000"/>
<TextView
android:id="@+id/news_content"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:padding="15dp"
android:textSize="18sp"/>
</LinearLayout>
<View
android:layout_width="1dp"
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
android:background="#000"/>
</RelativeLayout>
新闻内容的布局主要分为两个部分,头部显示新闻标题,正文显示新闻内容,中间使用一条细线分隔开。
第四步:新建一个NewsContentFragment类:
package com.example.fragmenttest;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.fragment.app.Fragment;
public class NewsContentFragment extends Fragment {
private View view;
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
//加载新闻布局
view = inflater.inflate(R.layout.news_content_frag, container, false);
return view;
}
/**
* 将新闻标题和新闻内容显示在界面上 用来刷新新闻详情
*/
public void refresh(String newsTitle, String newsContent) {
View visibilityLayout = view.findViewById(R.id.visibility_layout);
visibilityLayout.setVisibility(View.VISIBLE);//把visibilityLayout设置成可见
TextView newsTitleText = (TextView) view.findViewById(R.id.news_title);//获取新闻标题控件
TextView newsContentText = (TextView) view.findViewById(R.id.news_content);//获取新闻正文控件
newsTitleText.setText(newsTitle);//刷新新闻标题
newsContentText.setText(newsContent);//刷新新闻内容
}
}
首先在onCreateView()方法里加载了我们刚刚创建的news content frag布局,这个没什么好解释的。接下来又提供了一个 refresh()方法,这个方法就是用于将新闻的标题和内容显示在界面上的。可以看到,这里通过 findViewBvId)方法分别获取到新闻标题和内容的控件,然后将方法传递进来的参数设置进去。
这样新闻内容的碎片fragment类文件和xml布局就创建好了。
但是他们都是在双页模式下使用的。如果想要在单页模式下使用的话。还需要再创建一个活动。
第五步:新建NewsContentActivity文件
会自动创建对应的news_content.xml文件,修改为:
<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/news_content_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="com.example.fragmenttest.NewsContentFragment"/>
</LinearLayout>
这里直接引入了上文创建的NewsContentFragment类,也就相当于引入了news_content_frag.xml布局文件。
然后修改newsContentActivity中的代码:
public class NewsContentActivity extends AppCompatActivity {
/**
* 构建Intent,传递所需数据
*/
public static void actionStart(Context context, String newsTitle, String newsContent){
Intent intent = new Intent(context,NewsContentActivity.class);
intent.putExtra("news_title",newsTitle);
intent.putExtra("news_content",newsContent);
context.startActivity(intent);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.news_content);
//获取传入的新闻标题、新闻内容
String newsTitle = getIntent().getStringExtra("news_title");
String newsContent = getIntent().getStringExtra("news_content");
//获取NewsContentFragment 实例
NewsContentFragment newsContentFragment = (NewsContentFragment) getSupportFragmentManager().findFragmentById(R.id.news_content_fragment);
//刷新NewsContentFragment 显示数据
newsContentFragment.refresh(newsTitle,newsContent);
}
}
在 onCreate() 方法中通过 Intent 获取传入的新闻标题和内容
然后调用 FragmentManager 的 findFragmentById() 方法得到 NewsContentFragment 的实例
接着调用它的 refresh() 方法,并将新闻的标题和内容传入,显示数据
第六步:创建新闻列表的布局 news_title_frag.xm
接下来还需再创建显示新闻列表的布局 news_title_frag.xml,如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!--新闻列表-->
<android.support.v7.widget.RecyclerView
android:id="@+id/news_title_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
第七步:新建 news_item.xml 作为 上述 RecyclerView 子项的布局
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/news_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:ellipsize="end"
android:textSize="18sp"
android:padding="10dp"/>
子项的布局就只有一个 TextView
新闻列表和子项布局都创建好了,接下来就需要一个用于展示新闻列表的地方
这里新建 NewsTitleFragment 作为展示新闻列表的碎片:
/**
* 新闻列表fragment
*/
public class NewsTitleFragment extends Fragment{
private boolean isTowPane;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.news_content_frag, container, false);
return view;
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (getActivity().findViewById(R.id.news_content_layout)!= null){
// 可以找到 news_content_layout 布局时,为双页模式
isTowPane = true;
}else {
// 找不到 news_content_layout 布局时,为单页模式
isTowPane = false;
}
}
}
为实现上述 onActivityCreated() 方法中判断当前时双页还是单页模式
接下来在 NewsTitleFragemt 中新建一个内部类 NewsAdapter 来作为 RecyclerView 的适配器
如下:
public class NewsTitleFragment extends Fragment{
private boolean isTowPane;
. . .
/**
* RecyclerViews适配器
* */
class NewsAdapter extends RecyclerView.Adapter<NewsAdapter.ViewHolder> {
private List<News> mNewsList;
class ViewHolder extends RecyclerView.ViewHolder {
TextView newsTitleText;
public ViewHolder(View view) {
super(view);
newsTitleText = (TextView) view.findViewById(R.id.news_title);//新闻标题
}
}
public NewsAdapter(List<News> newsList) {
mNewsList = newsList;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
//加载布局
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.news_item, parent, false);
//每个Item的点击事件
final ViewHolder holder = new ViewHolder(view);
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
News news = mNewsList.get(holder.getAdapterPosition());
//如果是双页模式,则刷新NewsContentActivity中的数据
if (isTwoPane) {
NewsContentFragment newsContentFragment = (NewsContentFragment) getFragmentManager().findFragmentById(R.id.news_content_fragment);
newsContentFragment.refresh(news.getTitle(), news.getContent());
} else {
//如果是单页模式,则直接启动NewsContentActivity
NewsContentActivity.actionStart(getActivity(), news.getTitle(), news.getContent());
}
}
});
return holder;
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
News news = mNewsList.get(position);
holder.newsTitleText.setText(news.getTitle());
}
@Override
public int getItemCount() {
return mNewsList.size();
}
}
需要注意的是,这里把适配器写成内部类是为了直接访问 NewsTitleFragment 的变量
比如:isTowPane
现在还剩最后一步收尾工作,就是向 RecyclerView 中填充数据了
修改 NewsTitleFragment 中的代码,如下所示:
public class NewsTitleFragment extends Fragment{
. . .
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.news_title_frag, container, false);
//RecyclerView实例
RecyclerView newsTitleRecyclerView = (RecyclerView) view.findViewById(R.id.news_title_recycler_view);
LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity());
newsTitleRecyclerView.setLayoutManager(layoutManager);//指定布局为线性布局
NewsAdapter adapter = new NewsAdapter(getNews());//把模拟新闻数据传入到NewsAdapter构造函数中
newsTitleRecyclerView.setAdapter(adapter);//完成适配器设置
return view;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (getActivity().findViewById(R.id.news_content_layout) != null) {
// 可以找到news_content_layout布局时,为双页模式
isTwoPane = true;
} else {
// 找不到news_content_layout布局时,为单页模式
isTwoPane = false;
}
}
/**
* 初始化50条模拟新闻数据
* @return
*/
private List<News> getNews() {
//创建集合
List<News> newsList = new ArrayList<>();
//实例化数据
for (int i = 1; i <= 50; i++) {
News news = new News();
news.setTitle("标题" + i);
news.setContent(getRandomLengthContent("东营职业学院电子信息与传媒学院" + i + ". "));
newsList.add(news);
}
return newsList;
}
/**
* 随机生成不同长度的新闻内容
* @param content
* @return
*/
private String getRandomLengthContent(String content) {
Random random = new Random();
int length = random.nextInt(20) + 1;
StringBuilder builder = new StringBuilder();
for (int i = 0; i < length; i++) {
builder.append(content);
}
return builder.toString();
}
. . .
}
下面是双页模式
首先看过前几页第一行代码的敲友应该可以知道,在res下面新建layout-sw600dp文件夹系统就在屏幕分辨率大于600的时候自动选择该文件夹下的文件,在文件夹下面新建activity_main文件。
<?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="horizontal">
<fragment
android:id="@+id/news_title_fragment"
android:name="com.yiyajing.mypremission.fragment.NewsTitleFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
<FrameLayout
android:id="@+id/news_content_layout"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="3">
<fragment
android:id="@+id/news_content_fragment"
android:name="com.yiyajing.mypremission.fragment.NewsContentFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
</LinearLayout>
可以看出,在双页模式下我们同时引入了两个碎片,并将新闻内容的碎片放在了一个FrameLayout布局下而这个布局的id正是news_content_layout,因此,能找到这个id就是双页模式,否则就是单页模式,双页模式情况下,系统会自动选择该布局。