Android Snackbar控件

1. Snackbar类

Snackbar是5.0版本出现的控件,类似于Toast,显示在屏幕的底部,包含文字信息与一个可选的操作按钮。需要添加Design依赖库,并且使用Theme.AppCompat主题。
Android Snackbar控件_第1张图片

2. 创建Snackbar类

Snackbar利用静态方法make()来创建实例

public static Snackbar make(@NonNull View view, @StringRes int resId, @Duration int duration) {
    return make(view, view.getResources().getText(resId), duration);
}

public static Snackbar make(@NonNull View view, @NonNull CharSequence text,
        @Duration int duration) {
    final ViewGroup parent = findSuitableParent(view);
    if (parent == null) {
        throw new IllegalArgumentException("No suitable parent found from the given view. "
                + "Please provide a valid view.");
    }

    final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
    final SnackbarContentLayout content =
            (SnackbarContentLayout) inflater.inflate(
                    R.layout.design_layout_snackbar_include, parent, false);
    final Snackbar snackbar = new Snackbar(parent, content, content);
    snackbar.setText(text);
    snackbar.setDuration(duration);
    return snackbar;
}

创建Snackbar实例,需要寻找合适的父视图,优先选择CoordinatorLayout作为父视图。

private static ViewGroup findSuitableParent(View view) {
    ViewGroup fallback = null;
    do {
        if (view instanceof CoordinatorLayout) {
            // We've found a CoordinatorLayout, use it
            return (ViewGroup) view;
        } else if (view instanceof FrameLayout) {
            if (view.getId() == android.R.id.content) {
                // If we've hit the decor content view, then we didn't find a CoL in the
                // hierarchy, so use it.
                return (ViewGroup) view;
            } else {
                // It's not the content view but we'll use it as our fallback
                fallback = (ViewGroup) view;
            }
        }

        if (view != null) {
            // Else, we will loop and crawl up the view hierarchy and try to find a parent
            final ViewParent parent = view.getParent();
            view = parent instanceof View ? (View) parent : null;
        }
    } while (view != null);

    // If we reach here then we didn't find a CoL or a suitable content view so we'll fallback
    return fallback;
}

SnackbarContentLayout继承LinearLayout,并实现了BaseTransientBottomBar.ContentViewCallback,包含一个TextViewButton
design_layout_snackbar_include.xml文件,

<view
    xmlns:android="http://schemas.android.com/apk/res/android"
    class="android.support.design.internal.SnackbarContentLayout"
    android:theme="@style/ThemeOverlay.AppCompat.Dark"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="bottom">

    <TextView
        android:id="@+id/snackbar_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:paddingTop="@dimen/design_snackbar_padding_vertical"
        android:paddingBottom="@dimen/design_snackbar_padding_vertical"
        android:paddingLeft="@dimen/design_snackbar_padding_horizontal"
        android:paddingRight="@dimen/design_snackbar_padding_horizontal"
        android:textAppearance="@style/TextAppearance.Design.Snackbar.Message"
        android:maxLines="@integer/design_snackbar_text_max_lines"
        android:layout_gravity="center_vertical|left|start"
        android:ellipsize="end"
        android:textAlignment="viewStart"/>

    <Button
        android:id="@+id/snackbar_action"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="@dimen/design_snackbar_extra_spacing_horizontal"
        android:layout_marginStart="@dimen/design_snackbar_extra_spacing_horizontal"
        android:layout_gravity="center_vertical|right|end"
        android:minWidth="48dp"
        android:visibility="gone"
        android:textColor="?attr/colorAccent"
        style="?attr/borderlessButtonStyle"/>

view>

3. Snackbar设置

Snackbar可以设置文本和按钮

public Snackbar setText(@NonNull CharSequence message)
public Snackbar setText(@StringRes int resId)
public Snackbar setAction(@StringRes int resId, View.OnClickListener listener)
public Snackbar setAction(CharSequence text, final View.OnClickListener listener)
public Snackbar setActionTextColor(ColorStateList colors)
public Snackbar setActionTextColor(@ColorInt int color)

Snackbar可以监听视图

addCallback(new Snackbar.Callback(){
    @Override
    public void onShown(Snackbar sb) {
    }

    @Override
    public void onDismissed(Snackbar transientBottomBar, int event) {
    }
})

Dismissevent有下面五种情况

  • DISMISS_EVENT_SWIPE,向右滑动消失,只有父视图是CoordinatorLayout情况下才会发生
  • DISMISS_EVENT_ACTION,点击右侧按钮消失
  • DISMISS_EVENT_TIMEOUT,设置的显示时间到了消失
  • DISMISS_EVENT_MANUAL,调用Snackbardismiss方法消失
  • DISMISS_EVENT_CONSECUTIVE,新的Snackbar出现导致旧的消失

4. SnackbarManager类

SnackbarManager用来管理Snackbar控件的状态。
Snackbarshow()方法,会调用SnackbarManagershow(int, Callback)方法,而mManagerCallback会回调SnackbarshowView()hideView(int)方法。

static {
    sHandler = new Handler(Looper.getMainLooper(), new Handler.Callback() {
        @Override
        public boolean handleMessage(Message message) {
            switch (message.what) {
                case MSG_SHOW:
                    ((BaseTransientBottomBar) message.obj).showView();
                    return true;
                case MSG_DISMISS:
                    ((BaseTransientBottomBar) message.obj).hideView(message.arg1);
                    return true;
            }
            return false;
        }
    });
}

