写一个小便签

**欢迎访问我的个人博客转发请注明出处:http://www.wensibo.top/2017/03/08/写一个小便签/ **
一直想要写一个便签应用,因为我一直在用的是锤子便签和一加便签,觉得体验还是可以的,但是始终觉得自己也是可以做的,这段时间因为有些事情耽误了,项目前几天做好了,一直没有时间上传到github (各位大爷路过给个star呗😉) ,今天趁着有时间顺便写了这篇文章,介绍一下写这个便签时遇到的一些问题,当作是跟大家一起分享吧!

功能

  • 实现最基本的增加、删除、修改便签
  • 便签能够保存到本地
  • 主界面采用Material Design设计风格,相对美观(勿喷)
  • Recycle View上下滑动可以自动隐藏Toolbar,以及Floating Action Button
  • Recycle View的Item可以实现如QQ的侧滑效果,可以通过点击删除、置顶进行编辑
  • 可以设置便签为星✨,那么将会在便签界面左边增加一个红色的标志,以提醒用户此便签为重要便签
  • 变迁主界面标有时间,并且按照编辑时间从新到旧进行排列

效果

效果截图

Talk is cheap,show me your code

如何在RecycleView中使用本地Sqlite数据库数据

RecycleView是google在推出Material Design时着重介绍的一个组件,它对传统的ListView已经可以说是完全代替了,功能强大是他的一个最大优点,但是有一点局限的就是我们自定义的RecycleView必须继承重写 RecyclerView.Adapter 和 RecyclerView.ViewHolder,虽然在里面我们可以随意重写方法,但是可以发现如果我们使用数据库作为数据源,RecyclerView.Adapter是无法支持读取Cursor的,但是开源的力量又再次显现了,直接给上github地址,但是我们这里只需要复用其中的两个文件就行了,容我娓娓道来。

1、添加下面的RecyclerViewCursorAdapter 和 CursorFilter到工程中
由于代码太长影响排版,我就直接附上下载链接
RecyclerViewCursorAdapter
CursorFilter

2、新建自定义的Adapter并且继承RecyclerViewCursorAdapter

  • NoteAdapter
public class NoteAdapter extends RecyclerViewCursorAdapter<NoteAdapter.MyNoteViewHolder> {

    private Context mContext;
    private RecyclerViewOnItemClickListener mOnItemClickListener;
    private onSwipeListener mOnSwipeListener;


    public NoteAdapter(Context context,Cursor cursor,int flags) {
        super(context,cursor,flags);
        this.mContext = context;
    }

    @Override
    public MyNoteViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View root = LayoutInflater.from(parent.getContext()).inflate(R.layout.note_row, parent, false);
        MyNoteViewHolder holder = new MyNoteViewHolder(root);
        return holder;
    }

    @Override
    public void onBindViewHolder(final MyNoteViewHolder holder, Cursor cursor) {
        int position = cursor.getPosition();
        holder.tv.setText(cursor.getString(cursor.getColumnIndex(NoteDbAdapter.COL_CONTENT)));
        holder.tv_dateTime.setText(cursor.getString(cursor.getColumnIndex(NoteDbAdapter.COL_DATETIME)));
        holder.mRowtab.setBackgroundColor(cursor.getInt(cursor.getColumnIndex(NoteDbAdapter.COL_IMPORTANT)) == 1?
                mContext.getResources().getColor(R.color.colorAccent):mContext.getResources().getColor(android.R.color.white)
        );
        holder.root.setTag(position);

        holder.tv.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (mOnItemClickListener != null) {
                    mOnItemClickListener.onItemClickListener(view, holder.getAdapterPosition());
                }
            }
        });


    }

    @Override
    protected void onContentChanged() {

    }

    /** 设置点击事件 */
    public void setRecyclerViewOnItemClickListener(RecyclerViewOnItemClickListener onItemClickListener) {
        this.mOnItemClickListener = onItemClickListener;
    }

    public RecyclerViewOnItemClickListener getOnItemClickListener() {
        return mOnItemClickListener;
    }

    /** 点击事件接口 */
    public interface RecyclerViewOnItemClickListener {
        void onItemClickListener(View view, int position);
    }


    /**
     * 内部类Holder
     */
    class MyNoteViewHolder extends RecyclerView.ViewHolder {
        private TextView tv;
        private TextView tv_dateTime;
        private View mRowtab;
        private Button btnTop;
        private Button btnDelete;
        private View root;

        public MyNoteViewHolder(View root) {
            super(root);
            this.root = root;
            tv = (TextView) root.findViewById(R.id.row_text);
            tv_dateTime = (TextView) root.findViewById(R.id.tv_note_time);
            mRowtab = root.findViewById(R.id.row_tab);
            btnTop = (Button) root.findViewById(R.id.btnTop);
            btnDelete = (Button) root.findViewById(R.id.btnDelete);
        }
    }

}

