当前位置: 首页 > 知识库问答 >
问题:

CursorAdapter支持的ListView删除动画在删除时“闪烁”

后焕
2023-03-14

我正在尝试使用SwipeToDismissUndoList库在< code>ListView中实现滑动删除,该库扩展了Roman Nurik的SwipeToDismiss示例。

我的问题是在删除动画。由于< code>ListView由< code>CursorAdapter支持,因此动画会在< code>onAnimationEnd中触发< code>onDismiss回调,但这意味着在< code>CursorAdapter使用delete更新之前,动画已经运行并重置了自身。

对于用户来说,这看起来像是一个闪烁,他们通过将注释扫走来删除注释,然后视图在一瞬间返回,然后消失,因为<code>游标适配器

这是我的<code>OnDismissCallback</code>:

private SwipeDismissList.OnDismissCallback dismissCallback = 
        new SwipeDismissList.OnDismissCallback() {
    @Override
    public SwipeDismissList.Undoable onDismiss(ListView listView, final int position) {
        Cursor c = mAdapter.getCursor();
        c.moveToPosition(position);
        final int id = c.getInt(Query._ID);
        final Item item = Item.findById(getActivity(), id);
        if (Log.LOGV) Log.v("Deleting item: " + item);

        final ContentResolver cr = getActivity().getContentResolver();
        cr.delete(Items.buildItemUri(id), null, null);
        mAdapter.notifyDataSetChanged();

        return new SwipeDismissList.Undoable() {
            public void undo() {
                if (Log.LOGV) Log.v("Restoring Item: " + item);
                ContentValues cv = new ContentValues();
                cv.put(Items._ID, item.getId());
                cv.put(Items.ITEM_CONTENT, item.getContent());
                cr.insert(Items.CONTENT_URI, cv);
            }
        };
    }
};

共有3个答案

任昊阳
2023-03-14

嘿,我有一个类似的问题,像这样解决了,希望它能帮助你:

我用了切特·哈斯在这个开发字节里展示的东西:http://www.youtube.com/watch?v=YCHNAi9kJI4

这与Roman的代码非常相似,但在这里他使用了ViewTreeWatch,因此在您从适配器中删除项目后,但在重新绘制列表之前,您有时间进行动画处理以缩小空白,并且它不会闪烁。另一个区别是,他将Listener设置为适配器中列表的每个视图(项目),而不是在ListView本身上。

这是我的代码示例:

这是ListActivity的onCreate,在这里我将监听器传递给适配器没有什么特别的:

ListAdapterTouchListener listAdapterTouchListener = new ListAdapterTouchListener(getListView());
    listAdapter = new ListAdapter(this,null,false,listAdapterTouchListener);

下面是ListAdapter的一部分(这是我自己的适配器,扩展了CursorAdapter),我在构造函数中传递侦听器,

private View.OnTouchListener onTouchListener;

public ListAdapter(Context context, Cursor c, boolean autoRequery,View.OnTouchListener listener) {
    super(context, c, autoRequery);
    onTouchListener = listener;
}

然后在newView方法中,我将其设置为视图:

@Override
public View newView(final Context context, Cursor cursor, ViewGroup parent) {
    View view = layoutInflater.inflate(R.layout.list_item,parent,false);
    // here should be some viewholder magic to make it faster
    view.setOnTouchListener(onTouchListener);

    return view;
}

听众与视频中显示的代码大致相同,我不使用背景容器,但这只是我的选择。因此,animateRemoval具有有趣的部分,如下所示:

private void animateRemoval(View viewToRemove){
    for(int i=0;i<listView.getChildCount();i++){
        View child = listView.getChildAt(i);
        if(child!=viewToRemove){

        // since I don't have stableIds I use the _id from the sqlite database
        // I'm adding the id to the viewholder in the bindView method in the ListAdapter

            ListAdapter.ViewHolder viewHolder = (ListAdapter.ViewHolder)child.getTag();
            long itemId = viewHolder.id;
            itemIdTopMap.put(itemId, child.getTop());
        }
    }

    // I'm using content provider with LoaderManager in the activity because it's more efficient, I get the id from the viewholder

    ListAdapter.ViewHolder viewHolder = (ListAdapter.ViewHolder)viewToRemove.getTag();
    long removeId = viewHolder.id;

    //here you remove the item

    listView.getContext().getContentResolver().delete(Uri.withAppendedPath(MyContentProvider.CONTENT_ID_URI_BASE,Long.toString(removeId)),null,null);

    // after the removal get a ViewTreeObserver, so you can set a PredrawListener
    // the rest of the code is pretty much the same as in the sample shown in the video

    final ViewTreeObserver observer = listView.getViewTreeObserver();
    observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
        @Override
        public boolean onPreDraw() {
            observer.removeOnPreDrawListener(this);
            boolean firstAnimation = true;
            for(int i=0;i<listView.getChildCount();i++){
                final View child = listView.getChildAt(i);
                ListAdapter.ViewHolder viewHolder = (ListAdapter.ViewHolder)child.getTag();
                long itemId = viewHolder.id;
                Integer startTop = itemIdTopMap.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().setListener(new Animator.AnimatorListener() {
                                @Override
                                public void onAnimationStart(Animator animation) {

                                }

                                @Override
                                public void onAnimationEnd(Animator animation) {
                                        swiping=false;
                                    listView.setEnabled(true);
                                }

                                @Override
                                public void onAnimationCancel(Animator animation) {

                                }

                                @Override
                                public void onAnimationRepeat(Animator animation) {

                                }
                            });
                            firstAnimation=false;
                        }
                    }
                }else{
                    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().setListener(new Animator.AnimatorListener() {
                            @Override
                            public void onAnimationStart(Animator animation) {

                            }

                            @Override
                            public void onAnimationEnd(Animator animation) {
                                swiping=false;
                                listView.setEnabled(true);
                            }

                            @Override
                            public void onAnimationCancel(Animator animation) {

                            }

                            @Override
                            public void onAnimationRepeat(Animator animation) {

                            }
                        });
                        firstAnimation=false;
                    }
                }
            }
            itemIdTopMap.clear();
            return true;
        }
    });
}

