缘起

想做一个笑话App的原因是因为在知乎上看过一个帖子,做Android可以有哪些数据可以练手,里面推荐了几个数据开放平台。在这些平台中无一不是有公共的笑话接口,当时心想这个可以拿来练手啊,还挺有意思的,估计还能积累一点用户。

碰巧(真的好巧)在Github中遇到了一个MVP设计模式的框架Beam,作者Jude95有一个笑话仓库————Joy(豆逼),就是一个做笑话的!更巧的是用到的接口也是我在关注的接口,心想不如改造一下吧,做个升级版,自己也可以在这个中学到别人是怎么写App的。后来发现这是一个非常正确的决定。

雏形

因为是基于别人的改进,所以在写之前就已经有雏形,当然这个雏形不是很完善,这恰恰给了我修改的空间。在获得作者的修改同意后,我就进一步研究这个利用MVP框架书写的App。未修改之前:

首先,豆逼只能查看段子和查看图片,我认为基本的复制文本和查看大图以及下载图片,这些都没有。作者只是用这个仓库来说明MVP模式的,所以只做了最基本的功能。作者也说,笑话连个id都没有,点赞、评论什么的根本没法做。那好,我就把我认为的文本复制和图片相关的做一下吧。

研究

MVP模式在这个项目之前我研究很少,只是听说,但是这个项目完全给我耳目一新的感觉,MVP对Android来说实在是太有用了!关于MVP我以后想仔细写个帖子研究一下,这里只想说明MVP使Android项目层次分明,代码结构简单,复用性高。参考作者的Beam

这个项目用了很棒的一个开源控件,也是项目作者自己的控件EasyRecyclerView,这个控件对我来说相见恨晚。线性布局仿EasyRecyclerView已经实现了下拉刷新,上拉加载更多,错误提示等,简直把项目开发中可能遇到的坑都给做好了,我之前只能一个一个的去实现这些功能!为什么没有早早的用上这个控件!

其他的没有重大的惊喜,但是项目总体感觉代码量很少,很精简。如果是我完成相同的功能的App,可能需要3倍的代码才能实现。

改进

查看大图

首先实现点击查看大图的功能。

PhotoView这个控件也是之前不久在Github中遇到的,使用的时候没想到竟然这么容易!只需要在xml中声明一个PhotoView,基本的放大、缩小、手势识别都有了!太方便!可能也是北邮人论坛官方客户端采用的一个查看大图的工具。

在java文件加载图片时则与ImageView完全相同,这个不在赘述。

还有一个拓展的地方是,单击图片返回(= = 一般都有吧?)。这个需要根据PhotoView的官方说明,使用Attacher来管理点击事件,经过我测试,貌似直接声明ImageView的点击是不会有效果的。

图片下载

这个App采用的是Glide加载网络图片,而Glide并没有直接的下载存储的方法,只有自己拓展,耽误了些功夫。

直接分享一段图片下载和通知图库的代码吧。

    public void saveImage(String imageUrl) {

        String[] names = new String[0];
if (imageUrl != null) {
names = imageUrl.split("/");
}
String imageName = names[names.length - 1];
Glide
.with(getView())
.load(imageUrl)
.asBitmap()
.toBytes(Bitmap.CompressFormat.JPEG, 100)
.into(new SimpleTarget<byte[]>() {
@Override
public void onResourceReady(final byte[] resource, GlideAnimation<? super byte[]> glideAnimation) {
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) { if (ImageStorage.checkifImageExists(imageName)) {
Snackbar.make(getView().fab, "图片已存在", Snackbar
.LENGTH_LONG)
.setAction("Action", null).show();
return null;
}
String path = Environment.getExternalStorageDirectory().toString();
JUtils.Log("path", path); Bitmap bitmap = BitmapFactory.decodeByteArray(resource, 0, resource.length);
JUtils.Log("imageName", imageName); ImageStorage.saveToSdCard(getView(), bitmap, imageName); Snackbar.make(getView().fab, "图片已下载", Snackbar.LENGTH_LONG)
.setAction("Action", null).show(); return null;
}
}.execute();
}
});
}

