Android学习之简易版的新闻应用
•准备工作
新建一个项目,命名为 FragmentBestProject,并选择 Empty Activity;
并将项目的模式结构改为 Project 模式;
•进入主题
首先,准备好一个新闻实体类,新建类 News;
News.java
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; } }title 表示新闻标题,content 表示新闻内容;
接着新建布局文件 news_content_frag.xml,用于作为新闻内容的布局;
news_content_frag.xml
<?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/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="@color/black"/> <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="@color/black" /> </RelativeLayout>新闻的内容部分主要可以分为两个部分:
- 头部部分显示新闻标题
- 正文部分显示新闻内容
- 中间使用一条细线分隔开(利用 View 来实现)
- 左侧一条竖线区分标题列表(利用 View 来实现)
下面提供了双页模式和单页模式的布局示意图,双页和单页模式下文会讲,莫着急;
双页模式示意图:
单页模式示意图:
在新建一个 NewsContentFragment 类,继承自 Fragmet;
NewsContentFragment.java
public class NewsContentFragment extends Fragment { private View view; @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable 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 = view.findViewById(R.id.news_title); TextView newsContentText = view.findViewById(R.id.news_content); newsTitleText.setText(newsTitle);//刷新新闻的标题 newsContentText.setText(newsContent);//刷新新闻的内容 } }首先在 onCreateView() 方法里加载了我们刚刚创建的 news_content_frag 布局;
接下来又提供了一个 refresh() 方法;
这个方法就是用于将新闻的标题和内容显示在界面上;
这里通过 view.findViewById() 方法分别获取新闻的标题和内容控件,并通过 setText() 将传递的参数设置上;
到目前为止,我们就把新闻内容的碎片和布局都创建好了;
但上述布局都是在双页模式中使用的,如果想在单页模式中启动的话,我们还需要在创建一个活动;
新建一个 Empty Activity,命名为 NewsContentActivity,并将布局名指定为 news_content;
修改 news_content.xml 中的代码;
news_content.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"> <fragment android:id="@+id/news_content_fragment" android:name="com.example.fragmentbestproject.NewsContentFragment" android:layout_width="match_parent" android:layout_height="match_parent"/> </LinearLayout>此处代码充分发挥了代码的复用性,直接在布局中通过 android:name 属性引入 NewsContentFragment;
这样也就相当于把 news_content_frag 布局的内容自动添加进来;
接下来修改 NewsContentActivity.java 中的代码;
NewsContentActivity.java
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"); //获取news_content中的fragment NewsContentFragment newsContentFrgment = (NewsContentFragment) getSupportFragmentManager() .findFragmentById(R.id.news_content_fragment); newsContentFrgment.refresh(newsTitle,newsContent);//刷新NewsContentFragment界面 } }可以看到,在 onCreate() 方法中,我们通过 Intent 获取到了传入的新闻标题和内容;
然后调用 FragmentManager 的 findFragmentById() 方法得到了 NewsContentFragment 实例;
接着调用它的 refresh() 方法,并将新闻的标题和内容传入,就可以把这些数据显示出来了;
这里我们还提供了一个 actionStart() 方法,有关该方法的使用,可以参考我的这篇博客🔗;
接下来创建一个用于显示新闻列表的布局,新建 news_title_frag.xml;
news_title_frag.xml
<?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"> <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
<?xml version="1.0" encoding="utf-8"?> <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:paddingLeft="10dp" android:paddingRight="10dp" android:paddingTop="15dp" android:paddingBottom="15dp" />子项布局的代码也非常简单,只有一个 TextView。
代码进行到这,新闻列表和子项布局都已经创建好了;
接下来我们就需要一个用于展示新闻列表的地方;
新建一个 NewsTitleFragment 类作为展示新闻列表的碎片;
NewsTitleFragment.java
public class NewsTitleFragment extends Fragment { private boolean isTwoPane; @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.news_title_frag,container,false); return view; } @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); //通过判断是否可以找到 news_content_layout 布局来判定是单页模式还是双页模式 if(getActivity().findViewById(R.id.news_content_layout) != null){ isTwoPane = true;//找到->双页模式 } else isTwoPane = false;//未找到->单页模式 } }在 onCreateView() 方法中加载了 news_title_frag 布局;
在 onActivityCreated() 方法中,通过在活动中能否找到 news_content_layout 布局来判定是什么模式;
屏幕前的你是不是在往上扒拉代码,看看 news_content_layout 在哪个文件里?
其实,他还未出现,在下面代码里呢;
修改 activity_main.xml 中的代码;
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <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.fragmentbestproject.NewsTitleFragment" android:layout_width="match_parent" android:layout_height="match_parent" /> </FrameLayout>该代码表示,在单页模式下,只会加载一个新闻标题的碎片;
然后,新建 layout-sw600dp 文件夹,在这个文件夹下新建一个 activity_main.xml 文件;
sw600dp\activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <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.fragmentbestproject.NewsTitleFragment" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" /> <LinearLayout 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.fragmentbestproject.NewsContentFragment" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout> </LinearLayout>可以看出,在双页模式下我们同时引入了两个碎片,并将新闻内容的碎片放在了 LinearLayout 布局下;
注意看这个 <LinearLayout> 布局的 id,你发现了什么?
现在我们已经将绝大部分工作都完成了,但还剩下至关重要的一点;
就是在 NewsTitleFragment 中通过 RecyclerView 将新闻展示出来;
现在,我们在 NewsTitleFragment.java 中新建一个内部类 NewsAdapter 来作为 RecyclerView 的适配器;
NewsTitleFragment.java
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(@NonNull View view) { super(view); newsTitleText = view.findViewById(R.id.news_title); } } public NewsAdapter(List<News> list){ mNewsList = list; } @NonNull @Override public ViewHolder onCreateViewHolder(@NonNull 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()); //通过 isTwoPane 的取值判断是单页模式还是双页模式 if(isTwoPane){ /** * isTwoPane == true * 双页模式,刷新 NewsContentFragment 中的内容 */ NewsContentFragment newsContentFragment = (NewsContentFragment) getFragmentManager() .findFragmentById(R.id.news_content_fragment); newsContentFragment.refresh(news.getTitle(),news.getContent()); } else{ /** * isTwoPane == false * 单页模式,直接启动 NewsContentActivity */ NewsContentActivity.actionStart(getActivity(),news.getTitle(),news.getContent()); } } }); return holder; } @Override public void onBindViewHolder(@NonNull ViewHolder holder, int position) { News news = mNewsList.get(position); holder.newsTitleText.setText(news.getTitle()); } @Override public int getItemCount() { return mNewsList.size(); } } }需要注意的是,之前都是将适配器写成一个独立的类,而这次写成了内部类;
本次写成内部类的好处就是可以直接访问 isTwoPlane ;
观察 onCreateViewHolder() 方法中注册的点击事件;
首先获取到了被点击项的 News 实例,然后通过 isTwoPlane 判断模式;
如果是单页模式,就启动一个新的活动显示新闻内容;
如果是双页模式,就更新新闻内容碎片里的数据;
到目前为止,此次项目接近尾声,仅剩下最后一步的收尾工作——向 RecyclerView 中填充数据;
修改 NewsTitleFragment.java 中的代码;
public class NewsTitleFragment extends Fragment { private boolean isTwoPane; @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.news_title_frag,container,false); RecyclerView rv = view.findViewById(R.id.news_title_recycler_view); LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity()); rv.setLayoutManager(layoutManager); NewsAdapter adapter = new NewsAdapter(getNews()); rv.setAdapter(adapter); return view; } @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); //通过判断是否可以找到 news_content_layout 布局来判定是单页模式还是双页模式 if(getActivity().findViewById(R.id.news_content_layout) != null){ isTwoPane = true;//找到->双页模式 } else isTwoPane = false;//未找到->单页模式 } class NewsAdapter extends RecyclerView.Adapter<NewsAdapter.ViewHolder>{ private List<News> mNewsList; class ViewHolder extends RecyclerView.ViewHolder{ TextView newsTitleText; public ViewHolder(@NonNull View view) { super(view); newsTitleText = view.findViewById(R.id.news_title); } } public NewsAdapter(List<News> list){ mNewsList = list; } @NonNull @Override public ViewHolder onCreateViewHolder(@NonNull 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()); //通过 isTwoPane 的取值判断是单页模式还是双页模式 if(isTwoPane){ /** * isTwoPane == true * 双页模式,刷新 NewsContentFragment 中的内容 */ NewsContentFragment newsContentFragment = (NewsContentFragment) getFragmentManager() .findFragmentById(R.id.news_content_fragment); newsContentFragment.refresh(news.getTitle(),news.getContent()); } else{ /** * isTwoPane == false * 单页模式,直接启动 NewsContentActivity */ NewsContentActivity.actionStart(getActivity(),news.getTitle(),news.getContent()); } } }); return holder; } @Override public void onBindViewHolder(@NonNull ViewHolder holder, int position) { News news = mNewsList.get(position); holder.newsTitleText.setText(news.getTitle()); } @Override public int getItemCount() { return mNewsList.size(); } } private List<News> getNews(){ List<News> list = new ArrayList<>(); for(int i = 1;i <= 10;i++){ News news = new News(); news.setTitle("title "+i); news.setContent("this is news content "+i+"."); list.add(news); } return list; } }这样,我们所有的编写工作就已经完成了,快来试一下吧!
运行结果
Tablet
Phone
•小结
最后,让我们站在上帝视角总结一下这次的设计;
首先,新建一个包含 Empty Activity 的项目,命名为 FragmentBestProject;
为了能实现在手机上单页显示,在平板上双页显示,又新建了一个 layout-sw600dp 文件夹;
并在这个文件夹里新建了一个 activity_main.xml;
接下来该为界面设计标题和内容布局了;
考虑到一点,不论是单页模式,还是双页模式,都会有新闻标题的身影;
所以,我们新建 news_title_frag.xml,并配套新建了 NewsTitleFragment 用来显示该布局;
新建完显示 title 的文件,接下来该新建显示 content 的文件了;
新建 news_content_frag.xml,并配套新建了 NewsContentFragment 用来显示该布局;
双页模式下点击 title 直接在右边显示相应的 content;
而单页模式需要为 title 设置相应的点击跳转事件;
点击 title 开启对应的新的活动;
新建一个 Empty Activity,设置类名为 NewsContentActivity,布局文件名为 news_content;
这个新建的活动代表点击 title 跳转的新活动;
到这,有趣的图解式就结束了,接下来就是为页面添加数据了;
枯燥无味的东西不想再写第二遍了,看上面的 Adapter 就可以了;
画图不易,码字不易,求多支持,最后附上该图的 .psd 文件。