Android中Fragment的最佳实践—简易版的新闻应用

Android中Fragment的最佳实践—简易版的新闻应用

现在你已经将关于碎片的重要知识点都掌握得差不多了,不过在灵活运用方面可能还有些欠缺,因此下面该进入我们本章的最佳实践环节了。

前面有提到过,碎片很多时候都是在平板开发当中使用的,主要是为了解决屏幕空间不能充分利用的问题。那是不是就表明,我们开发的程序都需要提供一个手机版和一个Pad版呢?确实有不少公司都是这么做的,但是这样会浪费很多的人力物力。因为维护两个版本的代码成本很高,每当增加什么新功能时,需要在两份代码里各写一遍,每当发现一个bug时,需要在两份代码里各修改一次。因此今天我们最佳实践的内容就是,教你如何编写同时兼容手机和平板的应用程序

还记得在本章开始的时候提到过的一个新闻应用吗?现在我们就将运用本章中所学的知识来编写一个简易版的新闻应用,并且要求它是可以同时兼容手机和平板的。新建好一个FragmentBestPractice项目,然后开始动手吧!

app/build.gradle当中添加依赖库

由于待会在编写新闻列表时会使用到RecyclerView,因此首先需要在app/build.gradle当中添加依赖库,如下所示:

dependencies {
    implementation 'androidx.appcompat:appcompat:1.4.1'
    implementation 'com.google.android.material:material:1.5.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}

新建新闻实体类News

接下来我们要准备好一个新闻的实体类,新建类News,代码如下所示:

public class News {
private String title;
private 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

News类的代码还是比较简单的,title字段表示新闻标题,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>

新闻内容的布局主要可以分为两个部分,头部部分显示新闻标题,正文部分显示新闻内容,中间使用一条细线分隔开。这里的细线是利用View来实现的,将View的宽或高设置为1dp,再通过background属性给细线设置一下颜色就可以了。这里我们把细线设置成黑色。

新建NewsContentFragment类

然后再新建一个NewsContentFragment类,继承自Fragment,代码如下所示:

public class NewsContentFragment extends Fragment {
private View view;
@Override
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);
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()方法,这个方法就是用于将新闻的标题和内容显示在界面上的。可以看到,这里通过findViewById()方法分别获取到新闻标题和内容的控件,然后将方法传递进来的参数设置进去。

单页模式需新建NewsContentActivity

这样我们就把新闻内容的碎片和布局都创建好了,但是它们都是在双页模式中使用的,如果想在单页模式中使用的话,我们还需要再创建一个活动。右击com.example.fragmentbestpractice包→New→Activity→Empty Activity,新建一个NewsContentActivity,并将布局名指定成news_content,然后修改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:name="com.example.fragmentbestpractice.NewsContentFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</LinearLayout>

这里我们充分发挥了代码的复用性,直接在布局中引入NewsContentFragment,这样也就相当于把news_content_frag布局的内容自动加了进来。

然后修改NewsContentActivity中的代码,如下所示:

public class NewsContentActivity extends AppCompatActivity {
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)
getSupportFragmentManager().findFragmentById(R.id.news_content_fragment);
newsContentFragment.refresh(newsTitle, newsContent); //刷新NewsContentFragment界面
}
}

可以看到,在onCreate()方法中我们通过Intent获取到了传入的新闻标题和新闻内容,然后调用FragmentManager的findFragmentById()方法得到了NewsContentFragment的实例,接着调用它的refresh()方法,并将新闻的标题和内容传入,就可以把这些数据显示出来了。注意这里我们还提供了一个actionStart()方法,启动活动的最佳写法。

新建news_title_frag.xml

接下来还需要再创建一个用于显示新闻列表的布局,新建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">

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

</LinearLayout>

这个布局的代码就非常简单了,里面只有一个用于显示新闻列表的RecyclerView。

新建news_item.xml

既然要用到RecyclerView,那么就必定少不了子项的布局。新建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:maxLines="true"
android:ellipsize="end"
android:textSize="18sp"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:paddingTop="15dp"
android:paddingBottom="15dp" />

子项的布局也非常简单,只有一个TextView。仔细观察TextView,你会发现其中有几个属性是我们之前没有学过的。android:padding表示给控件的周围加上补白,这样不至于让文本内容会紧靠在边缘上。android:maxLines设置为true表示让这个TextView只能单行显示。android:ellipsize用于设定当文本内容超出控件宽度时,文本的缩略方式,这里指定成end表示在尾部进行缩略。

新建NewsTitleFragment

既然新闻列表和子项的布局都已经创建好了,那么接下来我们就需要一个用于展示新闻列表的地方。这里新建NewsTitleFragment作为展示新闻列表的碎片,代码如下所示:

