深入理解SnackBar

一.在FrameLayout中使用:

XML


<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="trs.com.fabdemo.MainActivity">

    <android.support.design.widget.FloatingActionButton
        android:layout_gravity="bottom|right"
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:src="@mipmap/ic_launcher" />
FrameLayout>

JAVA代码

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.fab).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Snackbar.make(v,"Test",Snackbar.LENGTH_LONG).show();
            }
        });
    }
}

效果

深入理解SnackBar_第1张图片

二.在CoordinatorLayout中使用

只有xml不同,因此只贴出xml

xml

"1.0" encoding="utf-8"?>
.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="trs.com.fabdemo.MainActivity">

    .support.design.widget.FloatingActionButton
        android:layout_gravity="bottom|right"
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:src="@mipmap/ic_launcher" />
.support.design.widget.CoordinatorLayout>

效果

深入理解SnackBar_第2张图片

分析

在CoordinatorLayout中使用的时候我们发现,SnackBar显示的时候,FloatingActionButton会自动上升,而SnackBar可以滑动删除。为什么会这样呢,首先看看我们SnackBar是怎么创建的。通过调用Snackbar.make方法,这个方法需要三个参数,一个view,一个标题,和显示时长。

 Snackbar.make(v,"Test",Snackbar.LENGTH_LONG)

对于标题和显示时长我们能理解,但是为什么要需要一个View呢,这其实就是关键。看看make方法的实现。

  public static Snackbar make(@NonNull View view, @NonNull CharSequence text,
            @Duration int duration) {
            //寻找一个适当的父类,这里就是关键
        Snackbar snackbar = new Snackbar(findSuitableParent(view));
        snackbar.setText(text);
        snackbar.setDuration(duration);
        return snackbar;
    }

  private static ViewGroup findSuitableParent(View view) {
        ViewGroup fallback = null;
        //遍历视图树
        do {
            if (view instanceof CoordinatorLayout) {
                //判断,如果是CoordinatorLayout则直接返回。         
                return (ViewGroup) view;
            } else if (view instanceof FrameLayout) {
                if (view.getId() == android.R.id.content) {
                //如果查找到最顶级的View,还没有找到CoordinatorLayout,那么我们就使用顶级view及decor,也就是Activity最上层的FrameLayout

                    return (ViewGroup) view;
                } else {

                    fallback = (ViewGroup) view;
                }
            }

            if (view != null) {   
                //继续循环遍历,父View
                final ViewParent parent = view.getParent();
                view = parent instanceof View ? (View) parent : null;
            }
        } while (view != null);     
        return fallback;
    }

会什么要寻找CoordinatorLayout呢,因为SnackBar的滑动删除就是通过CoordinatorLayout.Behavior实现的,我原来说过Behavior的设置可以通过xml,注解,代码三种方式设置,查看SnackBar的申明。发现其不是一个View,所以不能直接添加到CoordinatorLayout中。也就不能通过注解,和xml设置。
深入理解SnackBar_第3张图片
接着我们看看Snackbar的显示,通过调用show()方法,我们展示了snackbar

 Snackbar.make(v,"Test",Snackbar.LENGTH_LONG).show();

让我们看看show方法具体做了什么事情

      /**
     * Show the {@link Snackbar}.
     */
    public void show() {
        SnackbarManager.getInstance().show(mDuration, mManagerCallback);
    }

很简单的调用了SnackbarManager的show方法

public void show(int duration, Callback callback) {

                 //省略一些判断,最终调用此方法
                 ...
                showNextSnackbarLocked();
               ...
    }

 private void showNextSnackbarLocked() {
        if (mNextSnackbar != null) {
            mCurrentSnackbar = mNextSnackbar;
            mNextSnackbar = null;
            //获取当前Snackbar的callback
            final Callback callback = mCurrentSnackbar.callback.get();
            if (callback != null) {
            //调用callback的show方法
                callback.show();
            } else {
                // The callback doesn't exist any more, clear out the Snackbar
                mCurrentSnackbar = null;
            }
        }
    }

查看callback发现其是一个接口

   interface Callback {
        void show();
        void dismiss(int event);
    }

它的实现如下

   private final SnackbarManager.Callback mManagerCallback = new SnackbarManager.Callback() {
        @Override
        public void show() {
            //使用handler发送一个显示的消息。
            sHandler.sendMessage(sHandler.obtainMessage(MSG_SHOW, Snackbar.this));
        }

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

看看Handler的实现发现最终会调用此方法

  final void showView() {
        if (mView.getParent() == null) {
            final ViewGroup.LayoutParams lp = mView.getLayoutParams();

            if (lp instanceof CoordinatorLayout.LayoutParams) {
//如果我们的LayoutParams 是CoordinatorLayout.LayoutParams,就设置我们自己的Behavior,
//而LayoutParams是在构造方法时通过,
//mView=(SnackbarLayout)inflater.inflate(R.layout.design_layout_snackbar, mTargetParent, false);生成的。


                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) {
                        dispatchDismiss(Callback.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, cancel the timeout
                                SnackbarManager.getInstance().cancelTimeout(mManagerCallback);
                                break;
                            case SwipeDismissBehavior.STATE_IDLE:
                                // If the view has been released and is idle, restore the timeout
                                SnackbarManager.getInstance().restoreTimeout(mManagerCallback);
                                break;
                        }
                    }
                });
                ((CoordinatorLayout.LayoutParams) lp).setBehavior(behavior);
            }

            mTargetParent.addView(mView);
        }

查看Behavior,发现继承自SwipeDismissBehavior,关于SwipeDismissBehavior请看之前的一遍博客

 final class Behavior extends SwipeDismissBehavior<SnackbarLayout>

自此,我们明白的为什么在不同布局中会有不同的效果,关于FloatingActionButton为什么会上升,我可以简单的介绍一下。查看FloatingActionButton的声明。
深入理解SnackBar_第4张图片
发现它通过注解设置了自己的默认Behavior。查看此Behavior发现有如下代码。

   @Override
        public boolean layoutDependsOn(CoordinatorLayout parent,
                FloatingActionButton child, View dependency) {
            // 监听所有的Snackbar
            return SNACKBAR_BEHAVIOR_ENABLED && dependency instanceof Snackbar.SnackbarLayout;
        }

        @Override
        public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton child,
                View dependency) {
            if (dependency instanceof Snackbar.SnackbarLayout) {
                //如果是SnackbarLayout变化,则根据Snackbar更新自己的Translation
                updateFabTranslationForSnackbar(parent, child, dependency);
            } else if (dependency instanceof AppBarLayout) {
                // If we're depending on an AppBarLayout we will show/hide it automatically
                // if the FAB is anchored to the AppBarLayout
                //如果是AppBarlayout变化,则实现自动显示和隐藏
                updateFabVisibility(parent, (AppBarLayout) dependency, child);
            }
            return false;
        }

自此,SnackBar的分析就告一段落了,目前源码还有一些值得分析的地方,以后会继续更新的。谢谢大家的支持。

你可能感兴趣的:(Android)