希望这对你有帮助,它对我很好!你真的应该看devbyte,它帮了我很多!

莫乐
2023-03-14

我认为SwipeToDismisUndoList不适合基于游标的适配器。因为适配器依赖于内容提供者(setNotificationUri()ynsterContentWatch()...)的更改来更新UI。您不知道数据何时可用。这就是您面临的问题。

我想这有点像一个骗局。您可以使用<code>矩阵Xcursor</code>。

>

  • 在<code>onLoadFinished(Loader,Cursor)</code>中,您保留对从内容提供商返回的游标的引用。您需要稍后手动关闭它
  • 在<code>SwipedSmissList.OnDismissCallback中。onDismiss(),创建新的<code>MatrixCursor,从当前光标复制除要删除的项目之外的所有项目
  • 使用swapCursor()(不是changeCursor())将新创建的矩阵光标设置到适配器。因为<code>swapCursor()
  • 现在UI已经更新,您可以调用getContentResolver()。delete()并实际删除用户想要删除的项目。当内容提供商完成删除数据时,它会通知原始光标重新加载数据
  • 确保关闭您交换的原始光标。例如:

    private Cursor mOrgCursor;
    
    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
        if (mOrgCursor != null)
            mOrgCursor.close();
        mOrgCursor = data;
        mAdapter.changeCursor(mOrgCursor);
    }
    
    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
        if (mOrgCursor != null) {
            mOrgCursor.close();
            mOrgCursor = null;
        }
        mAdapter.changeCursor(null);
    }
    

  • 微生智刚
    2023-03-14

    我知道这个问题已被标记为“已回答”,但正如我在评论中指出的那样,使用MatrixCursor的问题在于它太低效了。复制除要删除的行之外的所有行意味着行删除以线性时间运行(列表视图中的项目数为线性)。对于大数据和较慢的手机,这可能是不可接受的。

    另一种方法是实现您自己的AbstractCursor,它忽略要删除的行。这会导致假行删除在恒定的时间内运行,并且在绘图时的性能损失可以忽略不计。

    示例实现:

    public class CursorWithDelete extends AbstractCursor {
    
    private Cursor cursor;
    private int posToIgnore;
    
    public CursorWithDelete(Cursor cursor, int posToRemove)
    {
        this.cursor = cursor;
        this.posToIgnore = posToRemove;
    }
    
    @Override
    public boolean onMove(int oldPosition, int newPosition)
    {
        if (newPosition < posToIgnore)
        {
            cursor.moveToPosition(newPosition);
        }
        else
        {
            cursor.moveToPosition(newPosition+1);
        }
        return true;
    }
    
    @Override
    public int getCount()
    {
        return cursor.getCount() - 1;
    }
    
    @Override
    public String[] getColumnNames()
    {
        return cursor.getColumnNames();
    }
    
    //etc.
    //make sure to override all methods in AbstractCursor appropriately
    

    按照前面的所有步骤操作,除了:

      < li >在SwipeDismissList中。OnDismissCallback.onDismiss(),创建新的CursorWithDelete。 < li >交换新光标
     类似资料:
    • 我已经实现了一个,可以在其中添加和删除项目。我希望添加的项目添加到第二个位置,并且每当我添加新项目时,动画运行良好。也就是说,最后一个项目向下移动,让新项目的空间淡入。 当我删除一个项目时,有一个问题我不知道如何解决。我希望它的行为是: < li >淡出删除的元素, < li >向上移动它下面的所有项目。 实际发生的事情是,首先,最后一项消失,然后动画的其余部分发生。当已删除元素下方的项目向上移动

    • 问题内容: 我们目前有一个使用的詹金斯管道。每个git分支都执行一个声纳分析,使用该属性创建一个声纳项目 。这是非常有用的,因为在合并每个分支之前都会对其进行分析,当一个分支与master合并并在GIT上消失时,该问题就会出现,该项目将继续在sonarqube上进行,并且需要手动删除。有没有办法自动做到这一点?或其他任何建议? 问题答案: 您可以定义以下方法来执行此工作,然后根据需要或在诸如合并/

    • 问题内容: 这在Microsoft SQL Server的T-SQL上是有效的语法,但在SQLite中则无效,在SQLite中是否有相同的替代语法? 问题答案: 通常,将整个联接移到一个子查询中,该子查询查找要删除的行的主键: 如果您有复合主键,则可以使用rowid代替: 如果您有复合主键,并且将该表创建为WITHOUT ROWID 表,则必须将联接重写为相关子查询:

    • 假设我们在 “contact-form” 分支上的工作已经完成了。并且我们也已经把最终的改动整合到了 “master” 分支。现在我们就不再需要这个分支了。把它删除掉吧: $ git branch -d contact-form 为了保持一致,我们也有必要删除它所对应的远程分支。附加上一个 “-r” 参数就可以了: $ git branch -dr origin/contact-form

    • 我正在用C#WPF做一个项目,它包含在一个客户机管理中,在那里我添加了新的客户机,在一个txt文档中有save,我有一个listview,在那里我可以看到所有添加的客户机,但现在我想实现一个delete按钮,在那里我从listview中选择一个客户机,并可以删除它。如果我现在做的正确,如果有人能帮我…,…谢谢。

    • 删除除顶部以外的其他项目。 禁用该节或动画。