网络编程

目标

通过网络接口获得新闻列表的json数据,将新闻列表展示在一个界面中

新闻列表实现的界面如下

接口http://106.12.97.199:8080/news_list_data.json
返回:新闻列表json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
[
{
"id":"cb86cdcd922fe4295d5bd134f82090ac",
"title":"索帅回忆弗格森当年奇怪表现 首回合输了却很高兴",
"havePic":true,
"desc":null,
"link":"https://sports.sina.com.cn/g/pl/2021-04-17/doc-ikmxzfmk7319050.shtml",
"source":"新浪",
"channelId":"5572a10ab3cdc86cf39001e7",
"channelName":"国际足球最新",
"pubDate":"2021-04-17 10:32:00",
"content":null,
"coverPic":"https://n.sinaimg.cn/sports/transform/69/w510h359/20210417/fb6c-knvsnuf8997548.jpg"
},

{
"id":"373819191b26ce4e12670d18d84bf28e",
"title":"日美首脑会谈强调同盟关系 菅义伟再展办奥决心",
"havePic":true,
"desc":"中新网4月17日电 综合报道,当地时间16日,美国总统拜登在白宫与来访的日本首相菅义伟进行会谈,并举行了联合记者会。这是拜登自上任以来,首次在白宫同外国首脑举行面对面会晤。",
"link":"http://www.chinanews.com/gj/2021/04-17/9456924.shtml",
"source":"中国新闻网",
"channelId":"5572a109b3cdc86cf39001db",
"channelName":"国内最新",
"pubDate":"2021-04-17 10:28:56",
"content":"中新网4月17日电 综合报道,当地时间16日,美国总统拜登在白宫与来访的日本首相菅义伟进行会谈,并举行了联合记者会。这是拜登自上任以来,首次在白宫同外国首脑举行面对面会晤。",
"coverPic":"http://image1.chinanews.com.cn/cnsupload/big/2021/03-26/4-426/75b9db5df3f64d2d921615127def8e2c.jpg"
}
]

实现过程

布局文件

新闻界面的xml文件,使用RecyclerView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
tools:context=".ui.course.courseFragment">

<androidx.recyclerview.widget.RecyclerView
android:id="@+id/NewsRecyclerView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@+id/news_title"
tools:layout_editor_absoluteX="1dp"
tools:layout_editor_absoluteY="1dp" />

<TextView
android:id="@+id/news_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="新闻"
android:gravity="center"
android:textColor="@color/white"
android:textSize="20sp"
android:background="@color/blue"
android:padding="20dp"
app:layout_constraintTop_toTopOf="parent"
tools:layout_editor_absoluteX="158dp"
tools:layout_editor_absoluteY="5dp" />

</androidx.constraintlayout.widget.ConstraintLayout>

每一个列表项目的xml文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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">


<TextView
android:id="@+id/news_title"
android:layout_width="274dp"
android:layout_height="36dp"
android:text="第一章"
android:textColor="@color/black"
android:textSize="20sp"
android:textStyle="bold"
android:gravity="left"
android:maxLines="1"
android:ellipsize="end"
app:layout_constraintTop_toTopOf="@+id/new_pic"
app:layout_constraintLeft_toRightOf="@+id/new_pic"
app:layout_constraintRight_toRightOf="parent"
android:layout_marginLeft="20dp"
tools:layout_editor_absoluteX="126dp"
tools:layout_editor_absoluteY="17dp" />

<ImageView
android:id="@+id/new_pic"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="20dp"
android:layout_marginBottom="30dp"
app:srcCompat="@drawable/default_icon"
tools:layout_editor_absoluteX="18dp"
tools:layout_editor_absoluteY="17dp" />

<TextView
android:id="@+id/news_date"
android:layout_width="174dp"
android:layout_height="wrap_content"
android:text="2021-04-17 10:28:56"
android:textSize="15sp"
app:layout_constraintLeft_toLeftOf="@+id/news_title"
app:layout_constraintBottom_toBottomOf="@+id/new_pic" />

