android snackbar源码,Android Study Material Design 五 番外篇 之:深入分析SnackBar源码

> LZ-Says:兜兜转转,似乎再次回到起点。。。

AAffA0nNPuCLAAAAAElFTkSuQmCC

前言

一个人,还是会有些寂寥,孤僻。。。

今天,怀着不知名的心情,一起来分析下SnackBar源码,看看从源码中,我们能get到什么技能。

本文目标

通过源码的角度来了解谷歌大牛是如何Coding的。

分析

这里,我们主要分析SnackBar暴露的俩个部分,一为make(),二为show()。

make()分析

我们在上一篇文章中,简单的了解了SnackBar使用,通过解决部分答疑,让你不知不觉中get一项又一项技能。

SnackBar的易用,我们再次回顾下,示例化SnackBar,我们只需要调用make()方法即可,类似于Toast,今天我们就从它入手,看看人家是怎么玩的。@NonNull // 注解 非空

public static Snackbar make(@NonNull View view, @NonNull CharSequence text, int duration) {        // 初始化SnackBar

Snackbar snackbar = new Snackbar(findSuitableParent(view));        // 赋值

snackbar.setText(text);        // 设置显示时间

snackbar.setDuration(duration);        return snackbar;

}

而接下来,我们看下@NonNull这里面又写了什么。/**

* Denotes that a parameter, field or method return value can never be null.

* <p>

* This is a marker annotation and it has no specific attributes.

*/

@Retention(CLASS)

@Target({METHOD, PARAMETER, FIELD})

public @interface NonNull {

}

这里不得不说谷歌大牛,相当易读哈。can never be null,不能为空。

这里不知大家有没有注意到在初始化SnackBar中,还有一个神秘的家伙,还不知道它在搞什么?private static ViewGroup findSuitableParent(View view) {

ViewGroup fallback = null;        do {            if(view instanceof CoordinatorLayout) {

return (ViewGroup)view; // 当前View属于CoordinatorLayout 直接返回

}            if(view instanceof FrameLayout) { // 当前View属于FrameLayout

if(view.getId() == 16908290) { // id等于content内容 当前activity包含的view

return (ViewGroup)view;

}

fallback = (ViewGroup)view;

}            if(view != null) {

ViewParent parent = view.getParent(); // 获取根布局

view = parent instanceof View?(View)parent:null; // 当前父布局属于view 直接return 否则return null

}

} while(view != null); // 一直在找根布局 找到最外层

return fallback;

}

由此可见,此方法的作用便是一直查找传入View的父布局,直到找到为止。

这里不得不说CoordinatorLayout,这家伙,是个好玩意,后期重点介绍下~

我们接下来看看SnackBar构造中又干了些什么鬼。private Snackbar(ViewGroup parent) {        this.mParent = parent;        this.mContext = parent.getContext();

LayoutInflater inflater = LayoutInflater.from(this.mContext);        // 渲染布局到View

this.mView = (Snackbar.SnackbarLayout)inflater.inflate(layout.design_layout_snackbar, this.mParent, false);

}

这里看到,谷歌大牛直接把SnackBar布局写死了。。。写死了。。。

关于inflate,这点LZ说明下,当最后参数为false时,此时布局是不会被加载到父布局中。当然,你可以理解为他们之间是独立的,这也就是为什么,当SnackBar弹出的时候,你可以选择与其交互,也可以继续你的操作的原因。

而关于为什么LZ知道,LZ这里放出源码内关键代码,大家一看便知:// We are supposed to attach all the views we found (int temp)

// to root. Do that now.

if (root != null && attachToRoot) {

root.addView(temp, params);

}

同时这里也用到synchronized机制,这点不得不说人家谷歌大牛思考确实挺全面,值得一学。

make()源码分析到此,接下来分析下show(),看看他是如何被Show出来的呢?

show()分析public void show() {

SnackbarManager.getInstance().show(this.mDuration, this.mManagerCallback);

}

点击进去可以看到关键点如下:首先,其内部通过SnackbarManager管理类去对Snackbar进行管理;

其次,show的时候需要传入时间以及一个Callback

我们以此进行分解说明,一起探索未知世界。public void show(int duration, SnackbarManager.Callback callback) {

Object var3 = this.mLock;        synchronized(this.mLock) { // 这里再次用到同步锁 保证每次只有一个进行

if(this.isCurrentSnackbar(callback)) {                this.mCurrentSnackbar.duration = duration;                // remove当前Callback

// 这里通过handler进行消息分发 又是handler 看来改天得分析下handler了

this.mHandler.removeCallbacksAndMessages(this.mCurrentSnackbar);                this.scheduleTimeoutLocked(this.mCurrentSnackbar);

} else {                if(this.isNextSnackbar(callback)) { // 这里大致猜测可能校验是否是下一个 如果是 设置时间 反之重新实例化

this.mNextSnackbar.duration = duration;

} else {                    this.mNextSnackbar = new SnackbarManager.SnackbarRecord(duration, callback);

}                // 这里相当于进行了回收操作 这点感觉很nice

if(this.mCurrentSnackbar == null || !this.cancelSnackbarLocked(this.mCurrentSnackbar, 4)) {                    this.mCurrentSnackbar = null;                    this.showNextSnackbarLocked();

}

}

}

}

