代码地址如下:
http://www.demodashi.com/demo/11212.html

前言

本案例已经开源!如果你想免费下载,可以访问我的Github,所有案例均在上面,只求给个star。当然愿意支付小小金额请我喝茶也行(大学穷狗-.-)

一、准备工作

  • 使用Android Studio开发
  • 微信和QQ第三方sdk,需要自行申请(这个简单)
  • 本案例使用干活集中营提供的api,使用MVp+Material Design作为主体架构进行开发
  • 体验完整功能,点击下载APK

二、程序实现

目录结构

目录结构如下,我按照功能分包:

实现思路

整体架构--MVP+Material

  • 首先你得了解MVP架构在android中的使用,如果你还不了解,可以阅读我的这篇文章
  • 如果你不熟悉Material可以读官方文档

重点代码分析

如果讲述整个App,估计一篇文章说不清楚。那我干脆取其中一条线来分析。

下面主要分析文章列表--文章详情--文章分享

主页文章列表

这里只选择Android文章模块进行介绍:

GankContract

  1. public interface GankContract {
  2. interface View extends BaseView<Presenter>{
  3. //错误
  4. void showError();
  5. //正在加载
  6. void showLoading();
  7. //停止加载
  8. void Stoploading();
  9. //显示数据列表
  10. void showResult(ArrayList<GankNews.Question> list);
  11. //网络错误
  12. void showNotNetError();
  13. }
  14. interface Presenter extends BasePresenter{
  15. // 请求数据
  16. void loadPosts(int PagerNum, boolean cleaing);
  17. //刷新数据
  18. void reflush();
  19. //加载更多
  20. void loadMore(int PagerNum);
  21. //显示详情
  22. void StartReading(int positon);
  23. //随便看看
  24. void LookAround();
  25. }
  26. }

GankFragment

Fragment的内容主要是文章列表,我们只分享重点:

  1. //下拉刷新实现
  2. recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
  3. boolean isScrollState=false;
  4. @Override
  5. public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
  6. super.onScrollStateChanged(recyclerView, newState);
  7. LinearLayoutManager manager= (LinearLayoutManager) recyclerView.getLayoutManager();
  8. //没有滚动时候
  9. if (newState==RecyclerView.SCROLL_STATE_IDLE){
  10. //获的最后一个可见的item
  11. int lastVisibilityItem=manager.findLastCompletelyVisibleItemPosition();
  12. int totalItemCount=manager.getItemCount();
  13. //判断是否滚动到底部并且是向下滑动
  14. if (lastVisibilityItem==(totalItemCount-1)&&isScrollState){
  15. presenter.loadMore(1);
  16. }
  17. }
  18. }
  19. //通知Presenter加载数据和设置item点击事件
  20. @Override
  21. public void showResult(ArrayList<GankNews.Question> list) {
  22. if (adapter==null){
  23. Log.i(TAG, "showResult: "+list.size());
  24. adapter=new GankNewsAdapter(list,getContext());
  25. adapter.setItemOnClickListener(new OnRecyclerViewOnClickListener() {
  26. @Override
  27. public void onItemClick(View v, int position) {
  28. presenter.StartReading(position);
  29. }
  30. @Override
  31. public void onItemLongClick(View v, int position) {
  32. }
  33. });
  34. recyclerView.setAdapter(adapter);
  35. }else {
  36. adapter.notifyDataSetChanged();
  37. }
  38. }

GankPresenter

