如果异步删除则会导致ListView项闪烁

时间:2015-10-05 20:37:31

标签: java android listview android-listview

我试图通过使用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>

0 个答案:

没有答案