而实际分发中,还需要对用户传入显示值进行校验,校验通过后,开始分发消息。private void scheduleTimeoutLocked(SnackbarManager.SnackbarRecord r) {        if(r.duration != -2) {            int durationMs = 2750;            // 如果大于0 便是用户设置值 反之设置默认

if(r.duration > 0) {

durationMs = r.duration;

} else if(r.duration == -1) {

durationMs = 1500;

}            // 移除

this.mHandler.removeCallbacksAndMessages(r);            // 分发

this.mHandler.sendMessageDelayed(Message.obtain(this.mHandler, 0, r), (long)durationMs);

}

}

下面瞅瞅是如何通过Handler进行消息分发的,到底在搞什么?private final Handler mHandler = new Handler(Looper.getMainLooper(), new android.os.Handler.Callback() {        public boolean handleMessage(Message message) {            switch(message.what) {            case 0: // 接收到hanlder消息进行处理

SnackbarManager.this.handleTimeout((SnackbarManager.SnackbarRecord)message.obj);                return true;            default:                return false;

}

}

});

private void handleTimeout(SnackbarManager.SnackbarRecord record) {

Object var2 = this.mLock;        synchronized(this.mLock) { // 同步锁

if(this.mCurrentSnackbar == record || this.mNextSnackbar == record) {                this.cancelSnackbarLocked(record, 2); // 关闭

}

}

}

private boolean cancelSnackbarLocked(SnackbarManager.SnackbarRecord record, int event) {        // 获取到回调

SnackbarManager.Callback callback = (SnackbarManager.Callback)record.callback.get();        if(callback != null) {            // dismiss

callback.dismiss(event);            return true;

} else {            return false;

}

}

看看人这套逻辑,思路,佩服。

噢,刚才遗漏一点,如下:private static class SnackbarRecord {

// 弱引用 程序没有内存时 回收此类内存

private final WeakReference callback;        private int duration;

SnackbarRecord(int duration, SnackbarManager.Callback callback) {            // 实例化

this.callback = new WeakReference(callback);            this.duration = duration;

}        boolean isSnackbar(SnackbarManager.Callback callback) {            return callback != null && this.callback.get() == callback;

}

}

大牛使用了弱引用,此类内存将在程序内存不足时自动回收~666

接下来我们关注一波文中常出现的Callback,看看这玩意是个什么鬼。private static class SnackbarRecord {

// 弱引用 程序没有内存时 回收此类内存

private final WeakReference callback;        private int duration;

SnackbarRecord(int duration, SnackbarManager.Callback callback) {            // 实例化

this.callback = new WeakReference(callback);            this.duration = duration;

}        boolean isSnackbar(SnackbarManager.Callback callback) {            return callback != null && this.callback.get() == callback;

}

}

提供显示以及dismiss俩种方式,正好配套。

还记得当我们调用show的时候,内部需要传入一个Callback么?感觉有关SnackBar真正显示的干货要来了。嘿嘿嘿~private final android.support.design.widget.SnackbarManager.Callback mManagerCallback = new android.support.design.widget.SnackbarManager.Callback() {        public void show() {

Snackbar.sHandler.sendMessage(Snackbar.sHandler.obtainMessage(0, Snackbar.this));

}        public void dismiss(int event) {

Snackbar.sHandler.sendMessage(Snackbar.sHandler.obtainMessage(1, event, 0, Snackbar.this));

}

};

可以看到在实例化中,实现了俩个方法,而在显示的时候仅仅发了一个消息,一起来看看。private static final Handler sHandler = new Handler(Looper.getMainLooper(), new android.os.Handler.Callback() {        public boolean handleMessage(Message message) {            switch(message.what) {            case 0:

((Snackbar)message.obj).showView(); // 显示

return true;            case 1:

((Snackbar)message.obj).hideView(message.arg1); // 隐藏

return true;            default:                return false;

}

}

});

