20

我想实现最新的设计支持库中包含的新 Snackbar,但它的提供方式对我来说似乎是违反直觉的,我假设还有很多其他人的使用。

当用户执行一项重要操作时,我想允许他们通过 Snackbar 撤消它,但似乎无法检测到它何时被解除执行该操作。按以下方式进行对我来说很有意义:

  1. 用户执行操作。
  2. 显示 Snackbar 并更新 UI,就像操作已完成一样(即,数据似乎已发送到数据库,但实际上尚未发送)。
  3. 如果用户按下“撤消”,则恢复 UI 更改。如果没有,当 Snackbar 被关闭时,它将发送数据。

但是因为我没有看到任何可访问的 OnDismissListener,因此我必须:

  1. 用户执行操作。
  2. 立即将信息发送到数据库并更新 UI。
  3. 如果用户按下“撤消”,则向数据库发送另一个调用以删除刚刚添加的数据并恢复 UI 更改。

我真的很想避免对数据库进行两次调用,并且在应用程序知道它是安全的(用户避免按“撤消”)时发送一个。我注意到通过 EventListener 在第三方库中有一些实现,但我真的很想坚持使用 Google 库。

4

7 回答 7

24

现在它确实

Snackbar.make(getView(), "Hi there!", Snackbar.LENGTH_LONG).setCallback( new Snackbar.Callback() {
                @Override
                public void onDismissed(Snackbar snackbar, int event) {
                    switch(event) {
                        case Snackbar.Callback.DISMISS_EVENT_ACTION:
                            Toast.makeText(getActivity(), "Clicked the action", Toast.LENGTH_LONG).show();
                            break;
                        case Snackbar.Callback.DISMISS_EVENT_TIMEOUT:
                            Toast.makeText(getActivity(), "Time out", Toast.LENGTH_LONG).show();
                            break;
                    }
                }

                @Override
                public void onShown(Snackbar snackbar) {
                    Toast.makeText(getActivity(), "This is my annoying step-brother", Toast.LENGTH_LONG).show();
                }
            }).setAction("Go away!", new View.OnClickListener() {
                @Override
                public void onClick(View v) {

                }
            }).show();
于 2015-08-24T19:44:01.753 回答
4

查看我的小吃店助手课程。

public class SnackbarUtils {

private static final String LOG_TAG = SnackbarUtils.class.getSimpleName();

private SnackbarUtils() {
}

public interface SnackbarDismissListener {
    void onSnackbarDismissed();
}

public static void showSnackbar(View rootView, String message, String actionMessage, View.OnClickListener callbacks, final SnackbarDismissListener dismissListener){
    Snackbar snackbar = Snackbar.make(rootView,message,Snackbar.LENGTH_LONG);
    snackbar.setAction(actionMessage,callbacks);
    if (dismissListener != null){
        snackbar.getView().addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
            @Override
            public void onViewAttachedToWindow(View v) {

            }

            @Override
            public void onViewDetachedFromWindow(View v) {
                dismissListener.onSnackbarDismissed();
            }
        });
    }
    snackbar.show();
}
}
于 2015-07-04T20:33:14.170 回答
2

我有同样的问题,但我提供了一个“撤消”来删除数据。
这就是我的处理方式:

  • 假装数据已从 db 中删除(从 UI 中隐藏,这是一个项目列表)
  • 等到小吃店“应该”消失
  • 向数据库发送删除调用
  • 如果,用户使用了撤消操作,则阻止待处理的 DB 调用

这可能对您不起作用,因为您正在插入数据并且您可能(?)需要它在第一次操作后在数据库中可用,但如果“伪造”数据(在 UI 中)是可行的,它将起作用。我的代码不是最优的,我会称之为 hack,但它是我在官方库中能找到的最好的。

    // Control objects
    boolean canRemoveData = true;
    Object removedData = getData(id);
    UI.remove(id);

    // Snackbar code
    Snackbar snackbar = Snackbar.make(view, "Data removed", Snackbar.LENGTH_LONG);
    snackbar.setAction("Undo", new View.OnClickListener() {
        @Override
        public void onClick(View v){
            canRemoveData = false;

            DB.remove(id);
        }
    });

    // Handler to time the dismissal of the snackbar
    new Handler(getActivity().getMainLooper()).postDelayed(new Runnable() {
        @Override
        public void run() {
            if(canRemoveData){
                DB.remove(id);
            }
        }
    }, (int)(snackbar.getDuration() * 1.05f)); 
    // Here I am using a slightly longer delay before sending the db delete call,
    // just because I don't trust the accuracy of the Handler timing and I want
    // to be on the safe side. Either way this is dirty code, but the best I could do.

