本文为Karthus77原创,转载请说明
文章目录
- 【小白做项目-02】
- 一、项目需求
- 二、效果展示
- 三、项目分析
- 四、实战操作
- 1.项目框架搭建
- 1.1 Activity搭建
- 1.2 添加网络访问权限
- 1.3 添加依赖
- 2.UI界面搭建
- 2.1界面部分
- 2.2状态栏透明效果设置
- 3.Recyclerview使用
- 3.1 Recyclerview工作原理
- 3.2 布局文件中Recyclerview
- 3.3 创建item布局
- 3.4 创建Adapter
- 4.网络访问获取数据
- 4.1 Calender日历获取时间
- 4.2 发送网络请求
- 4.3 Json数据解析与Map,list储存
- 4.4 Adapter适配与多item
- 5.刷新与加载
- 6.WebView的使用
- 7.评论展示与glide工具>
- 8.断网处理
- 五、总结
仿照知乎日报这个项目,是一个较为复杂的APP项目
该项目的核心是“数据获取”到“数据展示”的一个过程
核心知识点如下:
- Recyclerview多item
- Recyclview使用
- Json数据解析
- webview使用
- smartrefresh的使用
- 网络访问获取数据
- glide工具加载图片
该项目需要3个活动页面
- 主页面(展示新闻标题)
- 内容页面(展示具体新闻内容)
- 评论页面(展示用户评论)
AndroidManifest是对应Android应用的一个配置文件,例如我在该文件中加入了联网许可,这个APP就可以联网。 首先,什么是依赖,为什么要去添加它呢? 由于UI界面参考“知乎日报APP”我们只需要模仿即可 我们只需要做出图片中红线包裹的UI即可 我们可以看到红框部分状态栏是图片背景的底色 什么是Recyclerview呢?它有什么用呢? 我们说Recyclerview是一个控件,也就是说它是类似与Textview,Editview的一种工具。我们在Activity中对Textview等可以进行一系列的操作。同样,我们在Activity对Recyclerview进行操作。 Recyclerview既然要以一个形式重复展示多组数据(item),所以Recyclerview控件的本质功能就是“数据接收”到“数据展示”的一个工具。 在xml文件中加入Recyclerview 由于需要刷新和加载功能,我们提前在Recyclerview中包裹我们的smartfresh 新建Java Class 并命名为MyAdapater用于新闻类展示 我们在前面已经为项目添加了网络访问的权限 利用calender获取当天的时间和早中晚来显示红框部分 代码如下 我们已经新建了一个子线程,在这个子线程中进行联网操作 在这个过程中,url对应的是接口数据网站,setRequestMedthod设置访问方式为get,利用readline函数以行方式读取所有数据,读完以后,所有数据已经存贮在response当中 Json是一种易于理解的数据储存形式,有着广泛的应用,知乎日报官方Api返回我们的是Json数据,因此我们需要将他解析。 我们用火狐浏览器打开接口网站,火狐浏览器会自动帮我们解析,便于理解 runOnUiThread是返回主线程的操作,由于UI操作只能在主线程中操作,而我们的联网操作是在子线程中进行的操作,而我们需要将Json数据用Recyclerview的形式展示是UI操作所以需要返回UI线程,也就是主线程 数据储存在list当中,并利用map键值对的方式方便我们在Adpater中数据的获取 在Activity中就是这两行代码连接了Recyclerview和Adapter 在这段代码中,我们利用getItemViewtype获得需要展示的视图 我们可以看到这里有两种不同的item,所以我们的Recyclerview还要能够适配多种item形式,getItemViewType就是获取需要的Item我们以每条list中的大小(有几个map键值对)来判断对应的item种类 在onCreatViewHolder中绑定对应的item 在onBindViewHolder中对需要用到的每一种item进行操作 创建内部类绑定每一种布局 刷新与加载,听起来就是很高级的功能啊。 这段代码用于刷新事件的展开 这段代码用于加载事件,下面给出加载部分的完整代码 顾名思义,webview即网页视图的意思 仅仅三行代码即可,其中的url是对应该文章的url地址 glide的使用同样需要添加依赖,我们已经在第一步的时候添加了glide的依赖,那么glide怎么使用呢 glide的使用只有一行代码with部分是指当前的Activity,也就是context,load部分的get("image“),image是指所要加载图片的url地址 用上面的代码即可实现 我们的APP是需要联网的,但是万一你在没有网的情况下打开它会怎么样。 总的来说,这是一个真正意义上的,可以说是一个成熟APP,作为练手项目,我很推荐,它有一定的难度,能够充分考验一个项目整体的架构能力。当然,整篇教程我在许多细节方面没有详细解答,因为我认为需要留有一部分自己思考。我认为能够独立做完这个项目,那么一定会对你的Android开发有着很大的提升。 该项目的源代码和接口可以私信我即可,当然我可不是教大家做盗版软件啊,纯当小白自己练习。 下一个项目是一个淘宝类的项目,有个人用户系统,可以上架购买商品,有兴趣的小伙伴不妨关注一波~
在AndroidManifest.xml文件中加入以下两行代码。
并在 android:usesCleartextTraffic="true"
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
1.3 添加依赖
简单的来说,依赖是一个程序,是一堆代码,这段程序或代码可以帮助我们解决很多问题。比如我们在写C语言程序是,要include头文件,头文件中就已经为我们写好了很多函数的定义。依赖就是如此,Android Studio内部没有实现的功能,我们以依赖方式添加,就可以实现依赖中的功能。
需要说明的是,依赖虽好,可不要频繁使用,因为依赖是别人写出来的,内部的实现功能你了解的并不清晰,并不理解,此种利弊,需要自己好好考量
在build.gradle中加入以下依赖(glide图片加载工具,smartrefresh刷新工具,recyclerview视图工具) implementation 'com.github.bumptech.glide:glide:4.11.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.2'
implementation 'com.scwang.smartrefresh:SmartRefreshHeader:1.1.2'
2.UI界面搭建
2.1界面部分
2.2状态栏透明效果设置
设置方法,在xml文件对应的Activity的onCreate周期后面加入以下代码即可实现该效果 if (Build.VERSION.SDK_INT >= 21){
View decorView = getWindow().getDecorView();
decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
getWindow().setStatusBarColor(Color.TRANSPARENT);
}
3.Recyclerview使用
通俗的话,Recyclerview就是一个强大的控件,用于重复展示某一类东西。展示的每一个东西叫做item
如图中所示,每一个新闻就是Recyclerview中的一个item。同样在我们QQ聊天为微信聊天的地方,每一个联系人的小窗就是一个item,有了Recyclerview我们就可以做出知乎日报中新闻的效果了3.1 Recyclerview工作原理
那么如何进行数据的接受呢?数据的接受必须以一定的形式存贮起来,我们知道数据存贮的方式有数组,有链表,有结构体。而数据的展示就要用到Adapter适配器,即根据数据适配对应的展示形式。
Adapter能够将Recyclerview和Activity联系在一起,来展示信息。3.2 布局文件中Recyclerview
<com.scwang.smartrefresh.layout.SmartRefreshLayout
android:id="@+id/refreshLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
com.scwang.smartrefresh.layout.SmartRefreshLayout>
3.3 创建item布局
在 layout文件中新建一个layout XML file 文件用于写出每一条新闻文件
分析每一条新闻布局
可以看出每一个item有标题,小标题和图片三部分
这里给出该item的xml的代码
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/paper_item"
android:layout_width="match_parent"
android:layout_height="105dp">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="100dp">
<TextView
android:id="@+id/titleArticle"
android:layout_width="224sp"
android:layout_height="wrap_content"
android:layout_marginLeft="15sp"
android:textSize="16sp"
android:layout_marginTop="35sp"
android:textStyle="bold"
android:text="小事·怀孕,怕吗?"
android:textColor="@color/black">
TextView>
<TextView
android:id="@+id/viceTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12sp"
android:layout_below="@id/titleArticle"
android:layout_marginTop="5sp"
android:text="VOL·1244"
android:layout_marginLeft="15sp">
TextView>
<ImageView
android:id="@+id/imageTitle"
android:layout_width="75sp"
android:layout_height="75sp"
android:layout_alignParentRight="true"
android:layout_marginTop="25sp"
android:layout_marginRight="15sp">
ImageView>
<TextView
android:id="@+id/everyday"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/viceTitle"
android:text=""
android:textSize="10sp"
android:layout_marginLeft="50sp"
android:layout_marginTop="10sp">
TextView>
RelativeLayout>
LinearLayout>
3.4 创建Adapter
Adapater里面的具体代码我们在后面添加,先把它创建好4.网络访问获取数据
下面我们就进行网络访问的操作
首先网络访问需要进行在子线程而不是主线程中
因为网络访问是一个耗时操作
新建子线程 final Thread thread=new Thread(new Runnable()
4.1 Calender日历获取时间
final StringBuilder response = new StringBuilder();
final StringBuilder response1 = new StringBuilder();
String line;
String line1;
Calendar c=Calendar.getInstance();
int t = c.get(Calendar.HOUR_OF_DAY);
if(t>=18)
{
time.setText("晚上好!");
}
else if(t<=8&&t>=6)
{
time.setText("早上好!");
}
else
{
time.setText("知乎日报");
}
int d=c.get(Calendar.DAY_OF_MONTH);
String days=String.format("%d",d);
Date.setText(days);
int months=c.get(Calendar.MONTH);
switch (months+1)
{
case 1:month.setText("一月");
break;
case 2:month.setText("二月");
break;
case 3:month.setText("三月");
break;
case 4:month.setText("四月");
break;
case 5:month.setText("五月");
break;
case 6:month.setText("六月");
break;
case 7:month.setText("七月");
break;
case 8:month.setText("八月");
break;
case 9:month.setText("九月");
break;
case 10:month.setText("十月");
break;
case 11:month.setText("十一月");
break;
default:month.setText("十二月");
4.2 发送网络请求
先建立一个StringBulider获取数据
StringBulider能够将获取的数据转化成字符串储存起来final StringBuilder response = new StringBuilder();
try {
URL url=new URL("https://news-at.zhihu.com/api/3/stories/latest");//获取服务器哦地址
HttpURLConnection urlConnection= (HttpURLConnection) url.openConnection();//双方建立连接
urlConnection.setRequestMethod("GET");//给服务器发送请求
InputStream inputStream=urlConnection.getInputStream(); //字节流
Reader reader=new InputStreamReader(inputStream); //把字节流转化成字符流
BufferedReader bufferedReader=new BufferedReader(reader);//字符流 转成 缓冲流,一次可以读一行
while ((line=bufferedReader.readLine())!=null){
//当temp读到的数据为空就结束
response.append(line);
}
4.3 Json数据解析与Map,list储存
解析Json数据需要理解对象与数组
我们以latest这个接口为例,stories对应了当天的6篇文章的Json数据,也就说stories对应了一个Json数组,这个数组中有6个对象
在每个对象中有文章的大小标题,和对应封面图片的Url
下面给出解析代码runOnUiThread(new Runnable() {
@Override
public void run() {
try {
JSONObject jsonObject = new JSONObject(String.valueOf(response));
date=jsonObject.getInt("date");
int day=date%100;
String days=String.format("%d",day);
JSONArray jsonArray = jsonObject.getJSONArray("stories");
for (int i = 0; i < 6; i++) {
Map<String,Object> map=new HashMap<>();
JSONObject jsonObject1 = (JSONObject) jsonArray.getJSONObject(i);
String title = jsonObject1.getString("title");
String vicetitle = jsonObject1.getString("hint");
String url=jsonObject1.getString("url");
String id=jsonObject1.getString("id");
JSONArray jsonArray1=jsonObject1.getJSONArray("images");
String imageUrl=(String)jsonArray1.get(0);
map.put("url",url);
map.put("id",id);
map.put("image",imageUrl);
map.put("title", title);
map.put("hint",vicetitle);
list.add(map);
}
myAdapter = new MyAdapter(Paper.this,list);
recyclerView.setAdapter(myAdapter);
4.4 Adapter适配与多item
myAdapter = new MyAdapter(Paper.this,list);
recyclerView.setAdapter(myAdapter);
public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>{
private Paper context;
private List<Map<String,Object>> list;
private View inflater;
private AdapterView.OnItemClickListener onItemClickListener;
//构造方法,传入数据
public MyAdapter(Paper context, List<Map<String,Object>> list){
this.context = context;
this.list = list;
}
private static final int news = 0;
private static final int date = 1;
private static final int noNet= 2;
@Override
public int getItemViewType(int position) {
int size=list.get(position).size();
if (size==1)
{
return date;
}
else if(size==2)
{
return noNet;
}
else
{
return news;
}
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
//创建ViewHolder,返回每一项的布局
if(viewType==news)
{
inflater = LayoutInflater.from(context).inflate(R.layout.item,parent,false);
ViewHolder ViewHolder = new ViewHolder(inflater);
return ViewHolder;}
else if(viewType==noNet)
{
inflater=LayoutInflater.from(context).inflate(R.layout.item_nonet,parent,false);
noHolder noHolder=new noHolder(inflater);
return noHolder;
}
else
{
inflater = LayoutInflater.from(context).inflate(R.layout.item_date,parent,false);
dateHolder dateHolder = new dateHolder(inflater);
return dateHolder;
}
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {
//将数据和控件绑定
int viewType=getItemViewType(position);
if(viewType==0)
{
ViewHolder viewholder = (ViewHolder) holder;
String number_title=list.get(position).get("title").toString();
if(number_title.length()>27)
{
String title=number_title.substring(0,27);
viewholder.titlePaper.setText(title+"...");
}
else{
viewholder.titlePaper.setText(list.get(position).get("title").toString());
}
viewholder.titleVice.setText(list.get(position).get("hint").toString());
Glide.with(context).load(list.get(position).get("image")).into(viewholder.titleImage);
viewholder.paper_item.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent=new Intent(context,Context.class);
Bundle bd=new Bundle();
bd.putString("id",list.get(position).get("id").toString());
bd.putString("url",list.get(position).get("url").toString());
intent.putExtras(bd);
context.startActivity(intent);
}
});}
else if(viewType==2)
{
noHolder noHolder=(noHolder) holder;
noHolder.hint.setText("网络好像走丢了");
}
else {
dateHolder viewHolder = (dateHolder) holder;
String day=list.get(position).get("date").toString();
int nowDay=(Integer.parseInt(day)%100);
int nowMonth=((Integer.parseInt(day)%10000)/100);
viewHolder.date.setText(nowMonth+" 月 "+nowDay+" 日");}
}
@Override
public int getItemCount() {
//返回Item总条数
return list.size();
}
//内部类,绑定控件
class ViewHolder extends RecyclerView.ViewHolder{
LinearLayout paper_item;
TextView titlePaper;
TextView titleVice;
ImageView titleImage;
TextView everyday;
public ViewHolder(View view) {
super(view);
everyday=view.findViewById(R.id.everyday);
titlePaper= view.findViewById(R.id.titleArticle);
titleVice= view.findViewById(R.id.viceTitle);
titleImage = view.findViewById(R.id.imageTitle);
paper_item=view.findViewById(R.id.paper_item);
}
}
class dateHolder extends RecyclerView.ViewHolder{
TextView date;
TextView background;
public dateHolder (View view){
super(view);
date=view.findViewById(R.id.newDate);
background=view.findViewById(R.id.background);
}
}
class noHolder extends RecyclerView.ViewHolder{
TextView hint;
public noHolder (View view){
super(view);
hint =view.findViewById(R.id.hint);
}
}
5.刷新与加载
如何去理解它呢。刷新与加载的本质其实就是两个点击事件,只不过是以滑动的形式表现出来的罢了。
刷新和加载的实质也是数据的更新与加载,我们利用Recyclerview加载item使用到的list数据,list的数据变多了,Recyclerview显示的item也会变多refreshLayout.setOnRefreshListener
refreshLayout.setOnLoadMoreListener
refreshLayout.setOnLoadMoreListener(new OnLoadMoreListener() {
@Override
public void onLoadMore(RefreshLayout refreshlayout) {
refreshlayout.finishLoadMore(2000/*,false*/);//传入false表示加载失败
final Thread thread=new Thread(new Runnable() {
@Override
public void run() {
final StringBuilder response1 = new StringBuilder();
String line1;
try {
yesterday=yesterday-1;
URL Url=new URL("https://news-at.zhihu.com/api/3/news/before/"+String.valueOf(yesterday));//获取服务器哦地址
HttpURLConnection UrlConnection = (HttpURLConnection) Url.openConnection();//双方建立连接
UrlConnection.setRequestMethod("GET");
InputStream inputStream1=UrlConnection.getInputStream();
Reader reader1=new InputStreamReader(inputStream1);
BufferedReader bufferedReader1=new BufferedReader(reader1);
while ((line1=bufferedReader1.readLine())!=null){
//当temp读到的数据为空就结束
response1.append(line1);
}
runOnUiThread(new Runnable() {
@Override
public void run() {
try {
JSONObject jsonObject = new JSONObject(String.valueOf(response1));
JSONArray jsonArray = jsonObject.getJSONArray("stories");
Map<String,Object> map1=new HashMap<>();
Integer day1=jsonObject.getInt("date");
map1.put("date",day1);
list.add(map1);
for (int i = 0; i < 6; i++) {
Map<String,Object> map=new HashMap<>();
JSONObject jsonObject1 = (JSONObject) jsonArray.getJSONObject(i);
String title = jsonObject1.getString("title");
String vicetitle = jsonObject1.getString("hint");
String url=jsonObject1.getString("url");
String id=jsonObject1.getString("id");
JSONArray jsonArray1=jsonObject1.getJSONArray("images");
String imageUrl=(String)jsonArray1.get(0);
map.put("url",url);
map.put("id",id);
map.put("image",imageUrl);
map.put("title", title);
map.put("hint",vicetitle);
list.add(map);
}
myAdapter.notifyDataSetChanged();
} catch (JSONException e) {
e.printStackTrace();
}
}
});
inputStream1.close();
reader1.close();
bufferedReader1.close();
} catch (Exception e) {
runOnUiThread(new Runnable() {
@Override
public void run() {
list.clear();
Map<String,Object> map=new HashMap<>();
map.put("1",1);
map.put("2",2);
list.add(map);
myAdapter = new MyAdapter(Paper.this,list);
recyclerView.setAdapter(myAdapter);
}
});
e.printStackTrace();
//这里要有联网失败提示
}
}
});
thread.start();//启动线程
}
});
6.WebView的使用
对于每篇文章的内容部分我们使用webView控件即可WebView webView=findViewById(R.id.webView);
webView.getSettings().setJavaScriptEnabled(true);
webView.loadUrl(url);
7.评论展示与glide工具>
Glide.with(context).load(list.get(position).get("image")).into(viewholder.titleImage);
评论部分的头像图片显示为圆形,用glide工具可以加载圆形图片lide.with(context).load(list.get(position).get("image")).apply(RequestOptions.bitmapTransform(new CircleCrop())).into(viewholder.head);
评论部分的设计无需刷新加载,只需要用到多item即可,小伙伴们不妨自己尝试去做
对了,每条评论下面的时间要自己写算法哦,因为接口传过来的是一个很大的不知道怎么处理的数字,自己想想怎么能够得到具体时间吧8.断网处理
答案是:会闪退
所以我们需要做好断网处理,断网处理很简单。我们在联网操作时用到了
try{}catch结构
我们如果没有联网,程序会进入catch部分,所以我们在catch部分进行断网处理即可catch (Exception e) {
runOnUiThread(new Runnable() {
@Override
public void run() {
list.clear();
Map<String,Object> map=new HashMap<>();
map.put("1",1);
map.put("2",2);
list.add(map);
myAdapter = new MyAdapter(Paper.this,list);
recyclerView.setAdapter(myAdapter);
}
});
五、总结