<TextView
android:id="@+id/new_source"
android:layout_width="104dp"
android:layout_height="22dp"
android:textSize="15sp"
android:text="中国新闻网"
android:gravity="right"
app:layout_constraintTop_toTopOf="@+id/news_date"
app:layout_constraintRight_toRightOf="@+id/news_title"
tools:layout_editor_absoluteX="288dp"
tools:layout_editor_absoluteY="68dp" />

<TextView
android:id="@+id/itemline"
android:layout_width="match_parent"
android:layout_height="1dp"
android:text=""
android:layout_marginTop="20dp"
android:layout_marginBottom="20dp"
app:layout_constraintTop_toBottomOf="@+id/new_pic"
tools:layout_editor_absoluteX="22dp"
tools:layout_editor_absoluteY="153dp" />

</androidx.constraintlayout.widget.ConstraintLayout>

Java代码

在总的Activity中使用碎片Fragment,网络请求使用了okhttp,新闻页面的Fragment代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
package com.example.mytest.ui.course;

import android.app.DownloadManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import com.example.mytest.R;
import com.example.mytest.data.model.Item;
import com.example.mytest.data.model.NewsItem;
import com.example.mytest.databinding.FragmentHomeBinding;
import com.example.mytest.ui.Constant;
import com.example.mytest.ui.JsonParse;
import com.example.mytest.ui.MyAdapter;
import com.example.mytest.ui.NewsAdapter;

import java.io.IOException;
import java.util.ArrayList;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

public class courseFragment extends Fragment {

private View view;//定义view用来设置fragment的layout
public RecyclerView NewsRecyclerView;//定义RecyclerView
private ArrayList<NewsItem> NewsList = new ArrayList<NewsItem>();
//自定义recyclerveiw的适配器
private NewsAdapter newsRecyclerAdapter;
public static final int Msg_NEWS_OK = 1;
private MHandler mHandler;

@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
view = inflater.inflate(R.layout.fragment_home, container, false);
mHandler = new MHandler();
//对recycleview进行初始化配置
initRecyclerView();
//初始化数据
initData();
return view;
}

/**
* TODO 初始化数据
*/
private void initData() {
OkHttpClient okHttpClient = new OkHttpClient();
Request request = new Request.Builder().url(Constant.WEB_SITE+Constant.NEWS_URL).build();
Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}

@Override
public void onResponse(Call call, Response response) throws IOException {
String res = response.body().string();
Message msg = new Message();
msg.what = Msg_NEWS_OK;
msg.obj = res;
mHandler.sendMessage(msg);
}
});

}
class MHandler extends Handler {
public void dispatchMessage(Message msg){
super.dispatchMessage(msg);
switch (msg.what) {
case Msg_NEWS_OK:
if(msg.obj!=null){
String vlResult = (String) msg.obj;
ArrayList<NewsItem> pythonList = JsonParse.getInstance().getNewsList(vlResult);
newsRecyclerAdapter.setData(pythonList);
}
break;
}
}
}

/**
* TODO 对recycleview进行初始化配置
*/

private void initRecyclerView() {
//获取RecyclerView
NewsRecyclerView=(RecyclerView)view.findViewById(R.id.NewsRecyclerView);
//创建adapter
newsRecyclerAdapter = new NewsAdapter(getActivity(), NewsList);
//给RecyclerView设置adapter
NewsRecyclerView.setAdapter(newsRecyclerAdapter);
NewsRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false));
NewsRecyclerView.addItemDecoration(new DividerItemDecoration(getActivity(),DividerItemDecoration.VERTICAL));
}
}

为新闻列表中的RecyclerView设置适配器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
package com.example.mytest.ui;

import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;


import com.bumptech.glide.Glide;
import com.example.mytest.R;
import com.example.mytest.data.model.Item;
import com.example.mytest.data.model.NewsItem;

import java.util.ArrayList;

