介绍:首先这个应用是结合了我之前做的Dummynote,那个note的删除主要靠的是长按后的ContextMenu
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { menu.add(0, MENU_DELETE, 0, "删除记事"); menu.setHeaderTitle("要如何处理这笔记录?"); super.onCreateContextMenu(menu, v, menuInfo); }
这样做不算差。但是如果可以实现滑动删除的话,效果会更好。于是我借鉴了这篇文章http://blog.csdn.net/xiaanming/article/details/18311877
文章很详细,注释也很多。他一步步告诉你滑动删除具体是怎么实现的。
难点:
问题一、当用户要删除某一行时,我只能得到该行的行号,但不知道该行在数据库中的_id值是多少?
我因此使用了带 limit 和 offest 的语句
把
db.delete(DATABASE_TABLE,ROWID+"="+rowId,null);改成了
db.execSQL("delete from tids where _id in(select _id from tids limit 1 offset "+rowId+");"); //是从数据库中的第rowId+1条数据开始查询一条数据,即第rouId+1条
知识:
取前十行
select user_id from udb_user limit 10;
跳过前10行,再取前15行
select user_id from udb_user limit 10,15;跳过前15行,再取前10行
select user_id from udb_user limit 10 offest 15;
Healthyids
package demo.xpf.healthytids; import android.content.Intent; import android.os.Bundle; import android.app.Activity; import android.content.Intent; import android.database.Cursor; import android.support.v4.widget.SimpleCursorAdapter; import android.text.StaticLayout; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.AdapterView; import android.widget.Toast; import android.widget.AdapterView.OnItemClickListener; import demo.xpf.healthytids.SwipeDismissListView.OnDismissCallback; public class Healthytids extends Activity { private static final String TAG = "tids"; private SwipeDismissListView swipeDismissListView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); //getListView().setEmptyView(findViewById(R.id.empty));//设置列表为空时的显示内容为 empty文本框 setAdapter(); init(); } private TidsDbAdapter mDbHelper ; private Cursor mTidCursor; private void setAdapter(){ mDbHelper = new TidsDbAdapter(this); mDbHelper.open(); fillData(); } //fillData()刷新数据 private void fillData(){ mTidCursor = mDbHelper.getall(); Log.d(TAG, "filldata "+mTidCursor); startManagingCursor(mTidCursor); String[] from = new String[]{TidsDbAdapter.NOTE}; int[] to = new int[]{android.R.id.text1};//android.R.id.text1是Android 框架里面的TextView的一个标识符 //Now create a simple cursor adapter SimpleCursorAdapter adapter = new SimpleCursorAdapter(this, android.R.layout.simple_list_item_1, mTidCursor, from, to); swipeDismissListView = (SwipeDismissListView) findViewById(R.id.swipeDismissListView); swipeDismissListView.setAdapter(adapter); } private void init() { swipeDismissListView.setOnDismissCallback(new OnDismissCallback() { @Override public void onDismiss(int dismissPosition) { //adapter.remove(adapter.getItem(dismissPosition)); boolean T=mDbHelper.delete(dismissPosition); Log.d(TAG, "delete"+dismissPosition+" "+T); fillData(); } }); swipeDismissListView.setOnItemClickListener(new OnItemClickListener() { private static final int ACTIVITY_EDIT = 0x1001; public void onItemClick(AdapterView<?> parent, View view,int position, long id) { Intent intent = new Intent(Healthytids.this, NoteEdit.class); intent.putExtra(TidsDbAdapter.ROWID, id);//id就是_id值,不是当前的列表项值。代表绑定到这个“ListView”项目的“row ID” startActivityForResult(intent, ACTIVITY_EDIT); } }); } //Add an entity protected static final int MENU_INSERT = Menu.FIRST; @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. super.onCreateOptionsMenu(menu); menu.add(0, MENU_INSERT, 0, "新增记事"); return super.onCreateOptionsMenu(menu); } public boolean onOptionsItemSelected(MenuItem item){ switch(item.getItemId()){ case MENU_INSERT: String noteName = "New Note "; mDbHelper.create(noteName); fillData(); return true; } return super.onOptionsItemSelected(item); } }
SwipeDismissListView
package demo.xpf.healthytids; import static com.nineoldandroids.view.ViewHelper.setAlpha; import static com.nineoldandroids.view.ViewHelper.setTranslationX; import android.content.Context; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.ListView; import com.nineoldandroids.animation.Animator; import com.nineoldandroids.animation.AnimatorListenerAdapter; import com.nineoldandroids.animation.ValueAnimator; import com.nineoldandroids.view.ViewHelper; import com.nineoldandroids.view.ViewPropertyAnimator; /** * @blog http://blog.csdn.net/xiaanming * * @author xiaanming * */ @SuppressWarnings("unused") public class SwipeDismissListView extends ListView { /** * 认为是用户滑动的最小距离 */ private int mSlop; /** * 滑动的最小速度 */ private int mMinFlingVelocity; /** * 滑动的最大速度 */ private int mMaxFlingVelocity; /** * 执行动画的时间 */ protected long mAnimationTime = 150; /** * 用来标记用户是否正在滑动中 */ private boolean mSwiping; /** * 滑动速度检测类 */ private VelocityTracker mVelocityTracker; /** * 手指按下的position */ private int mDownPosition; /** * 按下的item对应的View */ private View mDownView; private float mDownX; private float mDownY; /** * item的宽度 */ private int mViewWidth; /** * 当ListView的Item滑出界面回调的接口 */ private OnDismissCallback onDismissCallback; /** * 设置动画时间 * * @param mAnimationTime */ public void setmAnimationTime(long mAnimationTime) { this.mAnimationTime = mAnimationTime; } /** * 设置删除回调接口 * * @param onDismissCallback */ public void setOnDismissCallback(OnDismissCallback onDismissCallback) { this.onDismissCallback = onDismissCallback; } public SwipeDismissListView(Context context) { this(context, null); } public SwipeDismissListView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public SwipeDismissListView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); ViewConfiguration vc = ViewConfiguration.get(context); mSlop = vc.getScaledTouchSlop(); mMinFlingVelocity = vc.getScaledMinimumFlingVelocity() * 8; //获取滑动的最小速度 mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity(); //获取滑动的最大速度 } @Override public boolean onTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: handleActionDown(ev); break; case MotionEvent.ACTION_MOVE: return handleActionMove(ev); case MotionEvent.ACTION_UP: handleActionUp(ev); break; } return super.onTouchEvent(ev); } /** * 按下事件处理 * * @param ev * @return */ private void handleActionDown(MotionEvent ev) { mDownX = ev.getX(); mDownY = ev.getY(); mDownPosition = pointToPosition((int) mDownX, (int) mDownY); if (mDownPosition == AdapterView.INVALID_POSITION) { return; } mDownView = getChildAt(mDownPosition - getFirstVisiblePosition()); if (mDownView != null) { mViewWidth = mDownView.getWidth(); } //加入速度检测 mVelocityTracker = VelocityTracker.obtain(); mVelocityTracker.addMovement(ev); } /** * 处理手指滑动的方法 * * @param ev * @return */ private boolean handleActionMove(MotionEvent ev) { if (mVelocityTracker == null || mDownView == null) { return super.onTouchEvent(ev); } // 获取X方向滑动的距离 float deltaX = ev.getX() - mDownX; float deltaY = ev.getY() - mDownY; // X方向滑动的距离大于mSlop并且Y方向滑动的距离小于mSlop,表示可以滑动 if (Math.abs(deltaX) > mSlop && Math.abs(deltaY) < mSlop) { mSwiping = true; //当手指滑动item,取消item的点击事件,不然我们滑动Item也伴随着item点击事件的发生 MotionEvent cancelEvent = MotionEvent.obtain(ev); cancelEvent.setAction(MotionEvent.ACTION_CANCEL | (ev.getActionIndex()<< MotionEvent.ACTION_POINTER_INDEX_SHIFT)); onTouchEvent(cancelEvent); } if (mSwiping) { // 跟谁手指移动item ViewHelper.setTranslationX(mDownView, deltaX); // 透明度渐变 ViewHelper.setAlpha(mDownView, Math.max(0f, Math.min(1f, 1f - 2f * Math.abs(deltaX)/ mViewWidth))); // 手指滑动的时候,返回true,表示SwipeDismissListView自己处理onTouchEvent,其他的就交给父类来处理 return true; } return super.onTouchEvent(ev); } /** * 手指抬起的事件处理 * @param ev */ private void handleActionUp(MotionEvent ev) { if (mVelocityTracker == null || mDownView == null|| !mSwiping) { return; } float deltaX = ev.getX() - mDownX; //通过滑动的距离计算出X,Y方向的速度 mVelocityTracker.computeCurrentVelocity(1000); float velocityX = Math.abs(mVelocityTracker.getXVelocity()); float velocityY = Math.abs(mVelocityTracker.getYVelocity()); boolean dismiss = false; //item是否要滑出屏幕 boolean dismissRight = false;//是否往右边删除 //当拖动item的距离大于item的一半,item滑出屏幕 if (Math.abs(deltaX) > mViewWidth / 2) { dismiss = true; dismissRight = deltaX > 0; //手指在屏幕滑动的速度在某个范围内,也使得item滑出屏幕 } else if (mMinFlingVelocity <= velocityX && velocityX <= mMaxFlingVelocity && velocityY < velocityX) { dismiss = true; dismissRight = mVelocityTracker.getXVelocity() > 0; } if (dismiss) { ViewPropertyAnimator.animate(mDownView) .translationX(dismissRight ? mViewWidth : -mViewWidth)//X轴方向的移动距离 .alpha(0) .setDuration(mAnimationTime) .setListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { //Item滑出界面之后执行删除 performDismiss(mDownView, mDownPosition); } }); } else { //将item滑动至开始位置 ViewPropertyAnimator.animate(mDownView) .translationX(0) .alpha(1) .setDuration(mAnimationTime).setListener(null); } //移除速度检测 if(mVelocityTracker != null){ mVelocityTracker.recycle(); mVelocityTracker = null; } mSwiping = false; } /** * 在此方法中执行item删除之后,其他的item向上或者向下滚动的动画,并且将position回调到方法onDismiss()中 * @param dismissView * @param dismissPosition */ private void performDismiss(final View dismissView, final int dismissPosition) { final ViewGroup.LayoutParams lp = dismissView.getLayoutParams();//获取item的布局参数 final int originalHeight = dismissView.getHeight();//item的高度 ValueAnimator animator = ValueAnimator.ofInt(originalHeight, 0).setDuration(mAnimationTime); animator.start(); animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (onDismissCallback != null) { onDismissCallback.onDismiss(dismissPosition); } //这段代码很重要,因为我们并没有将item从ListView中移除,而是将item的高度设置为0 //所以我们在动画执行完毕之后将item设置回来 ViewHelper.setAlpha(dismissView, 1f); ViewHelper.setTranslationX(dismissView, 0); ViewGroup.LayoutParams lp = dismissView.getLayoutParams(); lp.height = originalHeight; dismissView.setLayoutParams(lp); } }); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { //这段代码的效果是ListView删除某item之后,其他的item向上滑动的效果 lp.height = (Integer) valueAnimator.getAnimatedValue(); dismissView.setLayoutParams(lp); } }); } /** * 删除的回调接口 * * @author xiaanming * */ public interface OnDismissCallback { public void onDismiss(int dismissPosition); } }
TidsDbAdapter
package demo.xpf.healthytids; import java.util.Date; import android.content.ContentValues; import android.content.Context; import android.database.SQLException; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.database.*; import android.util.Log; public class TidsDbAdapter { private static final String TAG = "tids"; private static final String DATABASE_NAME = "tids.db"; private static final int DATABASE_VERSION = 1 ; private static final String DATABASE_TABLE = "tids"; private static final String DATABASE_CREATE = "create table tids(" +"_id INTEGER PRIMARY KEY," +"note TEXT NOT NULL," +"created INTEGER" +");"; private static class DatabaseHelper extends SQLiteOpenHelper{ public DatabaseHelper(Context context) { super(context, DATABASE_NAME,null,DATABASE_VERSION); // TODO Auto-generated constructor stub } @Override//创建数据表 public void onCreate(SQLiteDatabase db) { // TODO Auto-generated method stub try { db.execSQL(DATABASE_CREATE); Log.d(TAG, "onCreate !"); } catch (Exception e) { Log.d(TAG, e.toString()); } } @Override//更新数据表 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { try { // TODO Auto-generated method stub db.execSQL("DROP TABLE IF EXISTS "+DATABASE_TABLE); onCreate(db); Log.d(TAG, "inUpgrade!"); } catch (Exception e) { Log.d(TAG, "onUpgrade failure!"); } } } private Context mCtx = null; //抽象界面 private DatabaseHelper dbHelper; //数据库工具类 private SQLiteDatabase db; //数据库类 /** COnstructor**/ public TidsDbAdapter(Context ctx){ this.mCtx=ctx; } public TidsDbAdapter open () throws SQLException{ dbHelper = new DatabaseHelper(mCtx); db = dbHelper.getWritableDatabase();//数据库不存在就创造一个,若存在就根据版本库来决定是否更新数据库 return this; } public void close(){ dbHelper.close(); } public static final String ROWID = "_id"; public static final String NOTE = "note"; public static final String CREATED = "created"; //query single entry public Cursor get(long rowId)throws SQLException { Cursor mCursor = db.query(DATABASE_TABLE,//Which table to select new String[]{ROWID,NOTE,CREATED},//Which columns to return ROWID+"="+rowId, //Where clause null, //Where arguments null, //Group By clause null, //Having clause null //Order-by clause ); if(mCursor!=null){ mCursor.moveToFirst();//指针移到一开始 } return mCursor; } //query single entry public Cursor getall() { return db.query(DATABASE_TABLE,//Which table to select new String[]{ROWID,NOTE,CREATED},//Which columns to return null, //Where clause null, //Where arguments null, //Group By clause null, //Having clause null //Order-by clause ); } //add an entity public long create(String Note){ Date now = new Date(); ContentValues args = new ContentValues(); args.put(NOTE, Note); args.put(CREATED, now.getDate()); return db.insert(DATABASE_TABLE, null, args); } //remove an entity public boolean delete(long rowId){ Log.d(TAG, "delete func"+rowId); db.execSQL("delete from tids where _id in(select _id from tids limit 1 offset "+rowId+");"); //是从数据库中的第rowId+1条数据开始查询一条数据,即第rouId+1条。 return true; //return db.delete(DATABASE_TABLE,ROWID+"="+rowId,null)>0;//delete失败返回0 } //update an entity public boolean update(long rowId,String note){ ContentValues args = new ContentValues(); args.put(NOTE, note); return db.update(DATABASE_TABLE, args, ROWID+"="+rowId, null)>0; } }
NoteEdit
package demo.xpf.healthytids; import android.app.Activity; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.EditText; import demo.xpf.healthytids.TidsDbAdapter; public class NoteEdit extends Activity{ private static final String TAG = "tids"; private TidsDbAdapter mDbHelper; private SQLiteDatabase db; @Override protected void onCreate(Bundle SavedInstanceState){ super.onCreate(SavedInstanceState); mDbHelper = new TidsDbAdapter(this); mDbHelper.open(); setContentView(R.layout.note_edit); Log.d(TAG, "edit"); findViews(); showViews(SavedInstanceState); } private EditText field_note; private Button button_confirm; private void findViews() { // TODO Auto-generated method stub field_note = (EditText) findViewById(R.id.note); button_confirm = (Button) findViewById(R.id.confirm); } private Long mRowId; private void showViews(Bundle savedInstanceState) { mRowId = savedInstanceState != null? savedInstanceState.getLong(TidsDbAdapter.ROWID) : null; /*saveInstanceState 这个“bundle”数据容器中,取出Activity上一次处于“stop”状态是,键值为“_id”(NotesDbAdapter.ROWID)的内容值。如果这个ACtivity是 全新打开的或是之前的行程已经被回收了,那么“mRouId”的值被设为“null”*/ if (mRowId == null) { Bundle extras = getIntent().getExtras(); mRowId = extras != null ? extras.getLong(TidsDbAdapter.ROWID) : null; Log.d(TAG, "position "+mRowId); /* Cursor mCursor = db.query("tids",new String[]{"_id"}, null, null, null, null, null, mRowId+"1"); while (mCursor!=null) { mRowId = mCursor.getLong(0); //获取第一列的值,第一列的索引从0开始 Log.d(TAG, "id "+mRowId); } */ } populateFields();//根据RowId查询记录,并显示在EditText中 button_confirm.setOnClickListener(new View.OnClickListener(){ @Override public void onClick(View v) { mDbHelper.update(mRowId, field_note.getText().toString()); setResult(RESULT_OK); finish(); } }); } private void populateFields() { if (mRowId != null){ Cursor note = mDbHelper.get(mRowId); startManagingCursor(note); field_note.setText(note.getString(note.getColumnIndexOrThrow(TidsDbAdapter.NOTE))); } } }