情绪宣泄App:十年前IT男编程撩妹纪实
阅读本文,你将收获以下内容:
1、通过观察11年前的Android代码,了解安卓开发生态近十年间的演进。
2、通过了解这款创意App的功能,对IT男该如何运用技术做出反思。
3、不幸看到作者大学时期的照片,形象极其猥琐、狼狈、不堪……够了,谁在动键盘?!
前言
因为在掘金的创作者群里比嘻哈,有人觉得我经常信口开河,尤其我写了那篇《我裁完兄弟们辞职了,转行做了一名小职员》后,有掘友评论:“这篇文章艺术成分很高”、“感觉是编故事”等等。
其中,我文章里提到过一句:我大学期间搞过Android的APP去运作。
今天,就说说这件事,一来展现下创意作品,二来给自己挽回一些微信……违心……维新……不要了。
女朋友
回到2010年,那时我上大二,谈了一个女朋友,她现在是我孩子的妈妈。
女朋友哪里都好,漂亮、温柔、大方,唯一一点就是脾气太大。
因为我不会制造浪漫,所以女朋友经常对我发脾气,一发脾气就不理我。
我一想,如果她不理我的时间设为N,如果N值无限大,那就相当于分手了,这感情不就黄了吗?
情侣在吵架冷战期间,如何才能不见面也能如同见面一般宣泄情绪,从而刷存在感呢?
可以做一款App来解决这个问题。
冷战红娘App
这款App我取名叫“冷战红娘”,意思是情侣在冷战期间调和关系的红娘媒介,并且我还亲自设计了LOGO和启动页。
一个安卓小机器人,手里拿着玫瑰花,表示示好,头顶两个触角间发出电波,表示科技和智能。
那一年,我才19岁。不行了,我膨胀得快要爆掉了。
功能简介
本软件主要有三大功能:涂鸦对方照片、写情绪日记、告知情绪结果。
下面是首页的效果:
下面是实现代码的目录结构:
女朋友名字中带“冰”,我名字中带“磊”,因此项目名是LoveBing
,包名是com.lei.lovebing01
。
有安卓小伙伴看到目录结构可能会发现少文件,说我在糊弄你,起码你的build.gradle
得有吧。
朋友,这是2010年的安卓项目,那时的版本号是SdkVersion="8"
,也就是Android 2.2
,现在最新版本已经到了API 32, Android 12
了。从互联网时代来看,就好像是现在和清朝的区别。
那时还没有动态权限请求,存取文件也不用
FileProvider
,你可以随意读取其他程序的内部数据,应用层就可以静默发送短信和获取定位,开发者可以更好地实现自己的想法,不必受到很多限制。当然,这在现在看来是不安全的。所以,任何事物的成熟都是有周期的。
那时候也没有现在这么多的第三方框架,基本都是调用Android原生的API,操作数据库需要直接使用android.database.sqlite.SQLiteDatabase
,SQL语句要自己写,操作异常要自己处理。
下面,就让我们跟随功能,结合代码,一起去剖析一下这款App吧。
涂鸦
女朋友生气不理我了,短信不回,电话不接,女生宿舍我又进不去。但是,她又有怨气没地方宣泄。这时,她就会打开这个功能,可以把我的头像摆出来,然后进行各种攻击,支持花色涂抹,支持往我的照片上放小虫子、扔臭鸡蛋、使用炸弹爆破效果等等。天啊,我这是怎么了……不但有这种想法,而且还开发出了功能。
其实,要实现这个功能,非常简单。
首先,整个页面的数据,是通过配置完成的,各种颜色,工具,以及图标,需要事先声明好。
//信手涂鸦里的数据=====================================================
//工具的名字
public final static String[] colortext={"红色", "黄色","绿色",
"粉色", "黑色", "紫色", "蓝色", "浅绿", "棕色"};
//工具的图片
public final static int[] colorpic = {
R.drawable.color1, R.drawable.color2,
R.drawable.color3, R.drawable.color4,
R.drawable.color5, R.drawable.color6,
R.drawable.color7, R.drawable.color8,
R.drawable.color9 };
//信手涂鸦颜色选择
public static final int red = 0;
public static final int yellow = 1;
public static final int green = 2;
public static final int pink = 3;
public static final int black = 4;
public static final int purple = 5;
public static final int blackblue = 6;
public static final int lightgreen = 7;
public static final int orange = 8;
//使用工具里的数据=====================================================
public final static String[] toolstext={"鸡蛋", "炸弹","生物","喷溅"};//工具的名字
public final static int[] toolspic = {//工具的图片
R.drawable.dao, R.drawable.zhadan01,R.drawable.tool,R.drawable.penjian
};
public final static int[][] toolspic_01 = {//工具的图片使用后
{R.drawable.dao_01, R.drawable.dao_02, R.drawable.dao_03,R.drawable.dao_01}
,{ R.drawable.baozha01, R.drawable.baozha02, R.drawable.baozha03, R.drawable.baozha04}
,{R.drawable.tools_01, R.drawable.tools_02, R.drawable.tools_03, R.drawable.tools_04}
,{R.drawable.penjian01, R.drawable.penjian02, R.drawable.penjian03, R.drawable.penjian04}
};
通过配置的方式,有一个极大的好处,那就是以后你增加新的工具,不用修改代码,直接修改配置文件即可。
下面一步就是使用一个Canvas
画板,把上面的配置画到画布上,并响应用户的交互,为此我新建了一个CanvasView
,它继承了View
。
public class CanvasView extends View{
public Bitmap[][] bitmapArray;
private Canvas mCanvas;
public CanvasView(Context context) {
super(context);
bitmapArray=new Bitmap[MyData.toolspic.length][4];//实例化工具数组
//载入工具需要的图像
InputStream bitmapis;
for(int i=0; i<MyData.toolspic_01.length; i++){
for(int j=0; j<MyData.toolspic_01[i].length; j++){
bitmapis = getResources().openRawResource(MyData.toolspic_01[i][j]);
bitmapArray[i][j] = BitmapFactory.decodeStream(bitmapis);
}
}
// 使用mBitmap创建一个画布
mCanvas = new Canvas(mBitmap);
mCanvas.drawColor(0xFFFFFFFF);//背景为白色
}
//在用户点击的地方画使用的工具
public void drawTools(int bitmapID){
Random rand = new Random();
int myrand = rand.nextInt(bitmapArray[bitmapID].length);
mCanvas.drawBitmap(bitmapArray[bitmapID][myrand], mX-bitmapArray[bitmapID]
[myrand].getWidth()/2, mY-juli-bitmapArray[bitmapID][myrand].getHeight()/2, null);
}
……
}
上面只是部分关键代码,主要展示了如何加载图片,以及如何响应用户的操作,基本无难点。
女朋友毕竟花费了一番功夫,作品肯定要给她保留,因为她可能要展示给我看:你看,昨天你惹我多严重,我把你画成这样!
涂鸦完成之后,文件保存到SD卡目录即可。权限管理方面,在AndroidManifest.xml
中注册一下就行,除此之外,再无其他操作。
<!-- 向SD卡写入数据的权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
当然,现在不可以了,Android 6.0
以后,你得动态申请权限了。
值得一说的是,上面的图片浏览控件叫ImageSwitcher
,这是个老控件了,很简单就可以实现幻灯片浏览的效果。
日记
如果涂鸦无法完全解气,为了能让女朋友把生气的原因表述明白,我特意增加了这个生气日记的功能。效果等同于她面对面骂我,期望她写完了气也就消了。
你觉得数据应该保存到哪里?我首选的是Android内嵌的Sqlite3
数据库。
Android中最原始的sqlite数据库操作是这样的,先利用官方的SQLiteOpenHelper
创建数据库和数据表。
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
public class DBOpenHelper extends SQLiteOpenHelper {
private static final String DATABASENAME = "angrydiary.db"; //数据库名称
private static final int DATABASEVERSION = 1;//数据库版本
public DBOpenHelper(Context context) {
super(context, DATABASENAME, null, DATABASEVERSION);
}
public void onCreate(SQLiteDatabase db) {
//建数据表 message 日记
db.execSQL("CREATE TABLE message (message_id integer primary key autoincrement,
message_title varchar(50), message_text varchar(500), message_state integer)");
}
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("DROP TABLE IF EXISTS message");
onCreate(db);
}
}
然后再写上操作数据表数据的方法。就拿生气日记信息的数据处理举例子。
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
public class MessageServiceDB {
private DBOpenHelper dbOpenHelper;
//构造函数
public MessageServiceDb(Context context) {
this.dbOpenHelper = new DBOpenHelper(context);
}
//保存数据
public void save(Message message){
SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put("message_title", message.getTitle());
values.put("message_text", message.getText());
db.insert("message", null, values);
db.close();
}
//获得数据
public List<Message> getScrollData(Integer offset, Integer maxResult){
List<Message> messageList = new ArrayList<Message>();
SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
Cursor cursor = db.query("message", null, null, null, null, null, "message_id desc", offset+","+ maxResult);
while(cursor.moveToNext()){
Integer message_id = cursor.getInt(cursor.getColumnIndex("message_id"));
String message_title = cursor.getString(cursor.getColumnIndex("message_title"));
String message_text = cursor.getString(cursor.getColumnIndex("message_text"));
Message message = new TvMessage(message_id,message_title, message_text);
messageList.add(message);
}
cursor.close();
db.close();
return messageList;
}
//删除一项数据
public void delete(Integer id){
SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
db.delete("message", "message_id=?", new String[]{id.toString()});
db.close();
}
}
利用之前构造好的数据库帮助类dbOpenHelper
,然后调用增删改查进行数据处理。这里面的增删改查,有两种方式实现,一种直接写Sql语句,另一种支持对象操作。我基本上都是用的对象操作,比如删除直接就是db.delete(xx)
。
日志列表我也是煞费苦心,为了便于了解女朋友还对哪些事情生气,我特意开发了生气事件黑白名单功能。一个事件可以反复标记是否原谅我了。这样可以了解女朋友心中还有哪些心结,可以让我逐个攻破。
此处调用一个修改方法就可以了,其实就是取出message
对象后重新setState
一个新值就可以了。
//修改信息状态
public void update(Message message){
SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put("message_state", message.getState());
db.update("message", values, "message_id=?", new String[]{message.getId().toString()});
db.close();
}
需要注意的是,对于每次打开数据库或者Cursor
,都要记得关闭,不关闭不会影响功能,但是会带来风险。现在你使用各种框架的话,完全不用考虑这些操作,因为他们都帮你做了。
安卓开发,以前和现在,用SDK和用第三方框架,就像是汽车的手动挡和自动挡,其中的优劣,自己体会。虽然我是从老手动挡过来的,但是我站自动挡这边,因为我不会傻到逆着发展趋势去行走。
反馈
女朋友也涂鸦了,也写了生气日记,最后应该也累了。为了缓解她的疲劳,我特意开发了一个看图发愣的功能。只需要点开看图发愣,就会随机出现一个唯美的动态图,并且伴随着唰唰的雨声,可以让她看上几个小时,仔细思考人生,思考我这个男朋友值不值得交往。
对于如何展示gif动态图,前端可能要骂人了,因为gif还需要动代码吗?浏览器不全给解释了。但是,当时我需要自己去解析,安卓原生的图片控件是无法展示动图的。所以,你看老一辈的程序员面临多少困难,新一代的程序员完全不用考虑这些。所以,你们应该把更多精力放在更高级的研究上,因为我相信你们也有你们的困难点。
这个图很美,我单独拿出来了,朋友们可以保存下来,自己看。或者,你试试自己去解析一下。
好了,现在总该不生气了吧。针对于此时的场景,我又开发了一个快捷短信功能,女朋友可以选择短信模板,快速给我发送短信息,给我一个台阶,让我及时去哄她。她可以说不是她发的,是我开发的软件发的,这样可以避免“她先联系的我”这类不利立场的产生。
我一贯执行配置策略,短信模板也是写到value
文件夹下的xml
文件中的。
<string-array name="message_ex">
<item>不要生气了,我错了!</item>
<item>我不生气了,你快点陪我逛街!</item>
<item>讨厌,还不给我打电话!</item>
<item>我错了,我不该对你发火的!</item>
<item>三个小时内给我打电话!</item>
<item>快给我给我买爆米花!</item>
</string-array>
关于发送短信,这里面有两点细节。
SmsManager smsManager = SmsManager.getDefault();
PendingIntent sentIntent = PendingIntent.getBroadcast(DuanxinActivity.this, 0, new Intent(), 0);
//如果字数超过70,需拆分成多条短信发送
if (text.length() > 70) {
List<String> msgs = smsManager.divideMessage(text);
for (String msg : msgs) {
smsManager.sendTextMessage(phone, null, msg, sentIntent, null);
}
} else {
smsManager.sendTextMessage(phone, null, text, sentIntent, null);
}
第一,为什么不调用Intent(Intent.ACTION_SEND)
让系统去发送短信?一开始确实是这样的。但是,后来改良为调用代码应用内发送了。因为我不想让女朋友离开我的软件,跳到第三方系统应用,避免用户跳出场景,因为有时候女朋友会夺命连环发短信(告知对方问题很严重,提高优先级),需要来回切程序,这样用着不爽,就更生气了。而且自己发送我还能捕获到发送结果,给出“发送成功”的温馨提示,但是交给第三方应用发送你是获取不到的。
第二,关于字数超过70,需要拆成多条短信,这也是经过实践得来的。满满的都是痛。
有同学不明白为什么要发短信,因为那时候还没有微信,微信是2011年才出来的。
后记
后来,经过不断反馈和改良,这款App越来越完善。
最后,女朋友看我这么用心地对待这份感情,尤其对于她反馈的软件问题,我询问地非常仔细(复现场景、发生时间、前后操作流程),修改地也非常及时(改完了就让她再试试看),她感觉我是一个靠谱和细心的人,于是她也慢慢地不再那么容易生气了。
再后来,有一个全国高校的大学生IT技能比赛,我的老师就让我拿这个作品参赛了,最后去了北京大学进行了决赛。
虽然这款App技术含量不高,但它是一款身经百战的App,它经过了多次迭代,因为用户体验和创意比较好,我最终获得全国第七名的成绩,荣获二等奖。
下面是证书,教育部的证书我是不敢造假的,我用Photoshop(是的,我也会做UI设计)做了简单的遮挡和放大,主要想让大家看一下日期确实是2011年,和我文章里描述的一样(我没有编故事)。
这款App真的没有任何技术含量,无外乎控件的罗列、画板的绘制、数据的存储。我想现在的每一个大学生都能做的到。但是不夸张地讲,它看起来却是很强大的样子。而究其根源,我想应该就是它运用技术手段尝试去解决生活中的问题,让效果得到了放大,使它具备了生命力。
直至今天,我依然在做类似(用技术解决生活中的问题)的事情。
最后,我想借助掘金App的标语结束本文,这也是我每天都会看到的一句话:
相信技术,传递价值。
作者:TF男孩
来源:juejin.cn/post/7123985353878274056