一、完成效果
GitHub地址:星便签
-
主界面
-
侧栏菜单
-
基本功能(还有语音等)
二、实现:
明天再写吧,leileleile😂
实现功能:
功能 | 实现方法 |
---|---|
界面展示 | Android基础控件学习,DrawerLayout整体抽屉,RecyclerView + Adapter显示便签 |
便签数据存储 | 存到Sqlite,同时新建一个业务类完成对数据库操作的封装 |
分享 | 新建Intent跳转到系统自带的活动 |
提醒 | 闹钟组件 + Service |
搜索 | 重写SearchView中的方法 |
图片和 语音 | SpannableString + 正则表达式 实现存储和展示 |
挺简单的,主要是我对android编程完全不了解,所以花费了不少的时间。
以下步骤是我自己认为的,不知道对不对,毕竟开发经验很少
- 确定大概的需求,要实现哪些功能,哪些是必须的,那些是实在不会就算了的(这里显然能写便签是必须的,至于图片啥的,能实现就实现)、大概的UI,软件长啥样,使用的过程,可以使用一些开发软件帮助画出来,这样布局文件 和 活动脉络就容易理清。
- 第一次开发最好借鉴一下别人的,稍微看一下也行。完成 数据库的设计,便签的Id,内容,组别,等等想好。
- 开始写每一个布局,每一个功能,写的时候建议 准备在手上 新技术原理介绍 + 新技术实现例子 + 新技术的坑
好了,下面是文件的结构
遇到的困难
- 图片的添加 与 语音的添加
刚开始在纠结要不要把图片存在数据库,后来想了下如果要把图片存在数据库可能造成一些问题,存图片路径的话也需要,在展示图片的时候进行还原,也就意味着,纪录图片的同时还需要纪录它在文档中的位置。 所以最终选择了 SpannableString来实现。 SpanStr可以达到富文本的效果,只不过我们需要把富文本的标签用正则表达式提取出来,加工一下就可以了。这样存在一个便签的内容存在数据库中还是一个整体,而不会被分割成图片 + 文字 + 声音,在textView和editText中都能正确显示SpanStr。
但是如果你仅仅是加了图片的话,Html.formHtml()然后重写ImageGetter()会更加方便的(他的原理是当遇到<img >就会回调这个方法,加载图片)。
这里展示一下将String Note.content 转化成 我自己定义的 标签
<img src=''/> <voice src=''/>的代码。public class ContentToSpannableString { public static SpannableString Content2SpanStr(Context context, String noteContent){ //这里的fakeNoteContent 是虚假content,是展示给用户的,因为真正的content中包含着的声音src变为可点击spannable之后会很丑 String fakeNoteContent = noteContent; ArrayList<String> voiceSrc = new ArrayList<>(); Pattern voice = Pattern.compile("<voice src='(.*?)'/>"); Matcher mVoice = voice.matcher(noteContent); while(mVoice.find()){ String str1 = mVoice.group(0); fakeNoteContent = noteContent.replace(str1,""); String str2 = mVoice.group(1); voiceSrc.add(str2); } Log.d("voiceSrc的大小",Integer.toString(voiceSrc.size())); Pattern img = Pattern.compile("<img src='(.*?)'/>"); Matcher mImg = img.matcher(fakeNoteContent); // "\uD83C\uDFA4", 这是android手机的emoji录音图标 Pattern voiceLogo = Pattern.compile("\uD83C\uDFA4"); Matcher mVoiceLogo = voiceLogo.matcher(fakeNoteContent); SpannableString spanStr = new SpannableString(fakeNoteContent); while(mImg.find()){ String str = mImg.group(0); int start = mImg.start(); int end = mImg.end(); Uri imgUri = Uri.parse(mImg.group(1)); Drawable drawable = null; try { drawable = Drawable.createFromStream(context.getContentResolver().openInputStream(imgUri),null); drawable.setBounds(0,0,2 * drawable.getIntrinsicWidth(),2 * drawable.getIntrinsicHeight()); } catch (FileNotFoundException e) { e.printStackTrace(); } ImageSpan imageSpan = new ImageSpan(drawable); spanStr.setSpan(imageSpan,start,end,Spannable.SPAN_INCLUSIVE_EXCLUSIVE); } int i = 0; while(mVoiceLogo.find()){ Log.d("下标i",Integer.toString(i)); int start = mVoiceLogo.start(); int end = mVoiceLogo.end(); final String voiceFilePath = voiceSrc.get(i); i++; //可点击的SpannableString ClickableSpan clickableSpan = new ClickableSpan() { @Override public void onClick(View view) { //实现点击事件 Log.d("voice能否点击","能够点击"); MediaPlayer mp = new MediaPlayer(); try { mp.setDataSource(voiceFilePath); } catch (IOException e) { e.printStackTrace(); } //mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); mp.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { @Override public void onCompletion(MediaPlayer mediaPlayer) { if(mediaPlayer != null){ mediaPlayer.stop(); mediaPlayer.release(); mediaPlayer = null; } } }); try { mp.prepare(); } catch (IOException e) { e.printStackTrace(); } mp.start(); //etc } }; spanStr.setSpan(clickableSpan,start,end,Spannable.SPAN_INCLUSIVE_EXCLUSIVE); } return spanStr; } }
- 分享到 QQ,微信点了没反应,别慌,这是正常的,因为你需要去注册一下
- RecyclerView + CardView ( item布局是cardView)
RecyclerView的三种LayoutManager
瀑布会表达出杂乱,碰撞 但是 个体清晰的感觉
线性 工整
宫格则是整齐 -
用SpiderMan来实现奔溃信息的展示,查看自己的代码哪里出现了问题
效果如下:
```
SpiderMan.getInstance()
.init(this)
//设置是否捕获异常,不弹出崩溃框
.setEnable(true)
//设置是否显示崩溃信息展示页面
.showCrashMessage(true)
//是否回调异常信息,友盟等第三方崩溃信息收集平台会用到,
.setOnCrashListener(new SpiderMan.OnCrashListener() {
@Override
public void onCrash(Thread t, Throwable ex, CrashModel model) {
//CrashModel 崩溃信息记录,包含设备信息
}
});
```
在用户添加照片的时候使用到了知乎的Matisse库(图片选择器),Matisse配合Gilde来展示图片的时候,要写一个类实现ImageEngine,然后花了一下午在一个Matisse的bug,醉了,这也提醒我们,在使用第三库的时候要小心,有时候并不是我们代码的问题。
-
控制 RecyclerView 中 item的间距的时候
新建一个类SpacesItemDecorationpublic class SpacesItemDecoration extends RecyclerView.ItemDecoration { private int space; public SpacesItemDecoration(int space) { this.space = space; } @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { outRect.left = space; outRect.right = space; outRect.bottom = space; // Add top margin only for the first item to avoid double space between items if (parent.getChildPosition(view) == 0) outRect.top = space; } }
然后
//3是设置的间距 recyclerView.addItemDecoration(new SpacesItemDecoration(3));
6.0之后需要动态申请权限,请务必小心
方便的工具类 将 Uri转化为文件的绝对路径
webView
-
Android中的请求码与结果码
为什么要在每个activity中都要建立一个TAG?
这是为了能够方便辨认是从哪一个activity传来的intent
textView自动获得焦点问题,并且自动弹出软键盘
fab背景色 颜色叠加
edittext去掉下划线,editText光标颜色
recycleView一页只显示一个item
回调 委托
Serializable接口是启用其序列化功能的接口
数据库单例模式,防止生成多个数据库,
onCreate(database):首次使用软件时生成数据库表recycleView 的使用 设置间距 设置显示方向
深刻理解interface
android:fitsSystemWindows="true"
android:windowSoftInputMode="adjustResize|stateVisible"
accentColor