我的实际代码更复杂(处理 canRemoveData 在子类中无法访问的问题,但不是最终的,但这基本上是我设法实现您所说的内容。
希望有人能找到更好的解决方案。

于 2015-06-07T03:41:41.827 回答
1
public class CustomCoordinatorLayout extends CoordinatorLayout {

    private boolean mIsSnackBar = false;
    private View mSnakBarView = null;
    private OnSnackBarListener mOnSnackBarListener = null;

    public CustomCoordinatorLayout(Context context) {
        super(context);
    }

    public CustomCoordinatorLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        if(mIsSnackBar){
            // Check whether the snackbar is existed.
            // If it is not existed then index of the snackbar is -1.
            if(indexOfChild(mSnakBarView) == -1){
                mSnakBarView = null;
                mIsSnackBar = false;

                if(mOnSnackBarListener != null)
                    mOnSnackBarListener.onDismiss();
                Log.d("NEOSARCHIZO","SnackBar is dismissed!");
            }
        }
    }

    @Override
    public void onMeasureChild(View child, int parentWidthMeasureSpec, int     widthUsed, int parentHeightMeasureSpec, int heightUsed) {
    super.onMeasureChild(child, parentWidthMeasureSpec, widthUsed,     parentHeightMeasureSpec, heightUsed);

        // onMeaureChild is called before onMeasure.
        // The view of SnackBar doesn't have an id.
        if(child.getId() == -1){
            mIsSnackBar = true;
            // Store the view of SnackBar.
            mSnakBarView = child;

            if(mOnSnackBarListener != null)
                mOnSnackBarListener.onShow();
            Log.d("NEOSARCHIZO","SnackBar is showed!");
        }
    }

    public void setOnSnackBarListener(OnSnackBarListener onSnackBarListener){
        mOnSnackBarListener = onSnackBarListener;
    }

    public interface OnSnackBarListener{
        public void onShow();
        public void onDismiss();
    }
}

我使用自定义协调器布局。当显示 Snackbar 时,将调用 CoordinatorLayout 的 onMeasure 和 onMeasureChild。所以我重写了这些方法。

请注意,您必须设置自定义协调器布局的子级 ID。因为我通过id找到了SnackBar的视图。SnackBar 的 id 为 -1。

CustomCoordinatorLayout layout = (CustomCoordinatorLayout)findViewById(R.id.main_content);
layout.setOnSnackBarListener(this);
Snackbar.make(layout, "Hello!", Snackbar.LENGTH_LONG).setAction("UNDO", new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    //TODO something
                }
            }).show();

在您的活动或片段中实现 OnSnackBarListener。当显示快餐栏时,它将调用 onShow。并且一个被解雇然后它将调用 onDismiss。

于 2015-06-22T04:13:55.323 回答
1

改进 Hitch.united 答案

                boolean mAllowedToRemove = true;
                Snackbar snack = Snackbar.make(mView, mSnackTitle, Snackbar.LENGTH_LONG);
                snack.setAction(getString(R.string.snackbar_undo), new OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        mAllowedToRemove = false;

                        // undo
                        ...
                    }
                });
                snack.getView().addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
                    @Override
                    public void onViewAttachedToWindow(View v) {

                    }

                    @Override
                    public void onViewDetachedFromWindow(View v) {
                        if(!mAllowedToRemove){
                            // handle actions like http requests
                            ...
                        }
                    }
                });
                snack.show();
于 2015-07-26T11:51:21.137 回答
1

这只是在v23中添加的。

要在显示或关闭小吃店时收到通知,您可以通过 提供 Snackbar.Callback setCallback(Callback)

于 2015-08-26T10:51:04.713 回答
0

Francesco 的回答(此处)是正确的,但不幸的是它仅适用于 API > 12。我向 Android 问题跟踪器提交了一个功能请求。如果您有兴趣,可以在这里查看并加注星标。谢谢。

于 2015-07-30T17:51:50.590 回答