public class NewsTitleFragment extends Fragment {
private boolean isTwoPane;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.news_title_frag, container, false);
return view;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (getActivity().findViewById(R.id.news_content_layout) != null) {
isTwoPane = true; //可以找到news_content_layout布局时,为双页模式
} else {
isTwoPane = false; //找不到news_content_layout布局时,为单页模式
}
}
}

可以看到,NewsTitleFragment中并没有多少代码,在onCreateView()方法中加载了news_title_frag布局,这个没什么好说的。我们注意看一下onActivityCreated()方法,这个方法通过在活动中能否找到一个id为news_content_layout的View来判断当前是双页模式还是单页模式,因此我们需要让这个id为news_content_layout的View只在双页模式中才会出现。

那么怎样才能实现这个功能呢?其实并不复杂,只需要借助我们刚刚学过的限定符就可以了。

修改activity_main.xml

首先修改activity_main.xml中的代码,如下所示:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/news_title_layout"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<fragment
android:id="@+id/news_title_fragment"
android:name="com.example.fragmentbestpractice.NewsTitleFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</FrameLayout>

上述代码表示,在单页模式下,只会加载一个新闻标题的碎片。

使用限定符新建layout-sw600dp文件夹再新建一个activity_main.xml

然后新建layout-sw600dp文件夹,在这个文件夹下再新建一个activity_main.xml文件,代码如下所示:

<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/news_title_fragment"
android:name="com.example.fragmentbestpractice.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.example.fragmentbestpractice.NewsContentFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
</LinearLayout>

可以看出,在双页模式下我们同时引入了两个碎片,并将新闻内容的碎片放在了一个FrameLayout布局下,而这个布局的id正是news_content_layout。因此,能够找到这个id的时候就是双页模式,否则就是单面模式。

现在我们已经将绝大部分的工作都完成了,但还剩下至关重要的一点,就是

在NewsTitleFragment中通过RecyclerView将新闻列表展示出来。我们

新建一个内部类NewsAdapter

在NewsTitleFragment中新建一个内部类NewsAdapter来作为RecyclerView的适配器,如下所示:

public class NewsTitleFragment extends Fragment {
private boolean isTwoPane;
...
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);
final ViewHolder holder = new ViewHolder(view);
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
News news = mNewsList.get(holder.getAdapterPosition());
if (isTwoPane) {
//如果是双页模式,则刷新NewsContentFragment中的内容
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();
}
}
}

RecyclerView的用法你已经相当熟练了,因此这个适配器的代码对你来说应该没有什么难度吧?需要注意的是,之前我们都是将适配器写成一个独立的类,其实也是可以写成内部类的,这里写成内部类的好处就是可以直接访问NewsTitleFragment的变量,比如isTwoPane。

观察一下onCreateViewHolder()方法中注册的点击事件,首先获取到了点击项的News实例,然后通过isTwoPane变量来判断当前是单页还是双页模式,如果是单页模式,就启动一个新的活动去显示新闻内容,如果是双页模式,就更新新闻内容碎片里的数据。

向RecyclerView中填充数据,修改NewsTitleFragment

现在还剩最后一步收尾工作,就是向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 newsTitleRecyclerView = (RecyclerView) view.findViewById
(R.id.news_title_recycler_view);
LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity());
newsTitleRecyclerView.setLayoutManager(layoutManager);
NewsAdapter adapter = new NewsAdapter(getNews());
newsTitleRecyclerView.setAdapter(adapter);
return view;
}
private List<News> getNews() {
List<News> newsList = new ArrayList<>();
for (int i = 1; i <= 50; i++) {
News news = new News();
news.setTitle("This is news title " + i);
news.setContent(getRandomLengthContent("This is news content " + i + ". "));
newsList.add(news);
}
return newsList;
}
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();
}
...
}

可以看到,onCreateView()方法中添加了RecyclerView标准的使用方法,在碎片中使用RecyclerView和在活动中使用几乎是一模一样的,相信没有什么需要解释的。另外,这里调用了getNews()方法来初始化50条模拟新闻数据,同样使用了一

个getRandomLengthContent()方法来随机生成新闻内容的长度,以保证每条新闻的内容差距比较大,相信你对这个方法肯定不会陌生了。

这样我们所有的编写工作就已经完成了,赶快来运行一下吧!首先在手机模拟器上运行,效果如图所示:

在这里插入图片描述

可以看到许多条新闻的标题,然后点击第一条新闻,会启动一个新的活动来显示新闻的内容。

接下来将程序在平板模拟器上运行,同样点击第一条新闻,效果如图所示:

在这里插入图片描述

怎么样?同样的一份代码,在手机和平板上运行却分别是两种完全不同的效果,说明我们程序的兼容性已经相当不错了。通过这个例子,我相信你对碎片的理解一定又加深了很多。

如果对你有帮助,就一键三连呗(关注+点赞+收藏),我会持续更新更多干货~~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序猿陌名!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值