Android-CoordinatorLayout.……

MD - CoordinatorLayout源码分析Behavi

2019-09-29  本文已影响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组件相结合,用户体验效果更佳。

上一篇下一篇

猜你喜欢

热点阅读