Android专题

android便签实现(仿小米便签,添加了一些新功能)

2019-01-28  本文已影响23人  小星star

一、完成效果

GitHub地址:星便签

  1. 主界面


    星便签
  2. 侧栏菜单


    侧栏菜单
  1. 基本功能(还有语音等)


    便签功能

二、实现:
明天再写吧,leileleile😂
实现功能:

功能 实现方法
界面展示 Android基础控件学习,DrawerLayout整体抽屉,RecyclerView + Adapter显示便签
便签数据存储 存到Sqlite,同时新建一个业务类完成对数据库操作的封装
分享 新建Intent跳转到系统自带的活动
提醒 闹钟组件 + Service
搜索 重写SearchView中的方法
图片和 语音 SpannableString + 正则表达式 实现存储和展示

挺简单的,主要是我对android编程完全不了解,所以花费了不少的时间。

以下步骤是我自己认为的,不知道对不对,毕竟开发经验很少

  1. 确定大概的需求,要实现哪些功能,哪些是必须的,那些是实在不会就算了的(这里显然能写便签是必须的,至于图片啥的,能实现就实现)、大概的UI,软件长啥样,使用的过程,可以使用一些开发软件帮助画出来,这样布局文件 和 活动脉络就容易理清。
  2. 第一次开发最好借鉴一下别人的,稍微看一下也行。完成 数据库的设计,便签的Id,内容,组别,等等想好。
  3. 开始写每一个布局,每一个功能,写的时候建议 准备在手上 新技术原理介绍 + 新技术实现例子 + 新技术的坑

好了,下面是文件的结构

文件结构

遇到的困难

  1. 图片的添加 与 语音的添加
    刚开始在纠结要不要把图片存在数据库,后来想了下如果要把图片存在数据库可能造成一些问题,存图片路径的话也需要,在展示图片的时候进行还原,也就意味着,纪录图片的同时还需要纪录它在文档中的位置。 所以最终选择了 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;
    }
    
    
    }
    
    
  2. 分享到 QQ,微信点了没反应,别慌,这是正常的,因为你需要去注册一下
  3. RecyclerView + CardView ( item布局是cardView)
    RecyclerView的三种LayoutManager
    瀑布会表达出杂乱,碰撞 但是 个体清晰的感觉
    线性 工整
    宫格则是整齐
  4. 用SpiderMan来实现奔溃信息的展示,查看自己的代码哪里出现了问题
    效果如下:


    奔溃界面
```
SpiderMan.getInstance()
            .init(this)
            //设置是否捕获异常,不弹出崩溃框
            .setEnable(true)
            //设置是否显示崩溃信息展示页面
            .showCrashMessage(true)
            //是否回调异常信息,友盟等第三方崩溃信息收集平台会用到,
            .setOnCrashListener(new SpiderMan.OnCrashListener() {
                @Override
                public void onCrash(Thread t, Throwable ex, CrashModel model) {
                    //CrashModel 崩溃信息记录,包含设备信息
                }
            });
```
  1. 在用户添加照片的时候使用到了知乎的Matisse库(图片选择器),Matisse配合Gilde来展示图片的时候,要写一个类实现ImageEngine,然后花了一下午在一个Matisse的bug,醉了,这也提醒我们,在使用第三库的时候要小心,有时候并不是我们代码的问题。

  2. 控制 RecyclerView 中 item的间距的时候
    新建一个类SpacesItemDecoration

    public 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));
    
  3. 6.0之后需要动态申请权限,请务必小心

  4. 方便的工具类 将 Uri转化为文件的绝对路径

  5. webView

  6. Android中的请求码与结果码
    为什么要在每个activity中都要建立一个TAG?
    这是为了能够方便辨认是从哪一个activity传来的intent


    image.png
  7. textView自动获得焦点问题,并且自动弹出软键盘

  8. fab背景色 颜色叠加

  9. edittext去掉下划线,editText光标颜色

  10. recycleView一页只显示一个item

  11. 回调 委托

  12. Serializable接口是启用其序列化功能的接口

  13. 数据库单例模式,防止生成多个数据库,
    onCreate(database):首次使用软件时生成数据库表

  14. recycleView 的使用 设置间距 设置显示方向

  15. 深刻理解interface

  16. android:fitsSystemWindows="true"

  17. android:windowSoftInputMode="adjustResize|stateVisible"

  18. accentColor

上一篇下一篇

猜你喜欢

热点阅读