public class NewsAdapter extends RecyclerView.Adapter<NewsAdapter.NewsViewHodler> {

private Context context;
private ArrayList<NewsItem> NewsList;

public NewsAdapter(Context context, ArrayList<NewsItem> ItemList) {
//将传递过来的数据,赋值给本地变量
this.context = context;//上下文
this.NewsList = ItemList;//实体类数据ArrayList
}

@NonNull
@Override
public NewsViewHodler onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View itemView = View.inflate(context, R.layout.news, null);
return new NewsViewHodler(itemView);
}

@Override
public void onBindViewHolder(NewsViewHodler holder, int position) {
NewsItem data = NewsList.get(position);
holder.title.setText(data.getTitle());
holder.date.setText(data.getPubDate());
holder.source.setText(data.getSource());
Glide.with(context)
.load(data.getCoverPic())
.error(R.mipmap.ic_launcher)
.into(holder.pic);
}

public void setData(ArrayList<NewsItem> List){
this.NewsList=List;
notifyDataSetChanged();
}

@Override
public int getItemCount() {
return NewsList.size();
}

class NewsViewHodler extends RecyclerView.ViewHolder {
private ImageView pic;
private TextView title;
private TextView date;
private TextView source;
public NewsViewHodler(View itemView) {
super(itemView);
title = (TextView) itemView.findViewById(R.id.news_title);
date = (TextView) itemView.findViewById(R.id.news_date);
source = (TextView) itemView.findViewById(R.id.new_source);
pic = (ImageView) itemView.findViewById(R.id.new_pic);
//点击事件放在adapter中使用,也可以写个接口在activity中调用
//方法一:在adapter中设置点击事件
itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//可以选择直接在本位置直接写业务处理
//Toast.makeText(context,"点击了xxx",Toast.LENGTH_SHORT).show()
}
});

}
}
}

json数据的获取和处理使用了Gson

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.example.mytest.ui;

import com.example.mytest.data.model.NewsItem;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;

import java.lang.reflect.Type;
import java.util.ArrayList;

public class JsonParse {
private static JsonParse instance;
private JsonParse(){}
public static JsonParse getInstance(){
if(instance == null){
instance = new JsonParse();
}
return instance;
}
public ArrayList<NewsItem> getNewsList(String json){
Gson gson = new Gson();
Type listType = new TypeToken<ArrayList<NewsItem>>(){}.getType();
ArrayList<NewsItem> NewsList = gson.fromJson(json,listType);
return NewsList;
}
}

单个新闻项目对应的实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
package com.example.mytest.data.model;

public class NewsItem {
private String id; //新闻Id
private String title; //新闻标题
private Boolean havePic;//是否有图片
private String desc; //新闻简述
private String link; //原文地址
private String source; //新闻出处
private String channelId; //频道
private String channelName; //频道名字
private String pubDate; //发布时间
private String content; //新闻内容
private String coverPic; //封面图片

public Boolean getHavePic() {
return havePic;
}

public String getChannelId() {
return channelId;
}

public String getChannelName() {
return channelName;
}

public String getContent() {
return content;
}

public String getCoverPic() {
return coverPic;
}

public String getDesc() {
return desc;
}

public String getId() {
return id;
}

public String getLink() {
return link;
}

public String getPubDate() {
return pubDate;
}

public String getSource() {
return source;
}

public String getTitle() {
return title;
}

public void setChannelId(String channelId) {
this.channelId = channelId;
}

public void setChannelName(String channelName) {
this.channelName = channelName;
}

public void setContent(String content) {
this.content = content;
}

public void setDesc(String desc) {
this.desc = desc;
}

public void setCoverPic(String coverPic) {
this.coverPic = coverPic;
}

public void setHavePic(Boolean havePic) {
this.havePic = havePic;
}

public void setId(String id) {
this.id = id;
}

public void setLink(String link) {
this.link = link;
}

public void setPubDate(String pubDate) {
this.pubDate = pubDate;
}

public void setSource(String source) {
this.source = source;
}

public void setTitle(String title) {
this.title = title;
}
}

静态常量

1
2
3
4
5
6
7
package com.example.mytest.ui;

public class Constant {
public static final String WEB_SITE = "http://106.12.97.199:8080";
public static final String NEWS_URL = "/news_list_data.json";
}

项目地址

github项目地址