同样只分析重点代码:

  1. //根据当前页数加载列表数据
  2. @Override
  3. public void loadPosts(int PagerNum, final boolean cleaing) {
  4. CurrentPagerNum=PagerNum;
  5. if (cleaing) {
  6. view.showLoading();
  7. }
  8. if (Network.networkConnected(context)) {
  9. model.load(Api.Gank_Android + PagerNum, new OnStringListener() {
  10. @Override
  11. public void onSuccess(String result) {
  12. try {
  13. // Log.i(TAG, "gankpresenter.model.load.result"+result);
  14. GankNews news = gson.fromJson(result, GankNews.class);
  15. //contenvalues只能存储基本类型的数据,像string,int之类的,不能存储对象这种东西,而HashTable却可以存储对象。
  16. // ContentValues values = new ContentValues();
  17. if (cleaing) {
  18. list.clear();
  19. }
  20. for (GankNews.Question item : news.getResults()) {
  21. /**
  22. * 1.数据库查重:首先检测数据库中是否已经储存过该条数据
  23. * 2:因为每次重启后都是在网络上重新下载数据 如果是数据库已经存在的数据则不会重新加载,也导致了这些数据当前id值为空
  24. * ,所有要绑定队友的id值.
  25. */
  26. if (!queryIfIdExists(item.get_id())){
  27. DbLiteOrm.insert(item, ConflictAlgorithm.Replace);
  28. }else {
  29. ArrayList<GankNews.Question> ganklist=App.DbLiteOrm.query(new QueryBuilder<GankNews.Question>(GankNews.Question.class)
  30. .where(GankNews.Question.COL_ID+"=?",new String[]{item.get_id()}));
  31. GankNews.Question gankitem=ganklist.get(0);
  32. item.setId(gankitem.getId());
  33. }
  34. list.add(item);
  35. }
  36. view.showResult(list);
  37. }catch (JsonSyntaxException e){
  38. view.showError();
  39. }
  40. view.Stoploading();
  41. }
  42. @Override
  43. public void onError(VolleyError error) {
  44. view.Stoploading();
  45. view.showError();
  46. }
  47. });
  48. } else {
  49. //更新列表缓存 因为详情页都是用webView呈现 所以缓存content为空
  50. if (cleaing){
  51. QueryBuilder query=new QueryBuilder(GankNews.Question.class);
  52. query.appendOrderDescBy("id");
  53. query.limit(0,10*CurrentPagerNum);
  54. list.addAll(DbLiteOrm.<GankNews.Question>query(query));
  55. view.showResult(list);
  56. }else {
  57. view.showNotNetError();
  58. }
  59. }
  60. }
  61. //判断数据库是否已经存在
  62. public boolean queryIfIdExists(String _id){
  63. ArrayList<GankNews.Question> questionArrayList=App.DbLiteOrm.query(new QueryBuilder(GankNews.Question.class)
  64. .where(GankNews.Question.COL_ID+"=?",new String[]{_id}));
  65. if (questionArrayList.size()==0){
  66. return false;
  67. }
  68. return true;
  69. }
  70. //传递当前点击item的信息,进入详情阅读
  71. @Override
  72. public void StartReading(int positon) {
  73. //每个item就是一组数据
  74. GankNews.Question item=list.get(positon);
  75. Intent intent = new Intent(context, DetailActivity.class);
  76. intent.putExtra("type", BeanTeype.TYPE_Gank);
  77. intent.putExtra("id",list.get(positon).getId());
  78. int id=list.get(positon).getId();
  79. Log.i(TAG, "StartReading: "+id);
  80. intent.putExtra("_id", list.get(positon).get_id());
  81. intent.putExtra("url",list.get(positon).getUrl());
  82. intent.putExtra("title", list.get(positon).getDesc());
  83. if (item.getImages()==null){
  84. intent.putExtra("imgUrl", "");
  85. }else {
  86. intent.putExtra("imgUrl", list.get(positon).getImages().get(0));
  87. }
  88. /**
  89. * Content的startActivity方法,需要开启一个新的task。如果使用 Activity的startActivity方法,
  90. * 不会有任何限制,因为Activity继承自Context,重载了startActivity方法。
  91. */
  92. intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
  93. context.startActivity(intent);
  94. }
  95. //随便看看 随机选取
  96. @Override
  97. public void LookAround() {
  98. if (list.isEmpty()){
  99. view.showError();
  100. return;
  101. }
  102. StartReading(new Random().nextInt(list.size()));
  103. }

