【Android開發】找樂,一個笑話App的制作過程記錄
緣起
想做一個笑話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中遇到的,使用的時候沒想到居然這麽easy!僅僅須要在xml中聲明一個PhotoView。主要的放大、縮小、手勢識別都有了!太方便。可能也是北郵人論壇官方client採用的一個查看大圖的工具。
在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截圖
這些功能做完之後我改動了一下配色。終於效果大體如圖,部分功能未截圖。
應用市場
嗯,這些都實現了之後就上線應用商店了,主要有這幾個:
- 小米應用商店:http://app.mi.com/detail/286105
- 應用寶:http://android.myapp.com/myapp/detail.htm?
apkName=com.fuxuemingzhu.findjoy
- 豌豆莢:http://www.wandoujia.com/apps/com.fuxuemingzhu.findjoy
- Fir.im:http://fir.im/axy4
能夠掃碼下載:
應用寶下載:
豌豆莢下載:
Fir.im下載:
盡量不要用Fir。由於Fir沒有直觀的下載數目統計,嗯。盡量通過正規應用商店吧。
下載這事還得大家捧個場。
結語
盡管是一個非常easy的App,可是卻包括著非常多的心思在裏面。並且嘗試新的東西的時候能夠學到不少東西。這個是值得肯定的。畢竟我如今有種想把之前的App都揉碎又一次來寫的沖動。畢竟抵擋不住 MVP + Material Design的雙重誘惑啊!
Android 開發還有非常長的路要走。
本項目已經全然開源,代碼在:https://github.com/fuxuemingzhu/FindJoy,歡迎Star和Fork.
【Android開發】找樂,一個笑話App的制作過程記錄