MD - CoordinatorLayout源码分析Behavior工作流程

1.概述

  Material Design从Android5.0开始引入的,是一种全新的设计语言(翻译为“原材料设计”),其实是谷歌提倡的一种设计风格、理念、原则。结合Behavior实现非常酷炫的效果。接下来从上一篇文章 MD - 简单控件使用以及自定义Behavior 来从源码角度分析一下CoordinatorLayout和Behavior的工作流程。

  先看一下效果:

              在这里插入图片描述

2.分析前先思考三个问题

  1.Behavior要有效果为什么必须要使用CoordinatorLayout包裹
  2.为什么自定义的Behavior要放全类名
  3.Behavior效果怎么传递的

3. CoordinatorLayout源码分析

  带着上面三个问题来一一进行分析

  3.1.CoordinatorLayout的LayoutParams首先会获取子类属性。进行解析。

    打个比方只能在对应下才有效:
         LinearLayout:android:layout_weight属性
         RelativeLayout:android:layout_centerInParent属性

		LayoutParams(@NonNull Context context, @Nullable AttributeSet attrs) {
            super(context, attrs);

            final TypedArray a = context.obtainStyledAttributes(attrs,
                    R.styleable.CoordinatorLayout_Layout);

            this.gravity = a.getInteger(
                    R.styleable.CoordinatorLayout_Layout_android_layout_gravity,
                    Gravity.NO_GRAVITY);
         	//1.获取我们layout_behavior属性,如果有值就为true
            mBehaviorResolved = a.hasValue(
                    R.styleable.CoordinatorLayout_Layout_layout_behavior);
             
           /**
		    * 2.进行解析属性
		    * 	获取属性的值 a.getString(R.styleable.CoordinatorLayout_Layout_layout_behavior) 
		    */
            
            if (mBehaviorResolved) {
                mBehavior = parseBehavior(context, attrs, a.getString(
                        R.styleable.CoordinatorLayout_Layout_layout_behavior));
            }
            a.recycle();
			//
            if (mBehavior != null) {
                // If we have a Behavior, dispatch that it has been attached
                mBehavior.onAttachedToLayoutParams(this);
            }
        }
  3.2.解析Behavior属性值通过反射实例化对象

    步骤:1.获取我们布局中设置的全类名,xxx.xxx.xx 或者 .xxx
       2.通过获取类名获取class,获取两个参数的构造方法
       3.通过反射实例化 Behavior对象,newInstance(),所有的Behavior都会放到集合中
    ThreadLoacal:保护线程安全,一个线程对应一个ThreadLocal

	  static Behavior parseBehavior(Context context, AttributeSet attrs, String name) {
	  		// name就是app:layout_behavior的属性值
	        if (TextUtils.isEmpty(name)) {
	            return null;
	        }
	        
	        final String fullName;
	        //如果是以.开头,它会加上包名
	        if (name.startsWith(".")) {
	            fullName = context.getPackageName() + name;
	        } else if (name.indexOf('.') >= 0) {
	            fullName = name;
	        } else {
	            fullName = !TextUtils.isEmpty(WIDGET_PACKAGE_NAME)
	                    ? (WIDGET_PACKAGE_NAME + '.' + name)
	                    : name;
	        }
	
			//重点:
	        try {
	            /**
		    	 * 获取构造函数,进行存储   使用了ThreadLaocal来保护线程安全,可以去看一下Handler的源码。
		    	 * 	String:代表我们的类名
		    	 *  Constructor<Behavior> 是我们的构造方法
		    	 */
	            Map<String, Constructor<Behavior>> constructors = sConstructors.get();
	            if (constructors == null) {
	                constructors = new HashMap<>();
	                sConstructors.set(constructors);
	            }
	            Constructor<Behavior> c = constructors.get(fullName);
	            if (c == null) {
	            	//获取我们自定义Behavior的class对象
	                final Class<Behavior> clazz = (Class<Behavior>) context.getClassLoader()
	                        .loadClass(fullName);
	                c = clazz.getConstructor(CONSTRUCTOR_PARAMS);
	                //暴力访问所有private修饰的,不设置不能获取到私有的属性
	                c.setAccessible(true);
	                constructors.put(fullName, c);
	            }
	            return c.newInstance(context, attrs);
	        } catch (Exception e) {
	            throw new RuntimeException("Could not inflate Behavior subclass " + fullName, e);
	        }
	    }
  3.3.Behavior是通过RecyclerView的onTouchEvent进行传递

         在这里插入图片描述