GankNewsAdapter

因为文章分两种:有图和无图。所有要进行分类加载

  1. //判断是否有图和是否是底部加载item
  2. @Override
  3. public int getItemViewType(int position) {
  4. if (position==getItemCount()-1){
  5. return TYPE_FOOTER;
  6. }if (list.get(position).getImages()==null){
  7. return TYPE_NO_IMG;
  8. }
  9. return TYPE_NORMTAL;
  10. }
  11. //根据type加载不同ViewHolder
  12. @Override
  13. public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
  14. switch (viewType){
  15. case TYPE_NORMTAL:
  16. return new NormalViewHolder(inflater.inflate(R.layout.home_list_item_layout,parent,false),listener);
  17. case TYPE_FOOTER:
  18. return new FooterViewHolder(inflater.inflate(R.layout.list_footer,parent,false));
  19. case TYPE_NO_IMG:
  20. return new NoImageViewHolder(inflater.inflate(R.layout.home_list_item_without_image,parent,false),listener);
  21. }
  22. return null;
  23. }
  24. //使用Glide加载图片。无图则不加载
  25. @Override
  26. public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
  27. if (!(holder instanceof FooterViewHolder)){
  28. GankNews.Question item=list.get(position);
  29. if (item!=null){
  30. if (holder instanceof NormalViewHolder){
  31. Glide.with(context)
  32. .load(item.getImages().get(0))
  33. .asBitmap()
  34. .placeholder(R.mipmap.loading)
  35. .diskCacheStrategy(DiskCacheStrategy.SOURCE)
  36. .error(R.mipmap.loading)
  37. .centerCrop()
  38. .into(((NormalViewHolder) holder).imageView);
  39. ((NormalViewHolder) holder).textView.setText(item.getDesc());
  40. }else if (holder instanceof NoImageViewHolder){
  41. ((NoImageViewHolder) holder).textViewNoImg.setText(item.getDesc());
  42. }
  43. }
  44. }
  45. }

详情页

DetailActivity

  1. @Override
  2. protected void onCreate(@Nullable Bundle savedInstanceState) {
  3. super.onCreate(savedInstanceState);
  4. setContentView(R.layout.frame);
  5. if (savedInstanceState!=null){
  6. detailFragment= (DetailFragment) getSupportFragmentManager().getFragment(savedInstanceState,"detailFragment");
  7. }else {
  8. detailFragment=DetailFragment.newInstance();
  9. getSupportFragmentManager().beginTransaction().replace(R.id.container,detailFragment).commit();
  10. }
  11. //获取列表传过来的具体item数据
  12. Intent intent=getIntent();
  13. DetailPresenter presenter=new DetailPresenter(detailFragment,DetailActivity.this);
  14. presenter.setType((BeanTeype) intent.getSerializableExtra("type"));
  15. presenter.setId(intent.getIntExtra("id",1));
  16. presenter.set_id(intent.getStringExtra("_id"));
  17. presenter.setTitle(intent.getStringExtra("title"));
  18. presenter.setUrl(intent.getStringExtra("url"));
  19. presenter.setImgUrl(intent.getStringExtra("imgUrl"));
  20. }