final void showView() {        if(this.mView.getParent() == null) { // 当前view父容器为null

LayoutParams lp = this.mView.getLayoutParams(); // 获取LayoutParams

if(lp instanceof android.support.design.widget.CoordinatorLayout.LayoutParams) { // 如果包含

Snackbar.Behavior behavior = new Snackbar.Behavior();                // 设置透明度

behavior.setStartAlphaSwipeDistance(0.1F);

behavior.setEndAlphaSwipeDistance(0.6F);

behavior.setSwipeDirection(0);                // 设置监听

behavior.setListener(new OnDismissListener() {                    public void onDismiss(View view) {

Snackbar.this.dispatchDismiss(0);

}                    public void onDragStateChanged(int state) {                        switch(state) {                        case 0:

SnackbarManager.getInstance().restoreTimeout(Snackbar.this.mManagerCallback);                            break;                        case 1:                        case 2:

SnackbarManager.getInstance().cancelTimeout(Snackbar.this.mManagerCallback);

}

}

});

((android.support.design.widget.CoordinatorLayout.LayoutParams)lp).setBehavior(behavior);

}            // 不管如何 直接添加到父布局

this.mParent.addView(this.mView);

}        // 如果没显示出来 那么就通过动画加载进来

if(ViewCompat.isLaidOut(this.mView)) {            this.animateViewIn();

} else { // 否则就等待 启动时再次调用动画进入

this.mView.setOnLayoutChangeListener(new Snackbar.SnackbarLayout.OnLayoutChangeListener() {                public void onLayoutChange(View view, int left, int top, int right, int bottom) {

Snackbar.this.animateViewIn();

Snackbar.this.mView.setOnLayoutChangeListener((Snackbar.SnackbarLayout.OnLayoutChangeListener)null);

}

});

}

}

而有关动画,谷歌大拿也是废了脑细胞。private void animateViewIn() {        if(VERSION.SDK_INT >= 14) { // 校验当前系统版本 因为属性动画

ViewCompat.setTranslationY(this.mView, (float)this.mView.getHeight());

ViewCompat.animate(this.mView).translationY(0.0F).setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR).setDuration(250L).setListener(new ViewPropertyAnimatorListenerAdapter() {                public void onAnimationStart(View view) {

Snackbar.this.mView.animateChildrenIn(70, 180);

}                public void onAnimationEnd(View view) {                    if(Snackbar.this.mCallback != null) {

Snackbar.this.mCallback.onShown(Snackbar.this);

}

SnackbarManager.getInstance().onShown(Snackbar.this.mManagerCallback);

}

}).start();

} else {            // 如果小于14,直接加载动画

Animation anim = android.view.animation.AnimationUtils.loadAnimation(this.mView.getContext(), anim.design_snackbar_in);

anim.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR);

anim.setDuration(250L);

anim.setAnimationListener(new AnimationListener() {                public void onAnimationEnd(Animation animation) {                    if(Snackbar.this.mCallback != null) {

Snackbar.this.mCallback.onShown(Snackbar.this);

}

SnackbarManager.getInstance().onShown(Snackbar.this.mManagerCallback);

}                public void onAnimationStart(Animation animation) {

}                public void onAnimationRepeat(Animation animation) {

}

});            this.mView.startAnimation(anim);

}

}

看完之后只觉得相当Nice~!以后我们做这个的时候 也需要多加考虑,虽说目前市面主流差不多适配4.0就好,但是个别机型上还是会出现各种奇异问题。

同样在出来也就是消失的时候,也会有相应的动画呈现,具体大家可以参考文档进行参考学习了解即可。

接下来我们来看下有关其引用布局,看看能get什么技能。

这里可以了解到有关使用自定义View的俩种方法,如下:直接引用,当然此方法需要全包名+自定义View名;

通过父节点为View,设置其class同样也可以。

在这里,我们还能学习到如何引用内部类,便是通过如下方式:class="android.support.design.widget.Snackbar$SnackbarLayout"

这里大家如果看到LZ上一篇写的博文,可能会有疑问,LZ是如何知道它的id呢?很easy,下面直接贴出:<?xml  version="1.0" encoding="utf-8"?>

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: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: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:visibility="gone"

android:textColor="?attr/colorAccent"

style="?attr/borderlessButtonStyle"/>

而关于内部自定义View,简单了解下即可:public static class SnackbarLayout extends LinearLayout

LinearLayout,控制content以及按钮。GG了

总结

> 1) 首先,明确SnackBar是与SnackBarManager配合使用;

>

> 2) 其次,内部采用Hanlder进行消息分发;

>

> 3) 随后,初始化时不断查找父布局,知道找到为止,由此可见,SnackBar外部依赖于CoordinatorLayout,而我们实际的布局的最终父节点是FrameLayout。查找完毕后通过一些操作直接添加布局;

>

> 4) 虽说布局写死了,但是我们能通过getView去设置其写死内部样式等等你想要的效果;

>

> 5) 通过同步锁,保证同一时间内只能有一个进行操作,通过了handler分发机制以及栈还有弱引用,使我们的View更加人性化。

当然,也不仅仅如上几点,具体大家还可以自行发掘~

End

真正孤身一人了。。。MMP

偌大的屋子 空无一人

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值