Закусочная в библиотеке поддержки не включает OnDismissListener()?

Я хотел бы реализовать новую Snackbar, включенную в последнюю библиотеку поддержки дизайна, но то, как она предлагается, кажется нелогичным для меня и, я предполагаю, для многих других.

Когда пользователь выполняет важное действие, я хочу позволить ему отменить его через Snackbar, но, похоже, нет способа определить, когда он отклоняется для выполнения действия. Мне имеет смысл сделать это следующим образом:

  1. Пользователь выполняет действие.
  2. Показать Snackbar и обновить пользовательский интерфейс, как если бы действие было завершено (т. е. кажется, что данные отправлены в базу данных, но на самом деле это еще не так).
  3. Если пользователь нажал «отменить», отменить изменения пользовательского интерфейса. Если нет, то при закрытии Snackbar данные будут отправлены.

Но поскольку я не вижу доступного OnDismissListener, мне пришлось бы:

  1. Пользователь выполняет действие.
  2. Немедленно отправьте информацию в базу данных и обновите пользовательский интерфейс.
  3. Если пользователь нажимает «отменить», отправьте еще один вызов в базу данных, чтобы удалить только что добавленные данные и отменить изменения пользовательского интерфейса.

Мне бы очень хотелось избежать двух вызовов базы данных и просто отправить один, когда приложение знает, что это безопасно (пользователь избежал нажатия «отменить»). Я заметил, что это реализовано в сторонней библиотеке через EventListener, но мне бы очень хотелось придерживаться библиотеки Google.


person S Fitz    schedule 04.06.2015    source источник
comment
Это очень просто. У него даже нет isShowing().   -  person Jared Burrows    schedule 07.06.2015


Ответы (7)


Теперь это делает

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();
person 4gus71n    schedule 24.08.2015

Посмотрите мой вспомогательный класс для закусочных.

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();
}
}
person hitch.united    schedule 04.07.2015
comment
Отличная идея. Однако есть ли способ сделать это на устройствах Pre API 12? - person StuStirling; 30.07.2015

У меня та же проблема, но я предоставляю возможность отмены удаления данных.
Вот как я с этим справляюсь:

  • Представьте, что данные удалены из БД (скрыть от пользовательского интерфейса, который представляет собой список элементов)
  • Подождите, пока закусочная «должна» исчезнуть
  • Отправить вызов удаления в базу данных
  • ЕСЛИ пользователь использовал действие отмены, заблокируйте ожидающий вызов БД.

Это может не сработать для вас, поскольку вы вставляете данные, и вам может (?) понадобиться, чтобы они были доступны в базе данных после первого действия, но это сработает, если возможна «подделка» данных (в пользовательском интерфейсе). Мой код не оптимален, и я бы назвал это хаком, но это лучшее, что я смог найти, оставаясь в официальных библиотеках.

    // 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 не доступен внутри подклассов, не будучи окончательным, но в основном именно так мне удалось добиться того, о чем вы говорите.
Надеюсь, кто-то может найти лучшее решение.

person Warrick    schedule 07.06.2015
comment
Согласен, что это довольно хакерски, но я буду с этим, пока Google не предоставит официальный способ справиться с этим. Это еще менее сомнительно, потому что в настоящее время snackbar.getDuration() возвращает 0 (Snackbar.LENGTH_LONG также равно 0 по какой-то причине), поэтому ваш snackbar.getDuration() * 1.05f возвращает 0. Я рассчитал время как мог. примерно до 3500 мс, так что пока я придерживаюсь этого. - person S Fitz; 09.06.2015
comment
Хм, это странно. У меня это работало... но developer.android .com/reference/android/support/design/widget/ указывает его как 0 (а LENGTH_SHORT равно -1). Я не понимаю, почему (наряду с TOAST.LENGTH_SHORT/LONG) нельзя просто напрямую использовать миллисекундные значения, что дает нам больше контроля. Несомненно, Google улучшит все эти новые API в ближайшем будущем. На данный момент они кажутся немного сырыми. - person Warrick; 09.06.2015
comment
Ха-ха, да, не уверен, почему это работает для вас, потому что вся эта область Snackbar немного испорчена: stackoverflow.com/questions/30550792/. - person S Fitz; 10.06.2015
comment
Кроме того, примечание о моем решении: потенциально может возникнуть проблема, если пользователь перейдет к новому действию до того, как обработчик запустит событие БД.. Однако эта теория не отлажена :) - person Warrick; 10.06.2015
comment
Cnackbar — это простая замена Toast, и ничего более! Классический Google не заботится о политике разработчиков - person Bozic Nebojsa; 10.06.2015
comment
Я могу так легко согласиться, я думаю, что разработчикам нужно много сделать, чтобы реализовать то, что кажется стандартными системными виджетами, потому что официальной библиотеки нет, хотя все приложения Google используют одни и те же блоки. Это может быть просто быстрый оборот релизов и отсутствие времени опубликовать все до следующего релиза Android. - person Warrick; 11.06.2015
comment
К этой переменной можно легко получить доступ из лямбда-функции. - person russellhoff; 11.12.2015

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, вызываются onMeasure и onMeasureChild из CoordinatorLayout. Поэтому я переопределил эти методы.

Обратите внимание, что вы должны установить идентификаторы дочерних элементов пользовательского макета координатора. Потому что я нахожу вид SnackBar по id. Идентификатор SnackBar равен -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. И тот уволен, тогда он вызовет Dismiss.

person neosarchizo    schedule 22.06.2015

Чтобы улучшить ответ 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();
person Hugo Mulder    schedule 26.07.2015

Это было только что добавлено в v23.

Чтобы получать уведомления, когда закусочная была показана или закрыта, вы можете предоставить Snackbar.Callback через setCallback(Callback).

person Paolo Rotolo    schedule 26.08.2015

Ответ Франческо (здесь) правильно, но, к сожалению, он работает только с API> 12. Я отправил запрос функции в систему отслеживания проблем Android. Вы можете проверить его здесь и отметить его, если вы интересно. Спасибо.

person javmarina    schedule 30.07.2015