其中ImageStorage.java:

    public class ImageStorage {

        public static String saveToSdCard(Context context, Bitmap bitmap, String filename) {

            String stored = null;

            File sdcard = Environment.getExternalStorageDirectory();

            File folder = new File(sdcard.getAbsoluteFile(), "FindJoy");//the dot makes this directory hidden to
// the
// user
folder.mkdir();
File file = new File(folder.getAbsoluteFile(), filename + ".jpg");
if (file.exists())
return stored; try {
FileOutputStream out = new FileOutputStream(file);
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, out);
out.flush();
out.close();
stored = "success";
JUtils.Log("stored", stored);
} catch (Exception e) {
e.printStackTrace();
}
// 其次把文件插入到系统图库
try {
MediaStore.Images.Media.insertImage(context.getContentResolver(),
file.getAbsolutePath(), filename, null);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
// 最后通知图库更新
context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse("file://" + file
.getAbsolutePath())));
return stored;
} public static File getImage(String imagename) { File mediaImage = null;
try {
String root = Environment.getExternalStorageDirectory().toString();
File myDir = new File(root);
if (!myDir.exists())
return null; mediaImage = new File(myDir.getPath() + "/FindJoy/" + imagename);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return mediaImage;
} public static boolean checkifImageExists(String imagename) {
Bitmap b = null;
File file = ImageStorage.getImage("/" +
imagename + "" +
".jpg");
String path = file.getAbsolutePath(); if (path != null)
b = BitmapFactory.decodeFile(path); if (b == null || b.equals("")) {
return false;
}
return true;
}
}

为什么之前我试了很久但是一直发现图库没有图片呢?一直以为是自己的图片没有存储下来,后来用图库的查看文件夹的方式发现了FindJoy目录。

原来是需要通知图库更新,否则图片不会再图库中显示。具体请看上面代码。

复制段子

这个本身是不麻烦的,出现问题的地方在于,这个MVP框架中怎么对这个List加上OnItemClilkListner。本身我就不很熟,这个地方犯了不少错误,我怎么没想到看EasyRecyclerView的官方说明呢?

解决方法是在TextViewHolder中的itemView加上:

    itemView.setOnClickListener(view ->
new MaterialDialog.Builder(getContext())
.title(R.string.select)
.content(R.string.copy)
.positiveText(R.string.agree)
.negativeText(R.string.disagree)
.onPositive((dialog, which) -> {
// Gets a handle to the clipboard service.
ClipboardManager clipboard = (ClipboardManager) getContext().
getSystemService(Context.CLIPBOARD_SERVICE);
// Creates a new text clip to put on the clipboard
ClipData clip = ClipData.newPlainText("joy", data.getText());
// Set the clipboard's primary clip.
clipboard.setPrimaryClip(clip);
Snackbar.make(itemView, "已将该段子复制到粘贴板", Snackbar.LENGTH_SHORT).show();
})
.show()
);

官方库还有可以设置EasyRecyclerView的监听的方法,效果是一样的。

友盟统计

友盟统计可能是我自己往外发包的一个必选的项了,因为要知道App的使用情况啊。这次发现友盟统计比以前好用多了,jar包也放到了jCenter()仓库,非常方便了。

这里要赞一下这个MVP库的好处了,竟然可以让所有的Activity的生命周期都调用同一段代码来实现友盟统计中要求的所有Actvity的OnResume()和OnPause()方法中都调用统计方法。

实现是通过一个顶级管理类MyActivityLifeCycleDelegate继承ActivityLifeCycleDelegate,在里面设置友盟统计的方法。

    public class MyActivityLifeCycleDelegate extends ActivityLifeCycleDelegate {
public MyActivityLifeCycleDelegate(Activity act) {
super(act);
} @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
JUtils.Log("onCreate" + getActivity().getClass().getName());
} @Override
protected void onPause() {
super.onPause();
JUtils.Log("onPause");
MobclickAgent.onPause(getActivity());
} @Override
protected void onResume() {
super.onResume();
JUtils.Log("onResume");
MobclickAgent.onResume(getActivity());
}
}