1.RecyclerView:onTouchEvent方法

	 @Override
	 public boolean onTouchEvent(MotionEvent e) {
	        switch (action) {
	            case MotionEvent.ACTION_DOWN: {
					//调用了startNestedScroll方法
	                startNestedScroll(nestedScrollAxis, TYPE_TOUCH);
	            } break;
	
	        return true;
	 }

2.ViewParentCompat:onStartNestedScroll方法

	public static boolean onStartNestedScroll(ViewParent parent, View child, View target,
            int nestedScrollAxes, int type) {
            if (Build.VERSION.SDK_INT >= 21) {
                try {
                	//调用了父类(CoordinatorLayout) 的onStartNestedScroll
                    return parent.onStartNestedScroll(child, target, nestedScrollAxes);
                } catch (AbstractMethodError e) {
                    Log.e(TAG, "ViewParent " + parent + " does not implement interface "
                            + "method onStartNestedScroll", e);
                }
            } 
            
        	return false;
    }

3.CoordinatorLayout:onStartNestedScroll方法

	@Override
    public boolean onStartNestedScroll(View child, View target, int axes, int type) {
        boolean handled = false;
		//1.获取子孩子个数
        final int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View view = getChildAt(i);
            if (view.getVisibility() == View.GONE) {
                // If it's GONE, don't dispatch
                continue;
            }
            
            //2.获取子孩子的Behavior
            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
            final Behavior viewBehavior = lp.getBehavior();
            if (viewBehavior != null) {
            	//3.调用子View的onStartNestedScroll方法
                final boolean accepted = viewBehavior.onStartNestedScroll(this, view, child,
                        target, axes, type);
                handled |= accepted;
                lp.setNestedScrollAccepted(type, accepted);
            } else {
                lp.setNestedScrollAccepted(type, false);
            }
        }
        return handled;
    }

4.由此可知所有的方法,都是通过子类传递给父类,调用父类的方法完成

