Snackbar简单使用及源码浅析
来源:互联网 发布:手机app编程自学 编辑:程序博客网 时间:2024/06/08 09:19
snackbar的简单用法效果图:
贴上面效果的代码
public void onClick(View view){ Snackbar snackbar = Snackbar.make(btn,"长沙下雨了",Snackbar.LENGTH_SHORT) .setAction("确定", new View.OnClickListener() { @Override public void onClick(View view) { Toast.makeText(MainActivity.this,"点击确定了",Toast.LENGTH_SHORT).show(); } }) .setCallback(new Snackbar.Callback(){ @Override public void onShown(Snackbar sb) { super.onShown(sb); } @Override public void onDismissed(Snackbar transientBottomBar, int event) { super.onDismissed(transientBottomBar, event); } }) .setActionTextColor(Color.YELLOW); View v = snackbar.getView(); TextView tv = v.findViewById(R.id.snackbar_text); tv.setTextColor(Color.BLUE); snackbar.show(); }
通过Snackbar的make(@NonNull View view, @NonNull CharSequence text,
@Duration int duration)方法来创建一个实例,第一个参数是点击的view,第二个参数是要提示的字符串,第三个参数是snackbar要显示的时间,duration的取值有三个:LENGTH_SHORT较短时间,LENGTH_LONG较长时间,LENGTH_INDEFINITE不会消失(如果没有调用dismiss方法或者没有其他的snackbar需要显示),创建完成后可以用show()方法进行显示。
也可以通过setAction(CharSequence text, final View.OnClickListener listener)方法设置一个用于交互的按钮,例如上面的确定按钮。
通过setActionTextColor(@ColorInt int color)方法来设置交互按钮文字的颜色。
还可以通过setCallback(Callback callback)方法设置回调,实现onShown和onDismissed方法,就是在snackbar显示和消失时的回调,我们可以做一些我们想做的事情。
View v = snackbar.getView();
TextView tv = v.findViewById(R.id.snackbar_text);
tv.setTextColor(Color.BLUE);
这段代码是为了设置snackbar设置要显示的文字的颜色,(例如上面的“长沙下雨了”)
Snackbar是没有提供给我们改变文字颜色的方法的。我们可以在源码中可以发现这个从下面弹出来的snackbar的布局是由左边一个TextView,右边一个Button组成。我们通过id,直接获取TextView进行设置。
下面对Snackbar的源码就行浅浅的分析:
Snackbar继承BaseTransientBottomBar。
入口Snackbar.make()方法。
@NonNull 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; }
这一行代码
final ViewGroup parent = findSuitableParent(view);
通过findSuitableParent()方法找到顶层View,这里的顶层View不是DecorView,而是CoordinatorLayout或者FrameLayout,看这个方法的实现就知道为什么了:
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; }
这个方法里面是个do while循环 ,通过view.getParent()获取父布局,并再复制给view,直到找到CoordinatorLayout,然后return,或者找到id为android.R.id.content的FrameLayout布局。
那它为什么要找到这个布局呢,其实就是要在后面把我们的snackbar的实质布局添加到这上面显示出来。
看完这个方法,再回到Snackbar的make()方法里接着向下走,
final SnackbarContentLayout content =(SnackbarContentLayout) inflater.inflate( R.layout.design_layout_snackbar_include, parent, false);
这行代码就是创建了Snackbar显示的布局SnackbarContentLayout,SnackbarContentLayout是继承LinearLayout的,我们可以看一下这个layout布局:
<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>
里面有一个id为snackbar_text的TextView,和一个id为snackbar_action的Button。所以我们可以直接通过findviewbyid的方式拿到TextView和Button来改变它们的属性。
好,接下来再回到make方法向下走,
final Snackbar snackbar = new Snackbar(parent, content, content);
将找到的parent,和解析出的布局SnackbarContentLayout通过Snackbar的构造函数传进去,创建Snackbar实例,然后返回。这就是make()的实现。我们还需要再看一下Snackbar的创建,
private Snackbar(ViewGroup parent, View content, ContentViewCallback contentViewCallback) { super(parent, content, contentViewCallback); }
调用了super,看一下父类实现,
protected BaseTransientBottomBar(@NonNull ViewGroup parent, @NonNull View content, @NonNull ContentViewCallback contentViewCallback) { if (parent == null) { throw new IllegalArgumentException("Transient bottom bar must have non-null parent"); } if (content == null) { throw new IllegalArgumentException("Transient bottom bar must have non-null content"); } if (contentViewCallback == null) { throw new IllegalArgumentException("Transient bottom bar must have non-null callback"); } mTargetParent = parent; mContentViewCallback = contentViewCallback; mContext = parent.getContext(); ThemeUtils.checkAppCompatTheme(mContext); LayoutInflater inflater = LayoutInflater.from(mContext); // Note that for backwards compatibility reasons we inflate a layout that is defined // in the extending Snackbar class. This is to prevent breakage of apps that have custom // coordinator layout behaviors that depend on that layout. mView = (SnackbarBaseLayout) inflater.inflate( R.layout.design_layout_snackbar, mTargetParent, false); mView.addView(content); ViewCompat.setAccessibilityLiveRegion(mView, ViewCompat.ACCESSIBILITY_LIVE_REGION_POLITE); ViewCompat.setImportantForAccessibility(mView, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES); // Make sure that we fit system windows and have a listener to apply any insets ViewCompat.setFitsSystemWindows(mView, true); ViewCompat.setOnApplyWindowInsetsListener(mView, new android.support.v4.view.OnApplyWindowInsetsListener() { @Override public WindowInsetsCompat onApplyWindowInsets(View v, WindowInsetsCompat insets) { // Copy over the bottom inset as padding so that we're displayed // above the navigation bar v.setPadding(v.getPaddingLeft(), v.getPaddingTop(), v.getPaddingRight(), insets.getSystemWindowInsetBottom()); return insets; } }); mAccessibilityManager = (AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE); }
首先将找到的父布局parent赋值给mTargetParent,然后给mView赋值,通过mView = (SnackbarBaseLayout) inflater.inflate(R.layout.design_layout_snackbar, mTargetParent, false);这行代码,再将content布局添加到mView里。
到这里make()方法结束。
下面就是调用show()方法来显示了;
在看show方法前,先看两个初始化的操作,在Snackbar的父类BaseTransientBottomBar里有一个static静态代码块,初始化了一个Handler,用来发消息。
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; } }); }
还有一个是SnackbarManager的Callback的初始化。
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)); } };
show()和dismiss()方法都会通过Handler发消息,分别调用对用的BaseTransientBottomBar的showView()和hideView()方法。
好下面我们来看show方法,
/** * Show the {@link BaseTransientBottomBar}. */ public void show() { SnackbarManager.getInstance().show(mDuration, mManagerCallback); }
public void show(int duration, Callback callback) { synchronized (mLock) { if (isCurrentSnackbarLocked(callback)) { // Means that the callback is already in the queue. We'll just update the duration mCurrentSnackbar.duration = duration; // If this is the Snackbar currently being shown, call re-schedule it's // timeout mHandler.removeCallbacksAndMessages(mCurrentSnackbar); scheduleTimeoutLocked(mCurrentSnackbar); return; } else if (isNextSnackbarLocked(callback)) { // We'll just update the duration mNextSnackbar.duration = duration; } else { // Else, we need to create a new record and queue it mNextSnackbar = new SnackbarRecord(duration, callback); } if (mCurrentSnackbar != null && cancelSnackbarLocked(mCurrentSnackbar, Snackbar.Callback.DISMISS_EVENT_CONSECUTIVE)) { // If we currently have a Snackbar, try and cancel it and wait in line return; } else { // Clear out the current snackbar mCurrentSnackbar = null; // Otherwise, just show it now showNextSnackbarLocked(); } } }
在第一个if else里面如果snackbar是第一次显示,会走到最后一个else里面mNextSnackbar = new SnackbarRecord(duration, callback);
在SnackbarRecord的构造方法里就是简单的赋值操作
SnackbarRecord(int duration, Callback callback) { this.callback = new WeakReference<>(callback); this.duration = duration; }
然后再往下执行,就到了showNextSnackbarLocked()方法;
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; } } }
这里的callback就是前面传入并封装到SnackbarRecord里的SnackbarManager的内部类实例mManagerCallback,调用了callback的show方法。然后就衔接到了上面一开始初始化Hanlder和Callback的地方,show方法里Handler发消息调用BaseTransientBottomBar的showView方法。
final void showView() { if (mView.getParent() == null) { final ViewGroup.LayoutParams lp = mView.getLayoutParams(); if (lp instanceof CoordinatorLayout.LayoutParams) { // If our LayoutParams are from a CoordinatorLayout, we'll setup our Behavior final CoordinatorLayout.LayoutParams clp = (CoordinatorLayout.LayoutParams) lp; final Behavior behavior = new Behavior(); behavior.setStartAlphaSwipeDistance(0.1f); behavior.setEndAlphaSwipeDistance(0.6f); behavior.setSwipeDirection(SwipeDismissBehavior.SWIPE_DIRECTION_START_TO_END); behavior.setListener(new SwipeDismissBehavior.OnDismissListener() { @Override public void onDismiss(View view) { view.setVisibility(View.GONE); dispatchDismiss(BaseCallback.DISMISS_EVENT_SWIPE); } @Override public void onDragStateChanged(int state) { switch (state) { case SwipeDismissBehavior.STATE_DRAGGING: case SwipeDismissBehavior.STATE_SETTLING: // If the view is being dragged or settling, pause the timeout SnackbarManager.getInstance().pauseTimeout(mManagerCallback); break; case SwipeDismissBehavior.STATE_IDLE: // If the view has been released and is idle, restore the timeout SnackbarManager.getInstance() .restoreTimeoutIfPaused(mManagerCallback); break; } } }); clp.setBehavior(behavior); // Also set the inset edge so that views can dodge the bar correctly clp.insetEdge = Gravity.BOTTOM; } mTargetParent.addView(mView); }
showView方法最后会执行mTargetParent.addView(mView);结束end。
这样snackbar显示的布局就添加到了所谓的顶层布局FrameLayout的最上层了,而不会被我们的contentView布局所覆盖。
Snackbar是 Material Design Support 8大控件之一,它和Dialog,Toast都有类似之处,
Dialog略显笨拙,会阻断用户的连续性,体验会打折扣,交互性太强。而Dialog的生命周期跟Snackbar一样,都是跟随当前Activity. 而Toast与它们两不同,跟当前的Activity的无关,因为Toast是通过Window实现的。Toast与Snackbar的不同处还有就是Toast不会一直显示,Snackbar可以通过设置Snackbar.LENGTH_INDEFINITE不消失,还有Toast只是用于显示没有交互性,Snackbar可以交互。
暂且到此结束。
- Snackbar简单使用及源码浅析
- Snackbar 的简单使用
- Snackbar简单使用
- SnackBar的简单使用
- Snackbar简单使用
- Snackbar的简单使用
- Snackbar的简单使用
- Android Support Design 库 之 Snackbar使用及源码分析
- Android Support Design 库 之 Snackbar使用及源码分析
- Android Support Design 库 之 Snackbar使用及源码分析
- Android Support Design 库 之 Snackbar使用及源码分析
- Android Support Design 库 之 Snackbar使用及源码分析
- uCrop使用及源码浅析
- Android-Snackbar用法及源码分析
- 轻量级控件SnackBar使用以及源码分析
- (三十)Snackbar 使用及其源码分析
- design support library SnackBar简单使用
- Android中SnackBar的简单使用
- python-1-破解unix密码/etc/shadow
- python 拷贝
- RN调试的坑
- java-操作 Excel
- 一个简单的jquery图片轮播插件
- Snackbar简单使用及源码浅析
- Unity 读取 Excel 表格 配置 游戏 参数
- Windows编译出的Botan库运行起来崩溃问题的解决
- C/C++类与面向对象
- 03.09 新建虚拟机与CentOS安装
- HBase 协处理器 (Coprocessors)
- 使用fastboot命令刷机流程详解
- java中泛型的使用
- 浅谈任务跟踪工具