DetailContract

  1. public class DetailContract {
  2. interface Presenter extends BasePresenter{
  3. /**
  4. * 流浪器中打开
  5. * 复制文本
  6. * 复制连接
  7. * 添加收藏或取消收藏
  8. * 查询是否收藏
  9. * 请求数据
  10. * 分享到QQ
  11. * 分享到微信
  12. * 分享到朋友圈
  13. * 分享到微信收藏
  14. */
  15. void openInBrower();
  16. void copyText();
  17. void copyLink();
  18. void addToOrDeleteFromBookMarks();
  19. boolean queryIsBooksMarks();
  20. void requestData();
  21. void shareArticleToQQ(final MyQQListener listener);
  22. void shareArticleToWx();
  23. void shareArticleToWxCommunity();
  24. void shareArticleToWxCollect();
  25. }
  26. interface View extends BaseView<Presenter> {
  27. // 显示正在加载
  28. void showLoading();
  29. // 停止加载
  30. void stopLoading();
  31. // 显示加载错误
  32. void showLoadingError();
  33. // 显示分享时错误
  34. void showSharingError();
  35. // 正确获取数据后显示内容
  36. // void showResult(String result);
  37. // // 对于body字段的消息,直接接在url的内容
  38. void showResultWithoutBody(String url);
  39. // 设置顶部大图
  40. void showCover(String url);
  41. // 设置标题
  42. void setTitle(String title);
  43. // 设置是否显示图片
  44. void setImageMode(boolean showImage);
  45. // 用户选择在浏览器中打开时,如果没有安装浏览器,显示没有找到浏览器错误
  46. void showBrowserNotFoundError();
  47. // 显示已复制文字内容
  48. void showTextCopied();
  49. // 显示文字复制失败
  50. void showCopyTextError();
  51. // 显示已添加至收藏夹
  52. void showAddedToBookmarks();
  53. // 显示已从收藏夹中移除
  54. void showDeletedFromBookmarks();
  55. void showNotNetError();
  56. void shareSuccess();
  57. void shareError();
  58. void shareCancel();
  59. }
  60. }

DetailFragment

详情页主题是使用WebView显示,重点注意好设置属性和正确销毁:

  1. @Override
  2. public void initView(View view) {
  3. ......
  4. //webview设置属性
  5. webview.getSettings().setJavaScriptEnabled(true);
  6. //缩放,设置为不能缩放可以防止页面上出现放大和缩小的图标
  7. webview.getSettings().setBuiltInZoomControls(false);
  8. //缓存
  9. webview.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);
  10. //开启DOM storage API功能
  11. webview.getSettings().setDomStorageEnabled(true);
  12. //开启application Cache功能
  13. webview.getSettings().setAppCacheEnabled(false);
  14. .....
  15. }
  16. //早onDestroy中销毁WebView的对象
  17. @Override
  18. public void onDestroyView() {
  19. super.onDestroyView();
  20. webview.removeAllViews();
  21. webview.destroy();
  22. webview=null;
  23. }

