package net.bwie.month12exam.activity;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.Toast;
import com.google.gson.Gson;
import net.bwie.month12exam.R;
import net.bwie.month12exam.bean.VersionBean;
import net.bwie.month12exam.fragment.NewsFragment;
import java.io.IOException;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
/**
* 12月月考
* 0、导包 + 布局等初始化操作
* 1、使用OkHttp + Retrofit + RxJava + Gson下载网络数据
* 1/1、使用OkHttp获取最新apk版本信息
* 1/2、请求最新消息和过往消息这2页数据,各自封装在Fragment中,点击不同按钮切换
* 2、升级版操作:下载的数据直接存入数据库,无论是否有网,展示的数据都从数据库获取,无需判断数据来源
* 2/1、如果数据模型类格式有难度,我们就手动解析JSON,这样就就可以得到我们想要的数据格式了
* 3、从数据库读取数据并展示
* 4、Fresco/Glide展示图片
* 5、RecyclerView点击事件(以前用interface实现)
* 5/1、使用EventBus模仿ListView的点击事件:在adapter类中实现了item的点击事件,
* 使用EventBus将被点击item的位置和对应数据传到fragment/activity中,我们在fragment中接收位置等信息,
* 并实现跳转界面
* 5/2、EventBus使用流程:
* 1、先创建一个事件类,事件中可以封装要传递的数据
* 2、发送事件携带数据:从适配器的item点击事件中发送
* 3、实现一个方法,用于接收事件中携带的数据:在fragment中注册EventBus,实现一个方法接收事件
* 6、点击item,传送item相关数据使用Eventbus
* 7、跳转详情页,再次获取详情页的数据
* 8、向上滑动,标题收缩渐变:Coordinator+AppBarLayout+CollapsingToolbarLayout + Toolbar
* 9、详情页数据?
* 10、查看新闻真实点赞数?
* 11、所有UI控件都使用ButterKnife加载
*/
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
protected Button mLatestBtn;
protected Button mBeforeBtn;
protected LinearLayout mTopContainer;
protected FrameLayout mFragmentContainer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
super.setContentView(R.layout.activity_main);
// 获取最新APK版本信息
getAPKVersionInfo();
initView();
}
private void initView() {
mLatestBtn = (Button) findViewById(R.id.latest_btn);
mLatestBtn.setOnClickListener(MainActivity.this);
mBeforeBtn = (Button) findViewById(R.id.before_btn);
mBeforeBtn.setOnClickListener(MainActivity.this);
mTopContainer = (LinearLayout) findViewById(R.id.top_container);
mFragmentContainer = (FrameLayout) findViewById(R.id.fragment_container);
// 展示碎片
showFragment(NewsFragment.TYPE_LATEST);// 默认展示最新消息碎片
}
// 获取最新APK版本信息
private void getAPKVersionInfo() {
String newVersionUrl = "https://news-at.zhihu.com/api/4/version/android/2.3.0";
// 创建OkHttp网络请求客户端
OkHttpClient client = new OkHttpClient();
// 创建网络请求对象并设置请求地址、请求方式等操作
Request request = new Request.Builder()
.url(newVersionUrl)
.get()// 不写也行,默认就是get请求方式
.build();
// 根据请求对象新建请求任务,执行请求
Call call = client.newCall(request);
call.enqueue(new Callback() {
@Override
public void onResponse(Call call, final Response response) throws IOException {
Log.d("1510", "成功获取数据,线程:" + Thread.currentThread().getName());
// 坑!OkHttp同样具有Call的请求操作,但数据是在子线程中获取到
// Retrofit框架封装了OkHttp并进行了线程跳转处理,所以Retrofit获取网络数据在主线程
// 从响应体中获取json字符串
ResponseBody body = response.body();
String json = body.string();
final VersionBean version = new Gson().fromJson(json, VersionBean.class);
// 手动跳转到主线程,即可更新UI
runOnUiThread(new Runnable() {
@Override
public void run() {
Log.d("1510", "成功获取数据,线程:" + Thread.currentThread().getName());
Toast.makeText(MainActivity.this, version.getLatest() + ", " + version.getMsg(), Toast.LENGTH_LONG)
.show();
Log.d("1510", version.getLatest());
}
});
}
@Override
public void onFailure(Call call, IOException e) {
}
});
}
@Override
public void onClick(View view) {
if (view.getId() == R.id.latest_btn) {
showFragment(NewsFragment.TYPE_LATEST);
} else if (view.getId() == R.id.before_btn) {
showFragment(NewsFragment.TYPE_BEFORE);
}
}
// 切换最新消息碎片
private void showFragment(int type) {
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.fragment_container, NewsFragment.newInstance(type))
.commit();
}
}
package net.bwie.month12exam.activity;
import android.os.Bundle;
import android.support.design.widget.CollapsingToolbarLayout;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.MenuItem;
import android.webkit.WebView;
import android.widget.ImageView;
import android.widget.TextView;
import com.bumptech.glide.Glide;
import net.bwie.month12exam.R;
import net.bwie.month12exam.bean.DetailBean;
import butterknife.BindView;
import butterknife.ButterKnife;
/**
* 使用Toolbar,需要隐藏原有的Actionbar
* 在values/styles文件中修改为NoActionBar风格
*/
public class DetailActivity extends AppCompatActivity {
@BindView(R.id.title_iv)
ImageView mTitleIv;
@BindView(R.id.toolbar)
Toolbar mToolbar;
@BindView(R.id.collapsing_toolbar_layout)
CollapsingToolbarLayout mCollapsingToolbarLayout;
@BindView(R.id.web_view)
WebView mWebView;
@BindView(R.id.comments_tv)
TextView mCommentsTv;
@BindView(R.id.like_tv)
TextView mLikeTv;
private DetailBean mDetailBean;
private ActionBar mActionBar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_detail);
ButterKnife.bind(this);
initView();
initData();
showData();
}
private void initView() {
// toolbar虽然长得比actionbar好看,但功能还是延用了actionbar
// 把toolbar当做actionbar使用,就可以使用里面的逻辑了
setSupportActionBar(mToolbar);
// 获取toolbar对应的actionbar,操作逻辑
mActionBar = getSupportActionBar();
// 设置标题栏左边是否展示返回箭头
mActionBar.setDisplayHomeAsUpEnabled(true);
}
private void showData() {
// 给折叠toolbar布局设置标题
mCollapsingToolbarLayout.setTitle(mDetailBean.getTitle());
mToolbar.setTitle(mDetailBean.getTitle());
// 标题展开背景
Glide.with(this)
.load(mDetailBean.getTitle_bg_url())
.into(mTitleIv);
mCommentsTv.setText("评论:" + mDetailBean.getComments());
mLikeTv.setText("点赞:" + mDetailBean.getLike());
// 加载html代码
mWebView.loadDataWithBaseURL(null,
mDetailBean.getHtmlCode(),
"text/html",
"utf-8",
null);
}
private void initData() {
mDetailBean = new DetailBean();
mDetailBean.setTitle("美国男同学加我好友");
mDetailBean.setTitle_bg_url("http://file31.mafengwo.net/M00/21/3C/wKgBs1bz8dmAJqSwAArv_7pK7cI03.groupinfo.w680.jpeg");
mDetailBean.setComments(128);
mDetailBean.setLike(256);
String htmlCode = "";
for (int i = 0; i < 100; i++) {
htmlCode += "<div>adasdasdasdsdasdasdsfsddfsdfsdfsdfsdfsdfsdfsd</div><br/>";
}
mDetailBean.setHtmlCode(htmlCode);
}
// 实现菜单的点击监听,其中可实现toolbar返回按钮的点击监听
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home://
finish();
break;
}
return super.onOptionsItemSelected(item);
}
}
package net.bwie.month12exam.adapter;
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.RequestOptions;
import net.bwie.month12exam.R;
import net.bwie.month12exam.bean.StoriesBean;
import net.bwie.month12exam.event.SendPositionAndDataEvent;
import org.greenrobot.eventbus.EventBus;
import java.util.ArrayList;
import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
public class NewsAdapter extends RecyclerView.Adapter<NewsAdapter.ViewHolder> {
private Context mContext;
private List<StoriesBean> mDatas;
public NewsAdapter(Context context) {
mContext = context;
mDatas = new ArrayList<>();
}
// 添加数据的操作
public void addDatas(List<StoriesBean> stories) {
mDatas.addAll(stories);
// 一定要刷新界面
notifyDataSetChanged();
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(mContext)
.inflate(R.layout.item_news, parent, false);
// 初始化itemView的点击事件
initItemListener(itemView, parent);
return new ViewHolder(itemView);
}
// 初始化itemView的点击事件
// 点击item,咱得能知道被点击item的位置,和item对应的数据
private void initItemListener(View itemView, ViewGroup parent) {
// 获取item的父容器RecyclerView,用于监测被点击item的位置
final RecyclerView parentRecyclerView = (RecyclerView) parent;
itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 父容器获取子控件在布局中的【位置】
int position = parentRecyclerView.getChildLayoutPosition(v);
StoriesBean story = mDatas.get(position);
// // 跳转第二页,咱不这么干
// Intent intent = new Intent(mContext, DetailActivity.class);
// mContext.startActivity(intent);
// 使用EventBus将数据发送到Fragment中
EventBus.getDefault().post(new SendPositionAndDataEvent(position, story));
}
});
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
StoriesBean story = mDatas.get(position);
holder.mTitleTextView.setText(story.getTitle());
// 创建Glide配置对象,设置圆图
RequestOptions options = new RequestOptions()
.circleCrop();
Glide.with(mContext)
.load(story.getImge())
.apply(options)
.into(holder.mPicImageView);
}
@Override
public int getItemCount() {
return mDatas == null ? 0 : mDatas.size();
}
static class ViewHolder extends RecyclerView.ViewHolder {
@BindView(R.id.title_tv)
TextView mTitleTextView;
@BindView(R.id.pic_iv)
ImageView mPicImageView;
public ViewHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
}
}
}
package net.bwie.month12exam.application;
import android.app.Application;
import net.bwie.month12exam.bean.DaoMaster;
import net.bwie.month12exam.bean.DaoSession;
import net.bwie.month12exam.retrofit.MyJSONFactory;
import org.greenrobot.greendao.database.Database;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
public class MyApplication extends Application {
private static Retrofit sRetrofit;
private static DaoSession sDaoSession;
@Override
public void onCreate() {
super.onCreate();
initRetrofit();
initGreenDAO();
}
// 参考安卓系统原生的数据库操作方式
// 1、准备数据库助手SQLiteOpenHelper
// 1/1、在助手中指定了:数据库文件名,数据库版本号
// 1/2、初始化数据库时要建表,于是就准备了表名
// 2、使用助手帮我们创建数据库对象db
// 3、用db去操作数据库:增删改查
// 对比greenDAO
// 0、不再使用数据库助手创建表,根据面向对象思想,把表 -> 类,把列名 -> 类中的属性
// 应该先去封装数据模型类,添加必要的注解例如表,主键等,make以下工程自动生成必要代码
// 1、也要创建数据库助手DevOpenHelper
// 1/1、指定了数据库文件名。默认版本号为1
// 2、用助手帮我们创建数据库对象,注意:要使用greenDAO框架封装好的数据库对象
// 3、对比原生操作额外的一项:每次操作数据库其实就是一次和数据库交互的行为,也就是和数据库的一次会话Session
// 4、在会话中执行具体的操作行为:增删改查(面向对象思想的操作)
private void initGreenDAO() {
DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this, "zhihu.db");
Database db = helper.getWritableDb();
sDaoSession = new DaoMaster(db).newSession();// greenDAO框架主干类根据db对象创建的回话
}
// 初始化网络请求框架Retrofit
private void initRetrofit() {
sRetrofit = new Retrofit.Builder()
.baseUrl("https://news-at.zhihu.com/")
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
// .addConverterFactory(GsonConverterFactory.create())// 自动解析
.addConverterFactory(new MyJSONFactory())// 我们自己定义解析工厂去解析响应中的JSON数据
.build();
}
public static Retrofit getRetrofit() {
return sRetrofit;
}
public static DaoSession getDaoSession() {
return sDaoSession;
}
}
package net.bwie.month12exam.bean;
/**
* 详情页的假数据
*/
public class DetailBean {
private String title;
private String title_bg_url;
private String htmlCode;
private int comments;// 评论数
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getTitle_bg_url() {
return title_bg_url;
}
public void setTitle_bg_url(String title_bg_url) {
this.title_bg_url = title_bg_url;
}
public String getHtmlCode() {
return htmlCode;
}
public void setHtmlCode(String htmlCode) {
this.htmlCode = htmlCode;
}
public int getComments() {
return comments;
}
public void setComments(int comments) {
this.comments = comments;
}
public int getLike() {
return like;
}
public void setLike(int like) {
this.like = like;
}
private int like;// 点赞数
}
package net.bwie.month12exam.bean;
import java.util.List;
public class NewsBean {
/**
* date : 20131118
* stories : [{"images":["http://p4.zhimg.com/7b/c8/7bc8ef5947b069513c51e4b9521b5c82.jpg"],"type":0,"id":1747159,"ga_prefix":"111822","title":"深夜食堂 · 我的张曼妮"},{"images":["http://p3.zhimg.com/21/0c/210c7b63b931932fa7a1e62bf0113e7b.jpg"],"type":0,"id":1858551,"ga_prefix":"111822","title":"清朝皇帝上朝的时候说的是满语还是汉语?"},{"images":["http://p4.zhimg.com/7c/d1/7cd1496541c7964b2cf8614b9fa664b0.jpg"],"type":0,"id":1848791,"ga_prefix":"111821","title":"淘宝上那些适合送爸妈的东西"},{"images":["http://p2.zhimg.com/11/05/1105cfa3d12f3539ef35fa603614ed92.jpg"],"type":0,"id":1849914,"ga_prefix":"111820","title":"恋爱里的男子汉,迎头专心刷榜才是正经事"},{"images":["http://p4.zhimg.com/cf/1f/cf1fd58f22d3c5fd2fd2a5543d70f81d.jpg"],"type":0,"id":1854693,"ga_prefix":"111819","title":"鸡蛋黄和蛋清长成了的话分别是鸡的什么部位?"},{"images":["http://p2.zhimg.com/e3/d1/e3d15e98b3db498d53d9ed1b85d2fab5.jpg"],"type":0,"id":1861205,"ga_prefix":"111818","title":"鲜柚游戏周报\r\n回顾一周 iOS 精品游戏"},{"title":"吃很重要 · 第一口就开始 high 了(多图)","ga_prefix":"111818","images":["http://p2.zhimg.com/14/3b/143bd74ec7a0299b76d17e6b095799aa.jpg"],"multipic":true,"type":0,"id":1858917},{"images":["http://p2.zhimg.com/51/32/51324fa89e1aba7a337e20e98c9664f1.jpg"],"type":0,"id":1856401,"ga_prefix":"111818","title":"追女孩教练传授:妹子玩手机的话,你就也玩手机,挺好的"},{"images":["http://p3.zhimg.com/f0/97/f0973d30830eed315d46b531f38719cf.jpg"],"type":0,"id":1854400,"ga_prefix":"111817","title":"最美应用 · 给你一种新邮箱Molto"},{"images":["http://p1.zhimg.com/d6/11/d611dd7d57d144621779ec36c8df42fb.jpg"],"type":0,"id":1848590,"ga_prefix":"111817","title":"谁在维护比特币的核心算法?"},{"images":["http://p3.zhimg.com/f8/70/f870fac8fea14e56d2cddf926a4800f2.jpg"],"type":0,"id":1847175,"ga_prefix":"111816","title":"离岸金融:一种光明正大的钻空子行为"},{"images":["http://p1.zhimg.com/d4/30/d430ba0d8d9e51482b6a0bd8ff5ef6ee.jpg"],"type":0,"id":1846706,"ga_prefix":"111815","title":"银泰全面支持支付宝钱包付款,两个初学者的第一次"},{"images":["http://p1.zhimg.com/4b/8c/4b8c8f9c40f08fa9a1a830095131c67c.jpg"],"type":0,"id":1846781,"ga_prefix":"111814","title":"仅售 179 美元,Moto G 为什么定价这么低"},{"images":["http://p3.zhimg.com/2c/ce/2cce90676f6841e01ab683384f4daaf0.jpg"],"type":0,"id":1844934,"ga_prefix":"111813","title":"导演张一白:青春小说到青春电影,中间有层面纱"},{"images":["http://p4.zhimg.com/39/ff/39ff45effc9f6083bb8da5a6f768eaa2.jpg"],"type":0,"id":1838196,"ga_prefix":"111812","title":"知天下 · 圆顶事实上是宗教建筑中常用的一种造型"},{"images":["http://p1.zhimg.com/80/26/802617acf921694c7a2e732008e6c2cf.jpg"],"type":0,"id":1844302,"ga_prefix":"111811","title":"PrimeSense:苹果正在试图收购这个革命性体感控制设备"},{"images":["http://p2.zhimg.com/a6/42/a6423122d959de347cc8a8c61d150c21.jpg"],"type":0,"id":1844263,"ga_prefix":"111810","title":"家庭用 100M 光纤使用什么无线路由器才能发挥最大网速?"},{"images":["http://p2.zhimg.com/52/94/52941a00e16bffffe480e19c387d07d9.jpg"],"type":0,"id":1843578,"ga_prefix":"111809","title":"金融产品也有物流,并且好处多多"},{"images":["http://p1.zhimg.com/86/79/86799f8608bf39171b78456675a9f4f0.jpg"],"type":0,"id":1839454,"ga_prefix":"111807","title":"召回六十多万辆车,大众继续焦头烂额处理变速箱问题"},{"images":["http://p4.zhimg.com/3a/dd/3adda8a964695f3d0c84944fbb676cda.jpg"],"type":0,"id":1843290,"ga_prefix":"111807","title":"全方位冬日晨跑注意事项已供上,假设你已起床"},{"images":["http://p2.zhimg.com/a8/a6/a8a677d04d27a96cdb457e6c1a430d68.jpg"],"type":0,"id":1838920,"ga_prefix":"111807","title":"独处时才是了解自己的最好机会,你上完厕所会冲吗?"},{"images":["http://p4.zhimg.com/c5/7d/c57d1d0ee1ba83df700982a4f8e5ac26.jpg"],"type":0,"id":1843557,"ga_prefix":"111807","title":"李宗盛:既然青春留不住,不如听大叔讲故事"},{"images":["http://p3.zhimg.com/b7/b2/b7b223eaa3a6daaf680f266973803c75.jpg"],"type":0,"id":1839693,"ga_prefix":"111807","title":"创业公司财务怎么做,绝大多数创业初期年轻人不知道这个"},{"images":["http://p3.zhimg.com/21/32/21328ba459bee7961dc71de398002638.jpg"],"type":0,"id":1841395,"ga_prefix":"111806","title":"瞎扯 · 如何正确地吐槽"}]
*/
private String date;
private List<StoriesBean> stories;
public String getDate() {
return date;
}
public void setDate(String date) {
this.date = date;
}
public List<StoriesBean> getStories() {
return stories;
}
public void setStories(List<StoriesBean> stories) {
this.stories = stories;
}
}
package net.bwie.month12exam.bean;
import org.greenrobot.greendao.annotation.Entity;
import org.greenrobot.greendao.annotation.Id;
import org.greenrobot.greendao.annotation.Generated;
@Entity
public class StoriesBean {
// 下面的id表达的含义是内容,也就是文章id,还需要创建真正意义的主键_id
@Id(autoincrement = true)
private long _id;
private int id;// 文章id
private String title;
// private List<String> images;// 原始数据格式,使用List集合会产生2张表的管理操作,有点难
private String imge;// 即将使用手动JSON解析方式,可以产生我们要求的数据格式:只有1个url地址就够了
@Generated(hash = 1675582365)
public StoriesBean(long _id, int id, String title, String imge) {
this._id = _id;
this.id = id;
this.title = title;
this.imge = imge;
}
@Generated(hash = 929118848)
public StoriesBean() {
}
public long get_id() {
return this._id;
}
public void set_id(long _id) {
this._id = _id;
}
public int getId() {
return this.id;
}
public void setId(int id) {
this.id = id;
}
public String getTitle() {
return this.title;
}
public void setTitle(String title) {
this.title = title;
}
public String getImge() {
return this.imge;
}
public void setImge(String imge) {
this.imge = imge;
}
}package net.bwie.month12exam.bean;
public class VersionBean {
/**
* status : 1
* msg : 【更新】
- 极大提升性能及稳定性
- 部分用户无法使用新浪微博登录
- 部分用户无图模式无法分享至微信及朋友圈
* url : http://zhstatic.zhihu.com/pkg/store/daily/zhihu-daily-zhihu-2.6.0(744)-release.apk
* latest : 2.6.0
*/
private int status;
private String msg;
private String url;
private String latest;
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getLatest() {
return latest;
}
public void setLatest(String latest) {
this.latest = latest;
}
}package net.bwie.month12exam.event;
import net.bwie.month12exam.bean.StoriesBean;
public class SendPositionAndDataEvent {
private int mPosition;
private StoriesBean mData;
public SendPositionAndDataEvent(int position, StoriesBean data) {
mPosition = position;
mData = data;
}
public int getPosition() {
return mPosition;
}
public void setPosition(int position) {
mPosition = position;
}
public StoriesBean getData() {
return mData;
}
public void setData(StoriesBean data) {
mData = data;
}
}
package net.bwie.month12exam.fragment;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import net.bwie.month12exam.R;
import net.bwie.month12exam.activity.DetailActivity;
import net.bwie.month12exam.adapter.NewsAdapter;
import net.bwie.month12exam.application.MyApplication;
import net.bwie.month12exam.bean.DaoSession;
import net.bwie.month12exam.bean.NewsBean;
import net.bwie.month12exam.bean.StoriesBean;
import net.bwie.month12exam.bean.StoriesBeanDao;
import net.bwie.month12exam.event.SendPositionAndDataEvent;
import net.bwie.month12exam.httpservice.NewsHttpService;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.greendao.query.Query;
import org.greenrobot.greendao.query.QueryBuilder;
import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.functions.Consumer;
import io.reactivex.functions.Function;
import io.reactivex.schedulers.Schedulers;
/**
* 最新消息和过往消息的数据结构相同,
* 可以使用同一种类型的Fragment
* <p>
* 适配器最佳使用方式是伴随RecyclerView一起初始化,在网络数据产生后才去添加数据
* 这么写能够保证适配器数据稳定性和扩展灵活性
*/
public class NewsFragment extends Fragment {
// 定义两个标记代表不同的数据内容
// 人为规定1代表最新消息,2代表过往消息
public static final int TYPE_LATEST = 1;
public static final int TYPE_BEFORE = 2;
// 碎片类型
private int mType;
@BindView(R.id.recycler_view)
RecyclerView mRecyclerView;
private NewsAdapter mAdapter;
private DaoSession mDaoSession;
private StoriesBeanDao mDao;
// 传参数创建Fragment的方式
public static NewsFragment newInstance(int type) {
Bundle args = new Bundle();
args.putInt("type", type);// 从外部传递加载数据类型
NewsFragment fragment = new NewsFragment();
fragment.setArguments(args);
return fragment;
}
// 在创建碎片是接收碎片类型,根据碎片类型判断加载最新还是过往
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle args = getArguments();
if (args != null) {
mType = args.getInt("type");
}
mDaoSession = MyApplication.getDaoSession();
mDao = mDaoSession.getStoriesBeanDao();
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_news, container, false);
ButterKnife.bind(this, rootView);
// 在数据产生之前就初始化适配器,防止后面空指针异常
mAdapter = new NewsAdapter(getContext());
// 绑定适配器
mRecyclerView.setAdapter(mAdapter);
// 加载网络数据
loadData();
return rootView;
}
// 根据接收到的数据类型加载对应的数据
private void loadData() {
NewsHttpService httpService = MyApplication.getRetrofit()
.create(NewsHttpService.class);
// RxJava的观察者
Observable<NewsBean> observable = null;
switch (mType) {
case TYPE_LATEST:
observable = httpService.getLatestObservable();
break;
case TYPE_BEFORE:
observable = httpService.getBeforeObservable();
break;
}
// Retrofit自动获取数据,自动将数据源设置给观察者
// 我们只需要管理线程调度和数据变换
// 下载数据和数据变换跳转至子线程
observable.subscribeOn(Schedulers.io())
// 链式调用的书写风格,map对数据进行变换
.map(new Function<NewsBean, List<StoriesBean>>() {
// 将原始数据变换为我们想要的数据
@Override
public List<StoriesBean> apply(NewsBean newsBean) throws Exception {
return newsBean.getStories();
}
})
// 数据跳转至主线程
.observeOn(AndroidSchedulers.mainThread())
// 怎么用:【存储】到数据库中
/**
* 我们要向数据库插入数据了,要执行insert方法
* 谁来执行insert呢?dao对象来执行
* dao从哪来?从DaoSesscion会话中得到
* DaoSession从哪来?从数据库对象db中得到
* db从哪来?从数据库助手中得到
* 数据库助手从哪来?在MyApplication中创建
*/
.subscribe(new Consumer<List<StoriesBean>>() {
@Override
public void accept(List<StoriesBean> storiesBeans) throws Exception {
insertDatasInDB(storiesBeans);
showDataByDB();
}
},
// 第二个消费者叫onError,包括断网情况也会走该操作
new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
showDataByDB();
}
});
}
// 向数据库插入数据
private void insertDatasInDB(List<StoriesBean> storiesBeans) {
for (StoriesBean story : storiesBeans) {
// 设置主键_id为时间戳
story.set_id(System.currentTimeMillis());
// 使用dao插入数据
mDao.insert(story);
}
}
// 从数据库加载数据并展示
// 配合query查询类实现查询操作
// 获取查询对象,如果需要,设置查询条件
// 使用查询对象查询数据
// 添加到适配器中展示
private void showDataByDB() {
QueryBuilder<StoriesBean> builder = mDao.queryBuilder();
Query<StoriesBean> query = builder.build();
// 查询全部数据
List<StoriesBean> list = query.list();
if (list != null && !list.isEmpty()) {
mAdapter.addDatas(list);
}
}
// 在Fragment可见时注册EventBus,这样就能接收到数据了
// 在Fragment不可见时,解除注册,优化性能
@Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
@Override
public void onStop() {
super.onStop();
EventBus.getDefault().unregister(this);
}
// 定义一个方法,接收事件
// 方法参数要求设置为接收的事件
@Subscribe// EventBus订阅了该方法,即可将数据发送到该方法中
public void onReceivePositionAndData(SendPositionAndDataEvent event) {
int position = event.getPosition();
StoriesBean story = event.getData();
Toast.makeText(getContext(), "位置:" + position, Toast.LENGTH_SHORT).show();
// 接收到item点击事件这个事,我们跳转界面
Intent intent = new Intent(getContext(), DetailActivity.class);
startActivity(intent);
}
}
package net.bwie.month12exam.httpservice;
import net.bwie.month12exam.bean.NewsBean;
import io.reactivex.Observable;
import retrofit2.http.GET;
/**
* 该接口定义了Retrofit请求网络的方法
* 配合RxjAVA时,定义的方法返回值类型要求为RxJava的观察者Observable
* 泛型就是JSON解析后的数据模型类
*/
public interface NewsHttpService {
// 最新消息的方法
@GET("api/4/news/latest")
Observable<NewsBean> getLatestObservable();
// 过往消息的方法
@GET("api/4/news/before/20131119")
Observable<NewsBean> getBeforeObservable();
}
package net.bwie.month12exam.retrofit;
import android.support.annotation.Nullable;
import net.bwie.month12exam.bean.NewsBean;
import net.bwie.month12exam.bean.StoriesBean;
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import okhttp3.ResponseBody;
import retrofit2.Converter;
import retrofit2.Retrofit;
/**
* 自定义JSON解析工厂
* JSON数据来自网络的响应,所以我们需要重写响应体装换器,将JSON字符串人为进行解析操作并返回
*/
public class MyJSONFactory extends Converter.Factory {
@Nullable// 选择安卓包的注解
// 泛型一:原始的响应体,包含JSON字符串
// 泛型二:指定我们解析后的数据类型
@Override
public Converter<ResponseBody, NewsBean> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
return new Converter<ResponseBody, NewsBean>() {
@Override
public NewsBean convert(ResponseBody body) throws IOException {
// 使用JSONObject、JSONArray解析数据并手动封装
String json = body.string();
try {
JSONObject jsonObject = new JSONObject(json);
NewsBean news = new NewsBean();// 根数据模型类
// 获取JSON对象中的JSON数组
JSONArray storiesArray = jsonObject.getJSONArray("stories");
List<StoriesBean> storiesList = new ArrayList<>();
// 使用for循环封装JSON数组中的全部数据
for (int i = 0; i < storiesArray.length(); i++) {
// 获取JSON数组中的大括号对象,就是storiesbean
JSONObject storiesObject = storiesArray.getJSONObject(i);
StoriesBean story = new StoriesBean();
// stories对象中有一个JSON数组叫images,我们要提取出这个数组中唯一的字符串
JSONArray imagesArray = storiesObject.getJSONArray("images");
String imagesString = imagesArray.getString(0);
// 封装至我们的storiesbean中
story.setImge(imagesString);
// title可以直接通过story对象提取出来并封装
String title = storiesObject.getString("title");
story.setTitle(title);
int storyId = storiesObject.getInt("id");
story.setId(storyId);
// 将每一个story封装到集合中
storiesList.add(story);
}
// 循环后,将sotries集合封装到根数据模型类中
news.setStories(storiesList);
return news;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
};
}
}
// Glide
compile 'com.github.bumptech.glide:glide:4.2.0'
// Retrofit
compile 'com.squareup.retrofit2:converter-gson:2.3.0'
compile 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'
// RxJava+Retrofit
compile 'io.reactivex.rxjava2:rxjava:2.1.5'
compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
// app模块下的build.gradle中添加:
compile 'org.greenrobot:greendao:3.2.2'
// ButterKnife
// 在app文件夹的build.gradle中一起导入:
compile 'com.jakewharton:butterknife:8.8.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
// EventBus
compile 'org.greenrobot:eventbus:3.1.1'
implementation 'com.android.support:design:26.1.0'
implementation 'com.android.support:recyclerview-v7:26.1.0'
classpath 'org.greenrobot:greendao-gradle-plugin:3.2.2'