我试图通过使用Content Provider和CursorAdapter从幻灯片动画中异步删除ListView项目,但是当我通过调用Content Provider中的方法删除项目时,删除的数据被删除的视图重新出现在屏幕。数据本身已从数据库中正确删除,但我无法修复已删除视图的闪烁问题。如果我使用数据库同步删除,则不存在闪烁问题。我试图实现解决方案,这些解决方案已在以下文章中提出,但无法解决问题:
我发布了一个代码示例,使问题更具实证性。任何帮助都会受到很多赞赏:)。
类AndroidListViewCursorAdaptorActivity:
package com.example.keit.mytestapp;
import android.app.Activity;
import android.content.CursorLoader;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.Toast;
import com.example.keit.mytestapp.contentprovider.CustomContentProvider;
public class AndroidListViewCursorAdaptorActivity extends Activity implements android.app.LoaderManager.LoaderCallbacks<Cursor> {
private CountriesDbAdapter dbHelper;
private MyCursorAdapter dataAdapter;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
dbHelper = new CountriesDbAdapter(this);
dbHelper.open();
//Clean all data
dbHelper.deleteAllCountries();
//Add some data
dbHelper.insertSomeCountries();
//Generate ListView from SQLite Database
displayListView();
}
private void displayListView() {
Cursor cursor = dbHelper.fetchAllCountries();
// the XML defined views which the data will be bound to
int[] to = new int[] {
R.id.code
};
ListView listView = (ListView) findViewById(R.id.listView1);
// CursorLoader and CursorAdapter are initialized.
getLoaderManager().initLoader(0, null, this);
dataAdapter = new MyCursorAdapter(this, null, 1, listView) {
@Override
public void deleteRow(long id) {
Log.i("DELETE", "Country code: " + id);
//dbHelper.deleteCountry(id);
Uri uri = Uri.parse(CustomContentProvider.CONTENT_URI + "/"
+ id);
getContentResolver().delete(uri, null, null);
}
};
listView.setAdapter(dataAdapter);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> listView, View view,
int position, long id) {
dataAdapter.setSelectedItemId(id);
Cursor cursor = (Cursor) listView.getItemAtPosition(position);
// Get the state's capital from this row in the database.
String countryCode =
cursor.getString(cursor.getColumnIndexOrThrow("code"));
Toast.makeText(getApplicationContext(),
countryCode, Toast.LENGTH_SHORT).show();
}
});
}
// Creates a new loader after the initLoader () call.
@Override
public android.content.Loader<Cursor> onCreateLoader(int id, Bundle args) {
String[] projection = {
CountriesDbAdapter.KEY_ROWID,
CountriesDbAdapter.KEY_CODE,
CountriesDbAdapter.KEY_NAME,
CountriesDbAdapter.KEY_CONTINENT,
CountriesDbAdapter.KEY_REGION
};
CursorLoader cursorLoader = new CursorLoader(this,
CustomContentProvider.CONTENT_URI, projection, null, null, null);
return cursorLoader;
}
@Override
public void onLoadFinished(android.content.Loader<Cursor> loader, Cursor cursor) {
dataAdapter.swapCursor(cursor);
}
@Override
public void onLoaderReset(android.content.Loader<Cursor> loader) {
dataAdapter.swapCursor(null);
}
}
包中的CustomerContentProvider类&#34; contentprovider&#34;:
package com.example.keit.mytestapp.contentprovider;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;
import com.example.keit.mytestapp.CountriesDbAdapter;
import com.example.keit.mytestapp.DatabaseHelper;
public class CustomContentProvider extends android.content.ContentProvider {
// database
private SQLiteOpenHelper dbHelper;
// used for the UriMacher
private static final int DEBTS = 10;
private static final int DEBT_ID = 20;
private static final String AUTHORITY = "com.example.keit.mytestapp.contentprovider";
private static final String BASE_PATH = "mytestapp";
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY
+ "/" + BASE_PATH);
public static final String CONTENT_TYPE = ContentResolver.CURSOR_DIR_BASE_TYPE
+ "/mytestapp";
public static final String CONTENT_ITEM_TYPE = ContentResolver.CURSOR_ITEM_BASE_TYPE
+ "/countries";
private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
sURIMatcher.addURI(AUTHORITY, BASE_PATH, DEBTS);
sURIMatcher.addURI(AUTHORITY, BASE_PATH + "/#", DEBT_ID);
}
@Override
public boolean onCreate() {
dbHelper = new DatabaseHelper(getContext());
return false;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// Using SQLiteQueryBuilder instead of query() method
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
// Set the table
queryBuilder.setTables(CountriesDbAdapter.SQLITE_TABLE);
int uriType = sURIMatcher.match(uri);
switch (uriType) {
case DEBTS:
break;
case DEBT_ID:
// adding the ID to the original query
queryBuilder.appendWhere(CountriesDbAdapter.KEY_ROWID + "="
+ uri.getLastPathSegment());
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
SQLiteDatabase db = dbHelper.getWritableDatabase();
Cursor cursor = queryBuilder.query(db, projection, selection,
selectionArgs, null, null, sortOrder);
// make sure that potential listeners are getting notified
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor;
}
@Override
public String getType(Uri uri) {
return null;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
int uriType = sURIMatcher.match(uri);
SQLiteDatabase sqlDB = dbHelper.getWritableDatabase();
int rowsDeleted = 0;
long id = 0;
switch (uriType) {
case DEBTS:
id = sqlDB.insert(CountriesDbAdapter.SQLITE_TABLE, null, values);
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return Uri.parse(BASE_PATH + "/" + id);
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
int uriType = sURIMatcher.match(uri);
SQLiteDatabase sqlDB = dbHelper.getWritableDatabase();
int rowsDeleted = 0;
switch (uriType) {
case DEBTS:
rowsDeleted = sqlDB.delete(CountriesDbAdapter.SQLITE_TABLE, selection,
selectionArgs);
Log.i("MyActivity", "DEBTS ");
break;
case DEBT_ID:
String id = uri.getLastPathSegment();
if (TextUtils.isEmpty(selection)) {
rowsDeleted = sqlDB.delete(CountriesDbAdapter.SQLITE_TABLE,
CountriesDbAdapter.KEY_ROWID + "=" + id,
null);
Log.i("MyActivity", "DEBT_ID");
} else {
rowsDeleted = sqlDB.delete(CountriesDbAdapter.SQLITE_TABLE,
CountriesDbAdapter.KEY_ROWID + "=" + id
+ " and " + selection,
selectionArgs);
Log.i("MyActivity", "ELSE");
}
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return rowsDeleted;
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
int uriType = sURIMatcher.match(uri);
SQLiteDatabase sqlDB = dbHelper.getWritableDatabase();
int rowsUpdated = 0;
switch (uriType) {
case DEBTS:
rowsUpdated = sqlDB.update(CountriesDbAdapter.SQLITE_TABLE,
values,
selection,
selectionArgs);
break;
case DEBT_ID:
String id = uri.getLastPathSegment();
if (TextUtils.isEmpty(selection)) {
rowsUpdated = sqlDB.update(CountriesDbAdapter.SQLITE_TABLE,
values,
CountriesDbAdapter.KEY_ROWID + "=" + id,
null);
} else {
rowsUpdated = sqlDB.update(CountriesDbAdapter.SQLITE_TABLE,
values,
CountriesDbAdapter.KEY_ROWID + "=" + id
+ " and "
+ selection,
selectionArgs);
}
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return rowsUpdated;
}
}
类CountriesDbAdapter: 包com.example.keit.mytestapp;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
public class CountriesDbAdapter {
public static final String KEY_ROWID = "_id";
public static final String KEY_CODE = "code";
public static final String KEY_NAME = "name";
public static final String KEY_CONTINENT = "continent";
public static final String KEY_REGION = "region";
private static final String TAG = "CountriesDbAdapter";
private DatabaseHelper mDbHelper;
private SQLiteDatabase mDb;
public static final String DATABASE_NAME = "World";
public static final String SQLITE_TABLE = "Country";
public static final int DATABASE_VERSION = 1;
private final Context mCtx;
public static final String DATABASE_CREATE =
"CREATE TABLE if not exists " + SQLITE_TABLE + " (" +
KEY_ROWID + " integer PRIMARY KEY autoincrement," +
KEY_CODE + "," +
KEY_NAME + "," +
KEY_CONTINENT + "," +
KEY_REGION + "," +
" UNIQUE (" + KEY_CODE +"));";
private static class DatabaseHelper extends SQLiteOpenHelper {
DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
Log.w(TAG, DATABASE_CREATE);
db.execSQL(DATABASE_CREATE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.w(TAG, "Upgrading database from version " + oldVersion + " to "
+ newVersion + ", which will destroy all old data");
db.execSQL("DROP TABLE IF EXISTS " + SQLITE_TABLE);
onCreate(db);
}
}
public CountriesDbAdapter(Context ctx) {
this.mCtx = ctx;
}
public CountriesDbAdapter open() throws SQLException {
mDbHelper = new DatabaseHelper(mCtx);
mDb = mDbHelper.getWritableDatabase();
return this;
}
public void close() {
if (mDbHelper != null) {
mDbHelper.close();
}
}
public long createCountry(String code, String name,
String continent, String region) {
ContentValues initialValues = new ContentValues();
initialValues.put(KEY_CODE, code);
initialValues.put(KEY_NAME, name);
initialValues.put(KEY_CONTINENT, continent);
initialValues.put(KEY_REGION, region);
return mDb.insert(SQLITE_TABLE, null, initialValues);
}
public void deleteCountry (long id) {
int doneDelete = 0;
doneDelete = mDb.delete(SQLITE_TABLE, "_id" + "=" + id , null);
}
public boolean deleteAllCountries() {
int doneDelete = 0;
doneDelete = mDb.delete(SQLITE_TABLE, null , null);
Log.w(TAG, Integer.toString(doneDelete));
return doneDelete > 0;
}
public Cursor fetchCountriesByName(String inputText) throws SQLException {
Log.w(TAG, inputText);
Cursor mCursor = null;
if (inputText == null || inputText.length () == 0) {
mCursor = mDb.query(SQLITE_TABLE, new String[] {KEY_ROWID,
KEY_CODE, KEY_NAME, KEY_CONTINENT, KEY_REGION},
null, null, null, null, null);
}
else {
mCursor = mDb.query(true, SQLITE_TABLE, new String[] {KEY_ROWID,
KEY_CODE, KEY_NAME, KEY_CONTINENT, KEY_REGION},
KEY_NAME + " like '%" + inputText + "%'", null,
null, null, null, null);
}
if (mCursor != null) {
mCursor.moveToFirst();
}
return mCursor;
}
public Cursor fetchAllCountries() {
Cursor mCursor = mDb.query(SQLITE_TABLE, new String[] {KEY_ROWID,
KEY_CODE, KEY_NAME, KEY_CONTINENT, KEY_REGION},
null, null, null, null, null);
if (mCursor != null) {
mCursor.moveToFirst();
}
return mCursor;
}
public void insertSomeCountries() {
createCountry("AFG","Afghanistan","Asia","Southern and Central Asia");
createCountry("ALB","Albania","Europe","Southern Europe");
createCountry("DZA","Algeria","Africa","Northern Africa");
createCountry("ASM","American Samoa","Oceania","Polynesia");
createCountry("AND","Andorra","Europe","Southern Europe");
createCountry("AGO","Angola","Africa","Central Africa");
createCountry("AIA","Anguilla","North America","Caribbean");
}
}
Class MyCursorAdapter:
package com.example.keit.mytestapp;
import android.content.Context;
import android.database.Cursor;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.CursorAdapter;
import android.widget.ImageButton;
import android.widget.ListView;
import android.widget.TextView;
import java.util.HashMap;
public class MyCursorAdapter extends CursorAdapter {
private long selectedItemId;
private long previouslySelectedItemId;
private ListView adapterListView;
private static final int MOVE_DURATION = 300;
HashMap<Long, Integer> mItemIdTopMap = new HashMap<Long, Integer>();
public MyCursorAdapter(Context context, Cursor cursor, int flags) {
super(context, cursor, 0);
}
// A customer constructor for passing ListView inside a view in addition to default parameters.
public MyCursorAdapter(Context context, Cursor cursor, int flags, ListView listView) {
super(context, cursor, 0);
adapterListView = listView;
}
// The newView method is used to inflate a new view and return it,
// you don't bind any data to the view at this point.
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return LayoutInflater.from(context).inflate(R.layout.country_info, parent, false);
}
// The bindView method is used to bind all data to a given view
// such as setting the text on a TextView.
@Override
public void bindView(View view, final Context context, Cursor cursor) {
TextView countryCodeView = (TextView) view.findViewById(R.id.code);
String countryCode = cursor.getString(cursor.getColumnIndexOrThrow(CountriesDbAdapter.KEY_CODE));
countryCodeView.setText(countryCode);
// Listener is created for "Delete" button.
ImageButton deleteItem = (ImageButton) view.findViewById(R.id.delete_item);
deleteItem.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(final View view) {
Log.i("onClick", "Executed");
adapterListView.requestDisallowInterceptTouchEvent(true); // Will disallow touch events.
adapterListView.setEnabled(false); // Will disable list view (e.g. to avoid scrolling).
final View activeView = (View) view.getParent(); //ListView item parent view is returned.
if (activeView != null) {
long duration = 800;
float endX = activeView.getWidth();
float endAlpha = 0; // Opacity will be set to 0.
activeView.animate().setDuration(duration).
alpha(endAlpha).translationX(endX).
withEndAction(new Runnable() {
@Override
public void run() {
activeView.setAlpha(1);
activeView.setTranslationX(0);
animateRemoval(adapterListView, activeView);
}
});
Log.i("FAFA", "Animation ended");
// notifyDataSetChanged();
}
}
});
}
// The method sets a global temporary value of selected item's id.
public void setSelectedItemId(long id) {
previouslySelectedItemId = selectedItemId;
selectedItemId = id;
}
// The method returns a global temporary value of selected item's id.
public long getSelectedItemId() {
return selectedItemId;
}
/**
* The method will be always be overwritten by calling class to handle database manipulation in activity.
* @param id
*/
public void deleteRow(long id) {
}
private void animateRemoval(final ListView listview, final View viewToRemove) {
int firstVisiblePosition = listview.getFirstVisiblePosition();
for (int i = 0; i < listview.getChildCount(); ++i) {
View child = listview.getChildAt(i);
if (child != viewToRemove) {
int position = firstVisiblePosition + i;
long itemId = getItemId(position);
mItemIdTopMap.put(itemId, child.getTop());
}
}
final int position = adapterListView.getPositionForView(viewToRemove);
final ViewTreeObserver observer = listview.getViewTreeObserver();
observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
public boolean onPreDraw() {
observer.removeOnPreDrawListener(this);
boolean firstAnimation = true;
int firstVisiblePosition = listview.getFirstVisiblePosition();
for (int i = 0; i < listview.getChildCount(); ++i) {
final View child = listview.getChildAt(i);
int position = firstVisiblePosition + i;
long itemId = getItemId(position);
Integer startTop = mItemIdTopMap.get(itemId);
int top = child.getTop();
if (startTop != null) {
if (startTop != top) {
int delta = startTop - top;
child.setTranslationY(delta);
child.animate().setDuration(MOVE_DURATION).translationY(0);
if (firstAnimation) {
child.animate().withEndAction(new Runnable() {
public void run() {
listview.requestDisallowInterceptTouchEvent(false); // Will allow touch events.
listview.setEnabled(true); // Will enable list view (e.g. to avoid scrolling).
}
});
firstAnimation = false;
}
}
} else {
// Animate new views along with the others. The catch is that they did not
// exist in the start state, so we must calculate their starting position
// based on neighboring views.
int childHeight = child.getHeight() + listview.getDividerHeight();
startTop = top + (i > 0 ? childHeight : -childHeight);
int delta = startTop - top;
child.setTranslationY(delta);
child.animate().setDuration(MOVE_DURATION).translationY(0);
if (firstAnimation) {
child.animate().withEndAction(new Runnable() {
public void run() {
listview.requestDisallowInterceptTouchEvent(false); // Will allow touch events.
listview.setEnabled(true); // Will enable list view (e.g. to avoid scrolling).
}
});
firstAnimation = false;
}
}
}
CustomCursorWrapper cursorWrapper = new CustomCursorWrapper(getCursor(), position);
swapCursor(cursorWrapper);
deleteRow(selectedItemId);
mItemIdTopMap.clear();
return true;
}
});
}
}
Class CustomCursorWrapper:
package com.example.keit.mytestapp;
import android.database.Cursor;
import android.database.CursorWrapper;
public class CustomCursorWrapper extends CursorWrapper
{
private int mVirtualPosition;
private int mHiddenPosition;
public CustomCursorWrapper(Cursor cursor, int hiddenPosition)
{
super(cursor);
mVirtualPosition = -1;
mHiddenPosition = hiddenPosition;
}
@Override
public int getCount()
{
return super.getCount() - 1;
}
@Override
public int getPosition()
{
return mVirtualPosition;
}
@Override
public boolean move(int offset)
{
return moveToPosition(getPosition() + offset);
}
@Override
public boolean moveToFirst()
{
return moveToPosition(0);
}
@Override
public boolean moveToLast()
{
return moveToPosition(getCount() - 1);
}
@Override
public boolean moveToNext()
{
return moveToPosition(getPosition() + 1);
}
@Override
public boolean moveToPosition(int position)
{
mVirtualPosition = position;
int cursorPosition = position;
if (cursorPosition >= mHiddenPosition)
{
cursorPosition++;
}
return super.moveToPosition(cursorPosition);
}
@Override
public boolean moveToPrevious()
{
return moveToPosition(getPosition() - 1);
}
@Override
public boolean isBeforeFirst()
{
return getPosition() == -1 || getCount() == 0;
}
@Override
public boolean isFirst()
{
return getPosition() == 0 && getCount() != 0;
}
@Override
public boolean isLast()
{
int count = getCount();
return getPosition() == (count - 1) && count != 0;
}
@Override
public boolean isAfterLast()
{
int count = getCount();
return getPosition() == count || count == 0;
}
}
Class DatabaseHelper:
package com.example.keit.mytestapp;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
public class DatabaseHelper extends SQLiteOpenHelper {
// If you change the database schema, you must increment the database version.
public DatabaseHelper(Context context) {
super(context, CountriesDbAdapter.DATABASE_NAME, null, CountriesDbAdapter.DATABASE_VERSION);
}
// Creates SQL Lite database tables
public void onCreate(SQLiteDatabase db) {
db.execSQL(CountriesDbAdapter.DATABASE_CREATE);
}
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// This database is only a cache for online data, so its upgrade policy is
// to simply to discard the data and start over
//db.execSQL(DatabaseContract.Debts.SQL_DELETE_TABLE);
onCreate(db);
}
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
onUpgrade(db, oldVersion, newVersion);
}
}
country_info.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:descendantFocusability="blocksDescendants"
android:background="@android:color/darker_gray"
android:padding="6dip">
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:text="Code: "
android:textAppearance="?android:attr/textAppearanceMedium" />
<TextView
android:id="@+id/code"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TextView" />
<ImageButton
android:id="@+id/delete_item"
android:layout_width="40dp"
android:layout_height="fill_parent"
android:scaleType="centerInside"
android:src="@mipmap/ic_launcher"
android:focusable="false"
android:focusableInTouchMode="false"
android:background="@android:color/transparent"/>
</LinearLayout>
main.xml中:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent" android:layout_height="fill_parent"
android:orientation="vertical">
<TextView android:layout_width="fill_parent"
android:layout_height="wrap_content" android:padding="10dp"
android:text="@string/some_text" android:textSize="20sp" />
<ListView android:id="@+id/listView1" android:layout_width="fill_parent"
android:layout_height="fill_parent" />
</LinearLayout>