然后在App的Application中

Beam.setActivityLifeCycleDelegateProvider(MyActivityLifeCycleDelegate::new);

上面这行代码是IDE自己简化的,好高端啊,竟然有点不明白是怎么回事了。。)

哦,对了,不要忘记在Manifest中声明友盟的appkey。嗯。统计就集成好了。

自动更新

同样是友盟的服务,我也以为只是几分钟的事情就搞定了,可是因为自己的问题,耽误了一段时间,竟然还想着把这个锅扔给友盟。好吧,我错了。

这个和统计不一样的是需要手动下载包放到项目当中,其中包括了一个.so文件。

因为在app的gradle中声明了这句:

    compile fileTree(include: ['*.jar'], dir: 'libs')

我就以为万事大吉了。事实上我开启了友盟的debug模式才看了出来是我的.so没有加载进去。

嗯,jni应该这么声明,我给忘了:

    sourceSets {
main {
jniLibs.srcDirs = ['libs']
}
}

这样.so文件就能加载进去了。而友盟自动更新只需要在MainActivity中写一句代码:

    UmengUpdateAgent.update(this);

很酷对不对?

自动更新是根据app versionCode来判断的,更新的时候注意修改。

App截图

这些功能做完之后我修改了一下配色,最终效果大体如图,部分功能未截图。

应用市场

嗯,这些都实现了之后就上线应用商店了,主要有这几个:

可以扫码下载:

应用宝下载:

豌豆荚下载:

Fir.im下载:

尽量不要用Fir,因为Fir没有直观的下载数目统计,嗯。尽量通过正规应用商店吧。

下载这事还得大家捧个场。

结语

虽然是一个非常简单的App,但是却包含着非常多的心思在里面,而且尝试新的东西的时候可以学到不少东西,这个是值得肯定的。毕竟我现在有种想把之前的App都揉碎重新来写的冲动,毕竟抵挡不住 MVP + Material Design的双重诱惑啊!

Android 开发还有很长的路要走。

本项目已经完全开源,代码在:https://github.com/fuxuemingzhu/FindJoy,欢迎Star和Fork.

【Android开发】找乐,一个笑话App的制作过程记录的更多相关文章

  1. 我在开发第一个Swift App过程中学到的四件事

    本文转载至 http://www.itjhwd.com/wzkfyigeswiftsjs/ 译者注:本文作者Greg Heo,这是他为讲授iOS 8 App Extensions视频教程而实际使用Sw ...

  2. 2016 校招, Android 开发,一个本科应届的坎坷求职之路(转)

    转载出处:http://www.nowcoder.com/discuss/3244?type=2&order=0&pos=1&page=1 和大多数的面经不同,我不是大牛,手头 ...

  3. Android开发:LocationManager获取经纬度及定位过程(附demo)

    在Android开发其中.常常须要用到定位功能,尤其是依赖于地理位置功能的应用.非常多人喜欢使用百度地图,高德地图提供的sdk.开放API,可是在只须要经纬度,或者城市,街道地址等信息.并不须要提供预 ...

  4. ESA2GJK1DH1K升级篇: STM32远程乒乓升级,基于Wi-Fi模块AT指令TCP透传方式,MQTT通信控制升级(含有数据校验)-APP用户程序制作过程

    前言 这一节和上一节是搭配的 给大家鱼,也必须给鱼竿! 我期望自己封装的代码,无论过了多少年都有应用的价值! 这节说明一下制作APP用户程序的过程 咱是用MQTT通信控制模块实现升级,所以首先自己的程 ...

  5. Android开发中导入第三方库所遇问题记录

    1.重复循环依赖的问题 (1)需求 如下图所示: 在Android 项目中,采用模块化开发,一个是主跑application--Mudule A,另外一个是library--Library B 1)M ...

  6. 【Android开发】问答机器人,聊天类App的开发制作过程记录

    缘起 很久没写项目了,所以单纯的想练练手,正好看到有问答机器人的接口,想到之前也做过聊天项目,为什么不实验一下呢.当然也是简单调用接口的项目,并没有真正的完成问答的算法等等.业余项目,功能不齐全,只实 ...

  7. android 开发 实现一个app的引导页面,使用ViewPager组件(此引导的最后一页的Button会直接写在最后一页布局里,跟随布局滑进滑出)

    基本ViewPager组件使用方式与我之前写的https://blog.csdn.net/qq_37217804/article/details/80332634 这篇博客一致. 下面我们将重点详细解 ...

  8. android 开发 实现一个app的引导查看页面(使用ViewPager组件)

    我们安装完app后第一次打开app,通常都会有一个翻页图片形式的app引导简介说明.下面我们来实现这个功能.ViewPager这个组件与ListView和RecyclerView在使用上有很高的相似处 ...

  9. android开发实战-记账本APP(二)

    继昨天的开发,继续完成今天的内容. (一)开始构建一些业务逻辑,开始构建记账本的添加一笔记账的功能. ①对fab按钮的click时间进行修改,创建一个AlertDialog.Builder对象,因此我 ...