3、在Activity中这样用

mRecyclerView = (RecyclerView) findViewById(R.id.recycle_notes);
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
        mCursor = mNoteDbAdapter.fetchAllNotes();
        mNoteAdapter = new NoteAdapter(this, mCursor, 0);
        Log.d(TAG, "mCursor的大小为:" + mCursor.getCount());

        //设置点击事件
        mNoteAdapter.setRecyclerViewOnItemClickListener(new NoteAdapter.RecyclerViewOnItemClickListener() {
            @Override
            public void onItemClickListener(View view, int position) {
                if (mCursor == null || mCursor.isClosed()) {
                    if (mCursor == null) {
                        Log.d("NoteActivity", "newCursor is null");
                        Toast.makeText(NoteActivity.this, "newCursor is null", Toast.LENGTH_SHORT).show();
                    } else if (mCursor.isClosed()){
                        Log.d("NoteActivity", "newCursor is closed");
                        Toast.makeText(NoteActivity.this, "newCursor is null", Toast.LENGTH_SHORT).show();
                    }
                } else {
                    mCursor.moveToPosition(position);
                    String content = mCursor.getString(mCursor.getColumnIndex(NoteDbAdapter.COL_CONTENT));
                    int importtant = mCursor.getInt(mCursor.getColumnIndex(NoteDbAdapter.COL_IMPORTANT));
                    int id = mCursor.getInt(mCursor.getColumnIndex(NoteDbAdapter.COL_ID));
                    Log.d("NoteActivity", content + importtant);
                    Note clickNote = new Note(id, content, importtant);
                    Intent intent = new Intent();
                    intent.setClass(NoteActivity.this, NoteContentActivity.class);
                    Bundle bundle = new Bundle();
                    bundle.putSerializable("note", clickNote);
                    intent.putExtras(bundle);
                    startActivity(intent);
                }

            }
        });

        //设置适配器
        mRecyclerView.setAdapter(mNoteAdapter);

如何在RecycleView上下滑动时隐藏Toolbar和FAB按钮

思路很简单,只需要记录RecycleView向下滑动(手指向上滑动)时移动的距离,超过一定范围时就会调用Toolbar以及Floating Action Button的animate().translationY方法,令其在Y轴方向上移动,当RecycleView向上滑动(手指向下滑动)时又会反过来回到初始状态,并且当滑动到RecycleView底部时会强制Toolbar和FAB回到初始状态,上代码。

  • HidingScrollListener
public abstract class HidingScrollListener extends RecyclerView.OnScrollListener {

    private static final int HIDE_THRESHOLD = 20;
    private int scrolledDistance = 0;
    private boolean controlsVisible = true;
    private int mItemSize=0;

    public HidingScrollListener(int itemSize) {
        this.mItemSize = itemSize - 1;
    }

    /**
     *
     * @param recyclerView
     * @param dx 横向的滚动距离
     * @param dy 纵向的滚动距离
     *           记录的是两个滚动事件之间的偏移量,而不是总的滚动距离。
     */
    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);
        int firstVisibleItem = ((LinearLayoutManager) recyclerView.getLayoutManager()).findFirstVisibleItemPosition();
        int lastVisibleItem = ((LinearLayoutManager) recyclerView.getLayoutManager()).findLastVisibleItemPosition();

        if (firstVisibleItem == 0||lastVisibleItem==mItemSize) {
            if (!controlsVisible) {
                onShow();
                controlsVisible = true;
            }
        }else{
            //如果总的滚动距离超多了一定值
            // (这个值取决于你自己的设定,越大,需要滑动的距离越长才能显示或者隐藏),
            // 我们就根据其方向显示或者隐藏Toolbar(dy>0意味着下滚,dy<0意味着上滚)。
            if (scrolledDistance > HIDE_THRESHOLD && controlsVisible) {
                onHide();
                controlsVisible = false;
                scrolledDistance = 0;
            } else if (scrolledDistance < -HIDE_THRESHOLD && !controlsVisible) {
                onShow();
                scrolledDistance = 0;
                controlsVisible = true;
            }
        }
        //计算出滚动的总距离(deltas相加),
        // 但是只在Toolbar隐藏且上滚或者Toolbar未隐藏且下滚的时候
        if ((controlsVisible && dy > 0) || (!controlsVisible && dy < 0)) {
            scrolledDistance += dy;
        }
    }

    public abstract void onHide();
    public abstract void onShow();
}

在Avtivity中使用回掉方法

