欢迎访问我的个人博客转发请注明出处:http://www.wensibo.top/2017/03/08/写一个小便签/
一直想要写一个便签应用,因为我一直在用的是锤子便签和一加便签,觉得体验还是可以的,但是始终觉得自己也是可以做的,这段时间因为有些事情耽误了,项目前几天做好了,一直没有时间上传到github (各位大爷路过给个star呗😉) ,今天趁着有时间顺便写了这篇文章,介绍一下写这个便签时遇到的一些问题,当作是跟大家一起分享吧!
功能
- 实现最基本的增加、删除、修改便签
- 便签能够保存到本地
- 主界面采用Material Design设计风格,相对美观(勿喷)
- RecyclerView上下滑动可以自动隐藏Toolbar,以及Floating Action Button
- RecyclerView的Item可以实现如QQ的侧滑效果,可以通过点击删除、置顶进行编辑
- 可以设置便签为星✨,那么将会在便签界面左边增加一个红色的标志,以提醒用户此便签为重要便签
- 变迁主界面标有时间,并且按照编辑时间从新到旧进行排列
效果
效果图
Talk is cheap,show me your code
如何在RecyclerView中使用本地Sqlite数据库数据
RecyclerView是google在推出Material Design时着重介绍的一个组件,它对传统的ListView已经可以说是完全代替了,功能强大是他的一个最大优点,但是有一点局限的就是我们自定义的RecyclerView必须继承重写 RecyclerView.Adapter 和 RecyclerView.ViewHolder,虽然在里面我们可以随意重写方法,但是可以发现如果我们使用数据库作为数据源,RecyclerView.Adapter是无法支持读取Cursor的,但是开源的力量又再次显现了,直接给上github地址,但是我们这里只需要复用其中的两个文件就行了,容我娓娓道来。
1、添加下面的RecyclerViewCursorAdapter 和 CursorFilter到工程中
由于代码太长影响排版,我就直接附上下载链接
RecyclerViewCursorAdapter
CursorFilter
2、新建自定义的Adapter并且继承RecyclerViewCursorAdapter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
| 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); }
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中这样用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| 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);
|
思路很简单,只需要记录RecyclerView向下滑动(手指向上滑动)时移动的距离,超过一定范围时就会调用Toolbar以及Floating Action Button的animate().translationY方法,令其在Y轴方向上移动,当RecyclerView向上滑动(手指向下滑动)时又会反过来回到初始状态,并且当滑动到RecyclerView底部时会强制Toolbar和FAB回到初始状态,上代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| 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; }
@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{ if (scrolledDistance > HIDE_THRESHOLD && controlsVisible) { onHide(); controlsVisible = false; scrolledDistance = 0; } else if (scrolledDistance < -HIDE_THRESHOLD && !controlsVisible) { onShow(); scrolledDistance = 0; controlsVisible = true; } } if ((controlsVisible && dy > 0) || (!controlsVisible && dy < 0)) { scrolledDistance += dy; } }
public abstract void onHide(); public abstract void onShow(); }
|
在Avtivity中使用回掉方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| 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(); }
|
特别注意布局文件
如果你发现你运行的效果像下面的截图一样的话,那你肯定是因为布局文件上少写了这两句
1 2
| android:clipToPadding="false" android:paddingTop="?attr/actionBarSize"
|
bug截图
完整的布局代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| <?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中设置打开侧滑菜单,并且可以设置菜单在左还是在右
1
| ((SwipeMenuLayout) holder.root.findViewById(R.id.swipeMenuLayout)).setIos(false).setLeftSwipe(false).setSwipeEnable(true);
|
3、在Activity中设置动作事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| 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哦
v1.5.2