支持库中的 Snackbar 不包含 OnDismissListener()?

Posted

技术标签:

【中文标题】支持库中的 Snackbar 不包含 OnDismissListener()?【英文标题】:Snackbar in Support Library doesn't include OnDismissListener()? 【发布时间】:2015-08-18 19:21:45 【问题描述】:

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

当用户执行一项重要操作时,我想允许他们通过 Snackbar 撤消它,但似乎无法检测何时解除执行该操作。对我来说,这样做是有意义的:

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

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

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

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

【问题讨论】:

它非常非常基础。它甚至没有“isShowing()”。 【参考方案1】:

这是刚刚在 v23 中添加的。

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

【讨论】:

【参考方案2】:

现在是does

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();

【讨论】:

【参考方案3】:

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

【讨论】:

【参考方案4】:

改进 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();

【讨论】:

【参考方案5】:

查看我的帮助类以了解小吃店。

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();


【讨论】:

好主意。但是有没有办法在 Pre API 12 设备上执行此操作?【参考方案6】:
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。

【讨论】:

【参考方案7】:

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

假装数据已从数据库中删除(从 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 在子类中无法访问而不是最终的问题,但这基本上是我设法实现您所说的内容的方法。 希望有人能找到更好的解决方案。

【讨论】:

同意这很 hacky,但在 Google 提供官方方法来处理它之前,我会继续这样做。它甚至不那么阴暗,因为它当前的实现方式,snackbar.getDuration() 返回 0(Snackbar.LENGTH_LONG 由于某种原因也等于 0),所以你的snackbar.getDuration() * 1.05f 返回 0。我尽可能地计时到大约 3500 毫秒,所以我现在就这样做。 哼,这很奇怪。它对我有用......但developer.android.com/reference/android/support/design/widget/… 确实将其指定为 0(并且 LENGTH_SHORT 为 -1)。我不明白为什么(连同 TOAST.LENGTH_SHORT/LONG)不直接使用毫秒值,这给了我们更多的控制权。毫无疑问,Google 会在不久的将来改进所有这些新的 API。目前它们看起来有点生硬。 哈哈,是的,不知道为什么这对你有用,因为 Snackbar 的整个区域有点乱:***.com/questions/30550792/…。 另外,关于我的解决方案的注释,如果用户在 Handler 触发 DB 事件之前导航到新活动,可能会出现问题。虽然没有调试过这个理论 :) Cnackbar 是 Toast 的简单替代品,仅此而已!经典的 Google“不关心开发者”政策

以上是关于支持库中的 Snackbar 不包含 OnDismissListener()?的主要内容,如果未能解决你的问题,请参考以下文章

Android开发——Snackbar使用详解

Android 多行 Snackbar

Material Design ,Snackbar的使用

设计支持库 22.2.1 中的 Snackbar InflateException

棒棒糖前设备中的 Snackbar 高度太大

扩展Snackbar 使其支持居中显示