//为recycleview设置滚动监听器
        mRecyclerView.setOnScrollListener(new HidingScrollListener(mCursor.getCount()) {
            @Override
            public void onHide() {
                hideView();
            }

            @Override
            public void onShow() {
                showView();
            }
        });

       private void hideView() {
        mToolbar.animate().translationY(
                -mToolbar.getHeight()).setInterpolator(new AccelerateInterpolator(2));
        FrameLayout.LayoutParams ip = (FrameLayout.LayoutParams) mFloatingActionButton.getLayoutParams();
        int fabButtonMargin = ip.bottomMargin;
        mFloatingActionButton.animate().translationY(
                mFloatingActionButton.getHeight() + fabButtonMargin).setInterpolator(new AccelerateInterpolator(2)).start();
    }

    private void showView() {
        mToolbar.animate().translationY(0).setInterpolator(new DecelerateInterpolator(2));
        mFloatingActionButton.animate().translationY(0).setInterpolator(new DecelerateInterpolator(2)).start();
        }

特别注意布局文件
如果你发现你运行的效果像下面的截图一样的话,那你肯定是因为布局文件上少写了这两句

android:clipToPadding="false"
android:paddingTop="?attr/actionBarSize"

bug截图

完整的布局代码如下:

  • main_activity.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
             xmlns:app="http://schemas.android.com/apk/res-auto"
              android:layout_width="match_parent"
              android:layout_height="match_parent">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recycle_notes"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:clipToPadding="false"
        android:paddingTop="?attr/actionBarSize"
        />

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="?attr/colorPrimary"
        android:clipToPadding="false"
        app:titleTextColor="@android:color/white"
        />

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/button_add_note"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|right"
        android:layout_marginBottom="16dp"
        android:layout_marginRight="16dp"
        android:src="@drawable/ic_action_new"
        android:elevation="15dp"
        app:fabSize="normal"
        app:pressedTranslationZ="8dp"
        app:rippleColor="#ff87eb"
        />

</FrameLayout>

最后来讲讲如何实现仿QQ的侧滑出现删除、指定操作

首先得谢谢张旭童 ,他的一个库帮我解决了这个问题,点击这里可以访问他的项目。
1、在布局文件中使用com.mcxtzhang.swipemenulib.SwipeMenuLayout布局,在ItemView后添加button表示删除置顶按钮。
2、在Adapter中设置打开侧滑菜单,并且可以设置菜单在左还是在右

((SwipeMenuLayout) holder.root.findViewById(R.id.swipeMenuLayout)).setIos(false).setLeftSwipe(false).setSwipeEnable(true);

3、在Activity中设置动作事件

 mNoteAdapter.setOnSwipeListener(new NoteAdapter.onSwipeListener() {
            @Override
            public void onDel(int pos) {
                Toast.makeText(NoteActivity.this, "点击了第" + (pos+1) + "条item的删除按钮", Toast.LENGTH_SHORT).show();
                mCursor.moveToPosition(pos);
                int id = mCursor.getInt(mCursor.getColumnIndex(NoteDbAdapter.COL_ID));
                mNoteDbAdapter.deleteNoteById(id);
                mCursor = mNoteDbAdapter.fetchAllNotes();
                mNoteAdapter.changeCursor(mCursor);
            }

            @Override
            public void onTop(int pos) {
                Toast.makeText(NoteActivity.this, "点击了第" + (pos+1) + "条item的Top按钮", Toast.LENGTH_SHORT).show();
                mCursor.moveToPosition(pos);
                int id = mCursor.getInt(mCursor.getColumnIndex(NoteDbAdapter.COL_ID));
                Note editNote = mNoteDbAdapter.fetchNoteById(id);
                editNote.setDateTime(DateUtil.formatDateTime());
                mNoteDbAdapter.updateNote(editNote);
                mCursor = mNoteDbAdapter.fetchAllNotes();
                mNoteAdapter.changeCursor(mCursor);
            }
        });

大功告成,如果想要看详细代码,或者有什么建议可以到Github上给我发Issue或者直接在站内给我留言哦,记得star哦

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,997评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,603评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,359评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,309评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,346评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,258评论 1 300
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,122评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,970评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,403评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,596评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,769评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,464评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,075评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,705评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,848评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,831评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,678评论 2 354

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,091评论 25 707
  • 内容抽屉菜单ListViewWebViewSwitchButton按钮点赞按钮进度条TabLayout图标下拉刷新...
    皇小弟阅读 46,756评论 22 665
  • 有人看破红尘 更多的是在红尘里飘有的人 而仅有为数不多的几个看透红尘走上心灵之旅 心灵之旅的最终产物就是孤独 人本...
    钱老师碎碎念阅读 107评论 0 2
  • 今天林丹出界,刘恺威终于松了口气。 不少姑娘都曾面临谢杏芳一样的痛楚。 收集了15句真挚动容的内心独白: “我以为...
    Jukie阅读 2,828评论 4 1
  • 半夜里睡不着,看着窗外的夜空,晴朗的夜幕上点点星光透过片片云波似乎在诉说着什么,那偶尔经过的飞机如同流星般从...
    流光苏影阅读 230评论 0 4