美文网首页Android-CoordinatorLayout.……
MD - CoordinatorLayout源码分析Behavi

MD - CoordinatorLayout源码分析Behavi

作者: 如愿以偿丶 | 来源:发表于2019-09-29 21:37 被阅读0次

    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组件相结合,用户体验效果更佳。

    相关文章

      网友评论

        本文标题:MD - CoordinatorLayout源码分析Behavi

        本文链接:https://www.haomeiwen.com/subject/qrmqpctx.html