随机推荐

  1. clickhouse使用的一点总结

    clickhouse据说是用在大数据量的olap场景列式存储数据库,也有幸能够用到它在实际场景中落地.本篇就来说说简单的使用心得吧. 1. 整体说明 架构啥的,就不多说了,列式存储.大数据量.高性能. ...

  2. C++ and OO Num. Comp. Sci. Eng. - Part 5.

    类 class 关键字提供了一种包含机制,将数据和操作数据的方法结合到一起,作为内置类型来使用. 类可以包含私有部分,仅其成员和 friend 类访问,公有部分可以在程序中任意位置处访问. 构造函数与 ...

  3. 问题记录:SNP 标记 phasing

    GATK4 检测的SNP标记,有些位点会在检测过程中完成 phasing,在后续做基因型填充的时候有坑. GATK4 phasing 结果的缺失位点不是 ./. 也不是 .|.  而是直接变成一个单独 ...

  4. day05 django框架之路由层

    day05 django框架之路由层 今日内容概要 简易版django请求声明周期流程图(重要) 路由匹配 无名有名分组 反向解析 无名有名解析 路由分发 名称空间 伪静态 虚拟环境 简易版djang ...

  5. HDFS【Namenode、SecondaryNamenode、Datanode】

    目录 一. NameNode和SecondaryNameNode 1.NN和2NN 工作机制 2. NN和2NN中的fsimage.edits分析 3.checkpoint设置 4.namenode故 ...

  6. C++ 素数对猜想

    我的解法是先将2到n的所有素数全部列出来,再计算.将全部的素数列出来用了一个叫"埃拉托色尼筛法"的方法. 算法参照这里:https://www.sohu.com/a/2526745 ...

  7. Linux学习 - 文本编辑器Vim

    一.Vim工作模式 二.命令 插入 a 光标后插入 A 光标所在行尾插入 i 光标前插入 I 光标所在行首插入 o 光标下插入新行 O 光标上插入新行   删除 x 删除光标处字符 nx 删除光标处后 ...

  8. rust方法集

    随机数.数字对比.控制台输入 use std::io; use std::cmp::Ordering; use rand::Rng; fn main() { println!("please ...

  9. 理解inode以及软硬连接,和inode磁盘爆满的解决方案以及文件权限

    理解Linux的软硬链接 创建硬链接的命令 [root@centos6 data]#ln /data/f1 /data/f2 [root@centos6 data]#ll -itotal 1613 - ...

  10. my42_Mysql基于ROW格式的主从同步

    模拟主从update事务,从库跳过部分update事务后,再次开始同步的现象 主库 mysql> select * from dbamngdb.isNodeOK; +----+--------- ...