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 文件。

posted @ 2021-02-20 11:15  MElephant  阅读(884)  评论(0编辑  收藏  举报