4.SnackBar 源码分析

  先看一下效果:
              在这里插入图片描述

  4.1.使用:

	floatingActionButton = findViewById(R.id.fab);
        floatingActionButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Snackbar.make(v,"haha",Snackbar.LENGTH_SHORT).setAction("你好", new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        Toast.makeText(BehaviorActivity.this,"再见",Toast.LENGTH_SHORT).show();
                    }
                }).show();
            }
        });

  4.2.源码分析:

    1.首先查找父容器,必须是CoordinatorLayout 或者 FramLayout
    2.通过渲染一个布局进行添加,设置文本时间
    3.通过show方法通过Handler开启了两个动画 showView() 和 hideView()

	@NonNull
    public static Snackbar make(@NonNull View view, @NonNull CharSequence text, int duration) {
		//1.查找父容器,必须是CoordinatorLayout 或者 FramLayout
        ViewGroup parent = findSuitableParent(view);
        if (parent == null) {
            throw new IllegalArgumentException("No suitable parent found from the given view. Please provide a valid view.");
        } else {
			//2.加载系统的布局,布局就只有一个TextView 和一个 Button
            LayoutInflater inflater = LayoutInflater.from(parent.getContext());
            SnackbarContentLayout content = (SnackbarContentLayout)inflater.inflate(hasSnackbarButtonStyleAttr(parent.getContext()) ? layout.mtrl_layout_snackbar_include : layout.design_layout_snackbar_include, parent, false);
            Snackbar snackbar = new Snackbar(parent, content, content);
            snackbar.setText(text);
            snackbar.setDuration(duration);
            return snackbar;
        }
    }

	//以view为起点寻找合适的父布局必须是CoordinatorLayout 或者 FramLayout
	private static ViewGroup findSuitableParent(View view) {
        ViewGroup fallback = null;
        do {
            if (view instanceof CoordinatorLayout) {
                return (ViewGroup)view;
            }
            if (view instanceof FrameLayout) {
            	//id就是android.R.id.content,可以查看布局加载流程setContentView
                if (view.getId() == 16908290) {
                    return (ViewGroup)view;
                }
                fallback = (ViewGroup)view;
            }
            if (view != null) {
                ViewParent parent = view.getParent();
                view = parent instanceof View ? (View)parent : null;
            }
        } while(view != null);
        return fallback;
    }

 public void show(int duration, SnackbarManager.Callback callback) {
        synchronized(this.lock) {
            if (this.isCurrentSnackbarLocked(callback)) {
                this.currentSnackbar.duration = duration;
                this.handler.removeCallbacksAndMessages(this.currentSnackbar);
                //1.Hanlder发送消息开启动画
                this.scheduleTimeoutLocked(this.currentSnackbar);
            } else {
                if (this.isNextSnackbarLocked(callback)) {
                    this.nextSnackbar.duration = duration;
                } else {
                    this.nextSnackbar = new SnackbarManager.SnackbarRecord(duration, callback);
                }

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


    private void scheduleTimeoutLocked(SnackbarManager.SnackbarRecord r) {
        if (r.duration != -2) {
            int durationMs = 2750;
            if (r.duration > 0) {
                durationMs = r.duration;
            } else if (r.duration == -1) {
                durationMs = 1500;
            }
            this.handler.removeCallbacksAndMessages(r);
            //1.发送消息
            this.handler.sendMessageDelayed(Message.obtain(this.handler, 0, r), (long)durationMs);
        }
    }

	//Handler处理消息
	static {
        USE_OFFSET_API = VERSION.SDK_INT >= 16 && VERSION.SDK_INT <= 19;
        SNACKBAR_STYLE_ATTR = new int[]{attr.snackbarStyle};
        handler = new Handler(Looper.getMainLooper(), new android.os.Handler.Callback() {
            public boolean handleMessage(Message message) {
                switch(message.what) {
                case 0:
                    ((BaseTransientBottomBar)message.obj).showView();
                    return true;
                case 1:
                    ((BaseTransientBottomBar)message.obj).hideView(message.arg1);
                    return true;
                default:
                    return false;
                }
            }
        });
    }
    //开启动画显示
	final void showView() {
        if (ViewCompat.isLaidOut(this.view)) {
            if (this.shouldAnimate()) {
                this.animateViewIn();
            } else {
                this.onViewShown();
            }
        } else {
            this.view.setOnLayoutChangeListener(new BaseTransientBottomBar.OnLayoutChangeListener() {
                public void onLayoutChange(View view, int left, int top, int right, int bottom) {
                    BaseTransientBottomBar.this.view.setOnLayoutChangeListener((BaseTransientBottomBar.OnLayoutChangeListener)null);
                    if (BaseTransientBottomBar.this.shouldAnimate()) {
                        BaseTransientBottomBar.this.animateViewIn();
                    } else {
                        BaseTransientBottomBar.this.onViewShown();
                    }

                }
            });
        }
    }

    //进入动画,使用的是属性动画,设置监听等信息
    void animateViewIn() {
        final int translationYBottom = this.getTranslationYBottom();
        if (USE_OFFSET_API) {
            ViewCompat.offsetTopAndBottom(this.view, translationYBottom);
        } else {
            this.view.setTranslationY((float)translationYBottom);
        }

        ValueAnimator animator = new ValueAnimator();
        animator.setIntValues(new int[]{translationYBottom, 0});
        animator.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR);
        animator.setDuration(250L);
        animator.addListener(new AnimatorListenerAdapter() {
            public void onAnimationStart(Animator animator) {
                BaseTransientBottomBar.this.contentViewCallback.animateContentIn(70, 180);
            }

            public void onAnimationEnd(Animator animator) {
                BaseTransientBottomBar.this.onViewShown();
            }
        });
        animator.addUpdateListener(new AnimatorUpdateListener() {
            private int previousAnimatedIntValue = translationYBottom;

            public void onAnimationUpdate(ValueAnimator animator) {
                int currentAnimatedIntValue = (Integer)animator.getAnimatedValue();
                if (BaseTransientBottomBar.USE_OFFSET_API) {
                    ViewCompat.offsetTopAndBottom(BaseTransientBottomBar.this.view, currentAnimatedIntValue - this.previousAnimatedIntValue);
                } else {
                    BaseTransientBottomBar.this.view.setTranslationY((float)currentAnimatedIntValue);
                }

                this.previousAnimatedIntValue = currentAnimatedIntValue;
            }
        });
        animator.start();
    }

	//弹出动画
    private void animateViewOut(final int event) {
        ValueAnimator animator = new ValueAnimator();
        animator.setIntValues(new int[]{0, this.getTranslationYBottom()});
        animator.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR);
        animator.setDuration(250L);
        animator.addListener(new AnimatorListenerAdapter() {
            public void onAnimationStart(Animator animator) {
                BaseTransientBottomBar.this.contentViewCallback.animateContentOut(0, 180);
            }

            public void onAnimationEnd(Animator animator) {
                BaseTransientBottomBar.this.onViewHidden(event);
            }
        });
        animator.addUpdateListener(new AnimatorUpdateListener() {
            private int previousAnimatedIntValue = 0;

            public void onAnimationUpdate(ValueAnimator animator) {
                int currentAnimatedIntValue = (Integer)animator.getAnimatedValue();
                if (BaseTransientBottomBar.USE_OFFSET_API) {
                    ViewCompat.offsetTopAndBottom(BaseTransientBottomBar.this.view, currentAnimatedIntValue - this.previousAnimatedIntValue);
                } else {
                    BaseTransientBottomBar.this.view.setTranslationY((float)currentAnimatedIntValue);
                }

                this.previousAnimatedIntValue = currentAnimatedIntValue;
            }
        });
        animator.start();
    }

5.Snackbar与Dialog和Toast的比较

  通过上文的介绍,我们知道了Snackbar和Dialog、Toast一样都是用来作为android内提示信息的,三者之间的应用场景也有所不同。

5.1.Dialog

  模态对话框。也就说,此刻该对话框中的内容获取了焦点,想要操作对话框以外的功能,必须先对该对话框进行响应。
应用场景:对于删除确认、版本更新等重要性提示信息,需要用户做出选择的情况下,使用Dialog。

5.2.Toast

  非模态提示框。也就说提示框的显示并不影响我们对其他地方的操作,Toast无法手动控制隐藏,需要设置Toast的显示时长,一旦显示时间结束,Toast会自动消失。如果多次点击并显示Toast,就会出现Toast重复创建并显示,给用户造成一种Toast长时间不隐藏的幻觉。
  应用场景:对于无网络提示、删除成功、发布操作完成等这类不重要的提示性信息,使用Toast;

5.3.Snackbar

  Snackbar和Toast比较相似,但是用途更加广泛,并且它是可以和用户进行交互的。Snackbar使用一个动画效果从屏幕的底部弹出来,过一段时间后也会自动消失。
  应用场景:删除操作时,弹出Snackbar用于确认删除操作;消息发送失败时,弹出Snackbar,用于重新发送操作;当然重要的是与MD组件相结合,用户体验效果更佳。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值