DetailPresenter

  1. //复制链接地址
  2. @Override
  3. public void copyLink() {
  4. ClipboardManager manager= (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
  5. ClipData data=null;
  6. switch (type){
  7. case TYPE_Gank:
  8. data=ClipData.newPlainText("text",url);
  9. }
  10. manager.setPrimaryClip(data);
  11. view.showTextCopied();
  12. }
  13. //添加到收藏或者移除收藏
  14. @Override
  15. public void addToOrDeleteFromBookMarks() {
  16. switch (type){
  17. case TYPE_Gank:
  18. GankNews.Question gank= App.DbLiteOrm.queryById(id,GankNews.Question.class);
  19. if (queryIsBooksMarks()){
  20. view.showDeletedFromBookmarks();
  21. gank.mark=false;
  22. }else {
  23. view.showAddedToBookmarks();
  24. gank.mark=true;
  25. }
  26. App.DbLiteOrm.update(gank);
  27. break;
  28. case TYPE_Front:
  29. FrontNews.Question front=App.DbLiteOrm.queryById(id,FrontNews.Question.class);
  30. if (queryIsBooksMarks()){
  31. view.showDeletedFromBookmarks();
  32. front.mark=false;
  33. }else {
  34. view.showAddedToBookmarks();
  35. front.mark=true;
  36. }
  37. App.DbLiteOrm.update(front);
  38. break;
  39. case TYPE_IOS:
  40. IosNews.Question ios=App.DbLiteOrm.queryById(id,IosNews.Question.class);
  41. if (queryIsBooksMarks()){
  42. view.showDeletedFromBookmarks();
  43. ios.mark=false;
  44. }else {
  45. view.showAddedToBookmarks();
  46. ios.mark=true;
  47. }
  48. App.DbLiteOrm.update(ios);
  49. }
  50. }
  51. //查询是否已经收藏
  52. @Override
  53. public boolean queryIsBooksMarks() {
  54. if (_id ==null || type==null){
  55. view.showLoadingError();
  56. return false;
  57. }
  58. //true为已经收藏 false未收藏
  59. switch (type){
  60. case TYPE_Gank:
  61. GankNews.Question gank= App.DbLiteOrm.queryById(id,GankNews.Question.class);
  62. OrmLog.i(TAG,gank);
  63. boolean isMark=gank.mark;
  64. if (isMark){
  65. return true;
  66. }else {
  67. return false;
  68. }
  69. case TYPE_Front:
  70. FrontNews.Question front=App.DbLiteOrm.queryById(id,FrontNews.Question.class);
  71. if (front.mark){
  72. return true;
  73. }else {
  74. return false;
  75. }
  76. case TYPE_IOS:
  77. Log.i(TAG, "queryIsBooksMarks: "+id);
  78. IosNews.Question ios=App.DbLiteOrm.queryById(id,IosNews.Question.class);
  79. OrmLog.i(TAG,ios);
  80. if (ios.mark){
  81. return true;
  82. }else {
  83. return false;
  84. }
  85. }
  86. return false;
  87. }
  88. //分享到QQ
  89. @Override
  90. public void shareArticleToQQ(MyQQListener listener) {
  91. //title == desc
  92. if (TextUtils.isEmpty(imgUrl)){
  93. ShareSingleton.getInstance().shareToQQ((Activity) context,url,"推荐给你一篇文章",title, R.string.app_name, QQShare.SHARE_TO_QQ_FLAG_QZONE_ITEM_HIDE,listener);
  94. }else {
  95. ShareSingleton.getInstance().shareToQQ((Activity) context,url,"推荐给你一篇文章",title,imgUrl,R.string.app_name, QQShare.SHARE_TO_QQ_FLAG_QZONE_ITEM_HIDE,listener);
  96. }
  97. }
  98. //分享到微信
  99. @Override
  100. public void shareArticleToWx() {
  101. //title == desc
  102. ShareSingleton.getInstance().shareWebToWx(url,"",title,true);
  103. }
  104. //分享到朋友圈
  105. @Override
  106. public void shareArticleToWxCommunity() {
  107. //title == desc
  108. ShareSingleton.getInstance().shareWebToWx(url,"",title,false);
  109. }
  110. //分享到微信收藏
  111. @Override
  112. public void shareArticleToWxCollect() {
  113. //title == desc
  114. ShareSingleton.getInstance().shareWebToWxCollect(url,"干货",title);
  115. }

ShareSingleton

关于微信和QQ分享的具体方法还得参考官方文章,我这里提出我自己写好的分享单例类

  1. public class ShareSingleton {
  2. private Tencent mTencent;
  3. public static IWXAPI api;
  4. private static final int THUMB_SIZE = 150;
  5. //单例模式
  6. private ShareSingleton() {
  7. }
  8. public static final ShareSingleton getInstance(){
  9. return Singleton.INSTANCE;
  10. }
  11. private static class Singleton{
  12. private static final ShareSingleton INSTANCE=new ShareSingleton();
  13. }
  14. /**
  15. * 图文分享 图片来源网络
  16. * !! 分享操作要在主线程中完成
  17. * @param activity
  18. * @param targetUrl 这条分享消息被好友点击后的跳转URL。
  19. * @param shareTitle 分享的标题, 最长30个字符。
  20. * @param shareSummary 分享的消息摘要,最长40个字。
  21. * @param netImgUrl 可填 分享图片的URL或者本地路径
  22. * @param appName 手Q客户端顶部,替换“返回”按钮文字,如果为空,用返回代替
  23. * @param shareToQQExtInt 额外选项 是否自动打开分享到QZone的对话框
  24. * @param listener 分享回调接口
  25. */
  26. public void shareToQQ(Activity activity,String targetUrl,String shareTitle,String shareSummary,
  27. @Nullable String netImgUrl,@StringRes int appName,int shareToQQExtInt,MyQQListener listener){
  28. if (mTencent==null){
  29. mTencent=Tencent.createInstance(Constants.QQ_APP_ID,activity.getApplicationContext());
  30. }
  31. final Bundle params = new Bundle();
  32. params.putInt(QQShare.SHARE_TO_QQ_KEY_TYPE, QQShare.SHARE_TO_QQ_TYPE_DEFAULT);
  33. params.putString(QQShare.SHARE_TO_QQ_TARGET_URL,targetUrl);
  34. params.putString(QQShare.SHARE_TO_QQ_TITLE, shareTitle);
  35. params.putString(QQShare.SHARE_TO_QQ_SUMMARY, shareSummary );
  36. params.putString(QQShare.SHARE_TO_QQ_IMAGE_URL, netImgUrl);
  37. params.putString(QQShare.SHARE_TO_QQ_APP_NAME,activity.getString(appName));
  38. params.putInt(QQShare.SHARE_TO_QQ_EXT_INT, shareToQQExtInt);
  39. mTencent.shareToQQ(activity, params, listener);
  40. }
  41. /**
  42. * 文章分享 无图
  43. * !! 分享操作要在主线程中完成
  44. * @param activity
  45. * @param targetUrl 这条分享消息被好友点击后的跳转URL。
  46. * @param shareTitle 分享的标题, 最长30个字符。
  47. * @param shareSummary 分享的消息摘要,最长40个字。
  48. * @param appName 手Q客户端顶部,替换“返回”按钮文字,如果为空,用返回代替
  49. * @param shareToQQExtInt 额外选项 是否自动打开分享到QZone的对话框
  50. * @param listener 分享回调接口
  51. */
  52. public void shareToQQ(Activity activity,String targetUrl,String shareTitle,String shareSummary
  53. ,@StringRes int appName,int shareToQQExtInt,MyQQListener listener){
  54. if (mTencent==null){
  55. mTencent=Tencent.createInstance(Constants.QQ_APP_ID,activity.getApplicationContext());
  56. }
  57. final Bundle params = new Bundle();
  58. params.putInt(QQShare.SHARE_TO_QQ_KEY_TYPE, QQShare.SHARE_TO_QQ_TYPE_DEFAULT);
  59. params.putString(QQShare.SHARE_TO_QQ_TARGET_URL,targetUrl);
  60. params.putString(QQShare.SHARE_TO_QQ_TITLE, shareTitle);
  61. params.putString(QQShare.SHARE_TO_QQ_SUMMARY, shareSummary );
  62. params.putString(QQShare.SHARE_TO_QQ_APP_NAME,activity.getString(appName));
  63. params.putInt(QQShare.SHARE_TO_QQ_EXT_INT, shareToQQExtInt);
  64. mTencent.shareToQQ(activity, params, listener);
  65. }
  66. /**
  67. * 分享文章到微信/朋友圈
  68. * @param webUrl
  69. * @param webTitle
  70. * @param webDesc
  71. * @param isShareFriend
  72. */
  73. public void shareWebToWx(@NonNull String webUrl,String webTitle,String webDesc,boolean isShareFriend){
  74. // 注册操作也可以写死在Application中
  75. // 通过WXAPIFactory工厂,获取IWXAPI的实例
  76. api=WXAPIFactory.createWXAPI(App.getContext(),Constants.WX_APP_ID,true);
  77. // 将该app注册到微信
  78. api.registerApp(Constants.WX_APP_ID);
  79. //初始化一个WXWebpageObject对象,填写url
  80. WXWebpageObject webpag=new WXWebpageObject();
  81. webpag.webpageUrl=webUrl;
  82. //用WXWebpageObject对象初始化一个WXMediaMessage对象 填写标题和描述
  83. WXMediaMessage msg=new WXMediaMessage(webpag);
  84. msg.title=webTitle;
  85. msg.description=webDesc;
  86. //构造一个Req
  87. SendMessageToWX.Req req=new SendMessageToWX.Req();
  88. req.transaction=buildTransaction("webpage");//transaction 字段用于唯一标识一个请求
  89. req.message= msg;
  90. req.scene=isShareFriend ? SendMessageToWX.Req.WXSceneSession : SendMessageToWX.Req.WXSceneTimeline;
  91. api.sendReq(req);
  92. }
  93. /**
  94. * 分享文章到微信收藏
  95. * @param webUrl
  96. * @param webTitle
  97. * @param webDesc
  98. */
  99. public void shareWebToWxCollect(@NonNull String webUrl, String webTitle, String webDesc){
  100. // 注册操作也可以写死在Application中
  101. // 通过WXAPIFactory工厂,获取IWXAPI的实例
  102. api=WXAPIFactory.createWXAPI(App.getContext(),Constants.WX_APP_ID,true);
  103. // 将该app注册到微信
  104. api.registerApp(Constants.WX_APP_ID);
  105. //初始化一个WXWebpageObject对象,填写url
  106. WXWebpageObject webpag=new WXWebpageObject();
  107. webpag.webpageUrl=webUrl;
  108. //用WXWebpageObject对象初始化一个WXMediaMessage对象 填写标题和描述
  109. WXMediaMessage msg=new WXMediaMessage(webpag);
  110. msg.title=webTitle;
  111. msg.description=webDesc;
  112. //构造一个Req
  113. SendMessageToWX.Req req=new SendMessageToWX.Req();
  114. req.transaction=buildTransaction("webpage");//transaction 字段用于唯一标识一个请求
  115. req.message= msg;
  116. req.scene=SendMessageToWX.Req.WXSceneFavorite;
  117. api.sendReq(req);
  118. }

这篇文章就分析这么多,如果你想了解跟多,欢迎下载源码。主要部分源码都有注释

三、部分运行效果









四、其他补充

如果你有问题可以提交到Github的issue上,也可以给我发邮件。我的邮件是yeshuwei.swy@gmail.com

Android--从零开始开发一款文章阅读APP

代码地址如下:
http://www.demodashi.com/demo/11212.html

注:本文著作权归作者,由demo大师代发,拒绝转载,转载需要作者授权

Android--从零开始开发一款文章阅读APP的更多相关文章

  1. Android开源实战:使用MVP+Retrofit开发一款文字阅读APP

    文字控 使用MVP+Retrofit开发的一款文艺APP,它是一个非常优美的文字阅读应用,界面基本上符合material design设计规范. 在该项目中,我采用的是MVP架构,该架构目前在Andr ...

  2. 基于WanAndroid开放API实现的文章阅读APP

    简介 基于WanAndroid开放API开发的技术文章阅读App.主要功能包括:首页.体系.项目.公众号.搜索.登录.收藏.夜间模式等. 用到的第三方框架 RxJava RxAndroid Retro ...

  3. 从零开始开发一款app,所想到的

    我在知乎上看到这个问题http://www.zhihu.com/question/27645587.我在阅读了各位大牛的答案后,再加上自己的思考,就有了这篇文章的内容.     从零开始开发一款app ...

  4. 开发一款即时通讯App,从这几步开始

    欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 本文由腾讯云视频发表于云+社区专栏 关注公众号"腾讯云视频",一键获取 技术干货 | 优惠活动 | 视频方案 " ...

  5. 从零开始开发一款H5小游戏(二) 创造游戏世界,启动发条

    本系列文章对应游戏代码已开源 Sinuous game 上一节介绍了canvas的基础用法,了解了游戏开发所要用到的API.这篇文章开始,我将介绍怎么运用这些API来完成各种各样的游戏效果.这个过程更 ...

  6. 从零开始开发一款H5小游戏(三) 攻守阵营,赋予粒子新的生命

    本系列文章对应游戏代码已开源 Sinuous game. 每个游戏都会包含场景和角色.要实现一个游戏角色,就要清楚角色在场景中的位置,以及它的运动规律,并能通过数学表达式表现出来. 场景坐标 canv ...

  7. 如何开发一款堪比APP的微信小程序(腾讯内部团队分享)

    一夜之间,微信小程序刷爆了行业网站和朋友圈,小程序真的能如张小龙所说让用户"即用即走"吗? 其功能能和动辄几十兆安装文件的APP相比吗? 开发小程序,是不是意味着移动应用开发的一次 ...

  8. [Android游戏开发]八款开源 Android 游戏引擎 (巨好的资源)

    初学Android游戏开发的朋友,往往会显得有些无所适从,他们常常不知道该从何处入手,每当遇到自己无法解决的难题时,又往往会一边羡慕于 iPhone下有诸如Cocos2d-iphone之类的免费游戏引 ...

  9. Android商城开发系列(二)——App启动欢迎页面制作

    商城APP一般都会在应用启动时有一个欢迎界面,下面我们来实现一个最简单的欢迎页开发:就是打开商城App,先出现欢迎界面,停留几秒钟,自动进入应用程序的主界面. 首先先定义WelcomeActivity ...

随机推荐

  1. Spring中的设计模式2

    Spring设计模式分析   工厂模式和单态模式 工厂模式:可以将java对象对象的调用者从被调用者的实现逻辑中分离.调用者只关心被调用者必须满足的某种规则,这种规则我们看做是接口,不必关心实例的具体 ...

  2. PMP CMM

    CMM和PMP是两个不同的概念域,是用来解决不同问题的.我们所说的CMM,准确的说应该是叫做能力成熟度模型,北京猴子说的软件能力成熟度模型实际上应该称为SW-CMM,是CMM的一个子集.PMP可以看做 ...

  3. [BZOJ4025]二分图(线段树分治,并查集)

    4025: 二分图 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 2191  Solved: 800[Submit][Status][Discuss] ...

  4. [Codeforces-div.1 809C] Find a car

    [Codeforces-div.1 809C] Find a car 试题分析 莫名结论:\(a_{i,j}=(i-1) xor (j-1) +1\) 然后分成\(i\space xor\space ...

  5. 【Matrix-tree定理】【BEST Theorem】hdu6064 RXD and numbers

    题意:给你一张有向图,求从1出发,回到1的欧拉回路数量. 先特判掉欧拉回路不存在时的情况. 看这个吧:http://blog.csdn.net/yuanjunlai141/article/detail ...

  6. rmq问题:ST表

    存板子.O(nlogn)预处理,O(1)查询.空间O(nlogn). int d[1000006][25]; int mn[1000006]; void rmq_init() { for(int i= ...

  7. 初识Tomcat系统架构

    俗话说,站在巨人的肩膀上看世界,一般学习的时候也是先总览一下整体,然后逐个部分个个击破,最后形成思路,了解具体细节,Tomcat的结构很复杂,但是 Tomcat 非常的模块化,找到了 Tomcat最核 ...

  8. linux禁ping与限制ip登录

    以root进入linux系统,然后编辑文件icmp_echo_ignore_allvi /proc/sys/net/ipv4/icmp_echo_ignore_all将其值改为1后为禁止PING将其值 ...

  9. mysql-mmm故障整理

    Auth: JinDate: 20140414 1.master-slave同步问题1)故障描述和错误代码:监控报警slave故障登录slave服务器查看mysql> show slave st ...

  10. GCC 内联汇编(GCC内嵌ARM汇编规则)

    转:http://smileleeboo.howbbs.com/posts/list/3127/81062.html 更多文档参见:http://pan.baidu.com/s/1eQ7nd8Q 有时 ...