final SnackbarManager.Callback mManagerCallback = new SnackbarManager.Callback() {
    @Override
    public void show() {
        sHandler.sendMessage(sHandler.obtainMessage(MSG_SHOW, BaseTransientBottomBar.this));
    }

    @Override
    public void dismiss(int event) {
        sHandler.sendMessage(sHandler.obtainMessage(MSG_DISMISS, event, 0,
                BaseTransientBottomBar.this));
    }
};

public void show() {
    SnackbarManager.getInstance().show(mDuration, mManagerCallback);
}

SnackbarManager内部包含两个记录mCurrentSnackbarmNextSnackbar。在SnackbarManagershow(int, Callback)方法中,

  • 查看是否是当前Snackbar,如果是,更新超时时间,结束。
  • 查看是否是NextSnackbar,如果是,更新数据,如果不是创建新的NextSnackbar
  • 取消当前Snackbar或者显示NextSnackbar

show(int, Callback)方法

public void show(int duration, Callback callback) {
    synchronized (mLock) {
        if (isCurrentSnackbarLocked(callback)) {
            // 如果是当前Snackbar,更新duration和超时提示
            mCurrentSnackbar.duration = duration;

            mHandler.removeCallbacksAndMessages(mCurrentSnackbar);
            scheduleTimeoutLocked(mCurrentSnackbar);
            return;
        } else if (isNextSnackbarLocked(callback)) {
            // 如果是NextSnackbar,更新duration
            mNextSnackbar.duration = duration;
        } else {
            // 否则就创建新的NextSnackbar
            mNextSnackbar = new SnackbarRecord(duration, callback);
        }

        if (mCurrentSnackbar != null && cancelSnackbarLocked(mCurrentSnackbar,
                Snackbar.Callback.DISMISS_EVENT_CONSECUTIVE)) {
            // 如果当前Snackbar存在,取消显示当前Snackbar                
            return;
        } else {                
            mCurrentSnackbar = null;
            // 如果当前Snackbar不存在,显示NextSnackbar
            showNextSnackbarLocked();
        }
    }
}

// 取消显示当前Snackbar,调用callback的dismiss(DISMISS_EVENT_CONSECUTIVE)方法
private boolean cancelSnackbarLocked(SnackbarRecord record, int event) {
    final Callback callback = record.callback.get();
    if (callback != null) {
        // Make sure we remove any timeouts for the SnackbarRecord
        mHandler.removeCallbacksAndMessages(record);
        callback.dismiss(event);
        return true;
    }
    return false;
}

private boolean isCurrentSnackbarLocked(Callback callback) {
    return mCurrentSnackbar != null && mCurrentSnackbar.isSnackbar(callback);
}

private boolean isNextSnackbarLocked(Callback callback) {
    return mNextSnackbar != null && mNextSnackbar.isSnackbar(callback);
}

// 更新超时提示
private void scheduleTimeoutLocked(SnackbarRecord r) {
    if (r.duration == Snackbar.LENGTH_INDEFINITE) {
        // If we're set to indefinite, we don't want to set a timeout
        return;
    }

    int durationMs = LONG_DURATION_MS;
    if (r.duration > 0) {
        durationMs = r.duration;
    } else if (r.duration == Snackbar.LENGTH_SHORT) {
        durationMs = SHORT_DURATION_MS;
    }
    mHandler.removeCallbacksAndMessages(r);
    mHandler.sendMessageDelayed(Message.obtain(mHandler, MSG_TIMEOUT, r), durationMs);
}

// 显示NextSnackbar,调用callback的show方法
private void showNextSnackbarLocked() {
    if (mNextSnackbar != null) {
        mCurrentSnackbar = mNextSnackbar;
        mNextSnackbar = null;

        final Callback callback = mCurrentSnackbar.callback.get();
        if (callback != null) {
            callback.show();
        } else {
            // The callback doesn't exist any more, clear out the Snackbar
            mCurrentSnackbar = null;
        }
    }
}

SnackbarshowView()会调用onViewShown()hideView(int)会调用onViewHidden(int)

final void showView() {
    ... ...

    if (shouldAnimate()) {
        // If animations are enabled, animate it in
        animateViewIn();
    } else {
        // Else if anims are disabled just call back now
        onViewShown();
    }

    ... ...
}

final void hideView(@BaseCallback.DismissEvent final int event) {
    if (shouldAnimate() && mView.getVisibility() == View.VISIBLE) {
        animateViewOut(event);
    } else {
        // If anims are disabled or the view isn't visible, just call back now
        onViewHidden(event);
    }
}

void onViewShown() {
    SnackbarManager.getInstance().onShown(mManagerCallback);
}

void onViewHidden(int event) {    
    SnackbarManager.getInstance().onDismissed(mManagerCallback);
}

SnackbarManageronShown(Callback)onDismissed(Callback)方法。

public void onShown(Callback callback) {
    synchronized (mLock) {
        if (isCurrentSnackbarLocked(callback)) {
            scheduleTimeoutLocked(mCurrentSnackbar);
        }
    }
}

public void onDismissed(Callback callback) {
    synchronized (mLock) {
        if (isCurrentSnackbarLocked(callback)) {
            // If the callback is from a Snackbar currently show, remove it and show a new one
            mCurrentSnackbar = null;
            if (mNextSnackbar != null) {
                showNextSnackbarLocked();
            }
        }
    }
}

你可能感兴趣的:(Android,Material,Design,android,Material,Design,Snackbar)