介绍:首先这个应用是结合了我之前做的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.youkuaiyun.com/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.youkuaiyun.com/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)));
}
}
}