美文网首页
Android MD控件

Android MD控件

作者: PuHJ | 来源:发表于2019-03-20 15:14 被阅读0次

    最近记忆不咋滴,凡事还是拿笔记下来比较好。需要用到的时候,看一眼就可以了。

    一、简单用法

    <?xml version="1.0" encoding="utf-8"?>
    <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <android.support.design.widget.AppBarLayout
            android:id="@+id/appbar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
    
            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="@dimen/actionBarWithStatusBarSize"
                android:background="@color/colorAccent"
                android:paddingTop="@dimen/statusBarSize"
                app:titleTextAppearance="@style/TextAppearance.Title" />
    
        </android.support.design.widget.AppBarLayout>
    
        <android.support.v7.widget.RecyclerView
            android:id="@+id/recycler"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:clipToPadding="false"
            android:padding="@dimen/len_16"
            app:layout_behavior="@string/appbar_scrolling_view_behavior" />
    </android.support.design.widget.CoordinatorLayout>
    

    如上代码所示,这是一个简单的,比较标准的用法。其中:

    • CoordinatorLayout 是一个协调布局,最主要的是,其内部包含了静态的抽象的Behavior<V extends View>类,通过复写该类,并在复写的LayoutParams中解子类的Behavior,可实现该布局下的直接子组件的相互关联,达到协调作用。
    • AppBarLayout 继承的是布局垂直的LinearLayout,内部包含一个AppBarLayout垂直偏移量的改变回调。
    • Toolbar 是Android5.0后出来,代替Actionbar的
    • RecyclerView 就不多说了,集合比较少的时候效率还是ListView高

    二、CoordinatorLayout

    CoordinatorLayout被称为协调布局,原理上就是通过Behavior实现的。

    问题:
    1、CoordinatorLayout的用法?
    2、CoordinatorLayout为什么只能对直接子控件有效?
    3、CoordinatorLayout中的自定义Behavior?

    (1)用法

    第一步:通常讲android.support.design.widget.CoordinatorLayout放在最外层,但也不是绝对的。
    第二步:在子控件中添加Behavior,比如上面代码中的 app:layout_behavior="@string/appbar_scrolling_view_behavior",他是CoordinatorLayout的属性,不是直接子控件是没有用的。
    其中的@string/appbar_scrolling_view_behavior内容是android.support.design.widget.AppBarLayout$ScrollingViewBehavior,指明了Behavior类的位置。

    public static class ScrollingViewBehavior extends HeaderScrollingViewBehavior
    

    ScrollingViewBehavior继承的就是CoordinatorLayout中的Behavior。
    那么CoordinatorLayout是怎么获取到子控件中的Behavior了?
    先看看CoordinatorLayout中的LayoutParams,只摘取主要部分,只有该类才能获取到子控件中的参数。类似于android:layout_width="match_parent",这个是父控件约束子控件用的。

     public static class LayoutParams extends ViewGroup.MarginLayoutParams {
            /**
             * A {@link Behavior} that the child view should obey.
             */
            Behavior mBehavior;
    
            public LayoutParams(int width, int height) {
                super(width, height);
            }
    
            LayoutParams(Context context, 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);
                
                mBehaviorResolved = a.hasValue(
                        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);
                }
            }
    

    如上,解析子控件中包不包含R.styleable.CoordinatorLayout_Layout_layout_behavior,如果存在就开始解析Behavior。
    解析过程:

        static Behavior parseBehavior(Context context, AttributeSet attrs, String name) {
            if (TextUtils.isEmpty(name)) {
                return null;
            }
    
            final String fullName;
            // 开始如果是.开头,那么就需要补全名称
            if (name.startsWith(".")) {
                // Relative to the app package. Prepend the app package name.
                fullName = context.getPackageName() + name;
            } else if (name.indexOf('.') >= 0) {
                // Fully qualified package name.
                fullName = name;
            } else {
                // Assume stock behavior in this package (if we have one)
                fullName = !TextUtils.isEmpty(WIDGET_PACKAGE_NAME)
                        ? (WIDGET_PACKAGE_NAME + '.' + name)
                        : name;
            }
           // 到此定义的Behavior是个全路径了
            try {
               // static final ThreadLocal<Map<String, Constructor<Behavior>>> sConstructors =
                new ThreadLocal<>();
                Map<String, Constructor<Behavior>> constructors = sConstructors.get();
                if (constructors == null) {
                    constructors = new HashMap<>();
                    sConstructors.set(constructors);
                }
                // 通过ThreadLocal缓存
                Constructor<Behavior> c = constructors.get(fullName);
                if (c == null) {
                    final Class<Behavior> clazz = (Class<Behavior>) Class.forName(fullName, true,
                            context.getClassLoader());
                    c = clazz.getConstructor(CONSTRUCTOR_PARAMS);
                    c.setAccessible(true);
                    constructors.put(fullName, c);
                }
                // 通过反射生产Behavior对象
                return c.newInstance(context, attrs);
            } catch (Exception e) {
                throw new RuntimeException("Could not inflate Behavior subclass " + fullName, e);
            }
        }
    

    到此,就知道了CoordinatorLayout和Behavior的大体工作流程了。而且还要重写了ViewGroup.MarginLayoutParams。
    CoordinatorLayout的具体原理,还没研究,也暂不研究了。我觉得只要理解大体工作流程,会写自定义Behavior就可以了。关于自定义Behavior会放在后面介绍。

    三、AppBarLayout

    AppBarLayout相当于一个垂直结构的LinearLayout,例如上面通过ScrollingViewBehavior将滑动的RecyclerView和AppBarLayout绑定在一起,通过对方的滑动来决定AppBarLayout的滑动状态。

    (1)、LayoutParams

    部分源码:

    public static class LayoutParams extends LinearLayout.LayoutParams {
    
            /** @hide */
            @RestrictTo(LIBRARY_GROUP)
            @IntDef(flag=true, value={
                    SCROLL_FLAG_SCROLL,
                    SCROLL_FLAG_EXIT_UNTIL_COLLAPSED,
                    SCROLL_FLAG_ENTER_ALWAYS,
                    SCROLL_FLAG_ENTER_ALWAYS_COLLAPSED,
                    SCROLL_FLAG_SNAP
            })
    

    从代码中可以看出它是继承了LinearLayout.LayoutParams,所以它具有LinearLayout中的所有的loyout属性,自己特有的就是layout_scrollFlags属性。其中只有上面中的5个选项。分别代表的是不同的行为,下面就从字面上说说具体的作用。

    • scroll
      设为scroll的View会跟随滚动事件一起发生移动。如最开始代码,该View会有RecyclerView的滑动一起滑动。
    • enterAlways
      设为enterAlways的View,RecyclerView往下滚动时,该View会直接往下滚动,到底之后在执行RecyclerView的向下滚动。
    • exitUntilCollapsed
      设为exitUntilCollapsed的View,通常要设置该View一个minHeight,往上滑动时,该View直到滑动至它的最小高度后,再响应ScrollView的内部滑动事件。
    • enterAlwaysCollapsed
      enterAlwaysCollapsed一般跟enterAlways一起使用,它是指,View在往下“出现”的时候,首先是RecyclerView效果,当View的高度达到最小高度时,View就暂时不去往下滚动,直到RecyclerView滑动到顶部不再滑动时,View再继续往下滑动,直到滑到View的顶部结束
    • snap
      当松开手指时,AppBarLayout的子View要么向上全部滚出屏幕,要么向下全部滚进屏幕,不会存在滑动在中间的地方。

    (2)、OnOffsetChangedListener

        public interface OnOffsetChangedListener {
            /**
             * Called when the {@link AppBarLayout}'s layout offset has been changed. This allows
             * child views to implement custom behavior based on the offset (for instance pinning a
             * view at a certain y value).
             *
             * @param appBarLayout the {@link AppBarLayout} which offset has changed
             * @param verticalOffset the vertical offset for the parent {@link AppBarLayout}, in px
             */
            void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset);
        }
    

    该回调是AppBarLayout类提供给使用者的,他会时时的返回给调用者AppBarLayout的偏移值,可以根据偏移值来做一些额外的工作,比如显示的动画效果。

    四、CollapsingToolbarLayout 折叠布局

    (1)、作用

    从字面的意思就是,他提供了一个可以折叠的Toolbar,它继承至FrameLayout。通过给直接子控件设置layout_collapseMode和layout_collapseParallaxMultiplier属性,不同的折叠方式和视差因子带来不同的效果。

    (2)、自定义属性

        <declare-styleable name="CollapsingToolbarLayout">
            <attr name="expandedTitleMargin" format="dimension" />
            <attr name="expandedTitleMarginStart" format="dimension" />
            <attr name="expandedTitleMarginTop" format="dimension" />
            <attr name="expandedTitleMarginEnd" format="dimension" />
            <attr name="expandedTitleMarginBottom" format="dimension" />
            <attr name="expandedTitleTextAppearance" format="reference" />
            <attr name="collapsedTitleTextAppearance" format="reference" />
            <attr name="contentScrim" format="color" />
            <attr name="statusBarScrim" format="color" />
            <attr name="toolbarId" format="reference" />
            <attr name="scrimVisibleHeightTrigger" format="dimension" />
            <attr name="scrimAnimationDuration" format="integer" />
            <attr name="collapsedTitleGravity">
    
                <flag name="top" value="0x30" />
    
                <flag name="bottom" value="0x50" />
                
                <flag name="left" value="0x03" />
    
                <flag name="right" value="0x05" />
    
                <flag name="center_vertical" value="0x10" />
    
                <flag name="fill_vertical" value="0x70" />
    
                <flag name="center_horizontal" value="0x01" />
    
                <flag name="center" value="0x11" />
    
                <flag name="start" value="0x00800003" />
    
                <flag name="end" value="0x00800005" />
            </attr>
            <attr name="expandedTitleGravity">
    
                <flag name="top" value="0x30" />
    
                <flag name="bottom" value="0x50" />
    
                <flag name="left" value="0x03" />
    
                <flag name="right" value="0x05" />
    
                <flag name="center_vertical" value="0x10" />
    
                <flag name="fill_vertical" value="0x70" />
    
                <flag name="center_horizontal" value="0x01" />
    
                <flag name="center" value="0x11" />
    
                <flag name="start" value="0x00800003" />
    
                <flag name="end" value="0x00800005" />
            </attr>
            <attr name="titleEnabled" format="boolean" />
            <attr name="title" />
        </declare-styleable>
    
    • expandedTitleMargin、expandedTitleMarginStart、expandedTitleMarginTop、expandedTitleMarginEnd、expandedTitleMarginBottom
      展开状态下,标题栏的位置信息,通过Margin值来决定的。而闭合非展开时候的标题栏的位置是固定好的。
    • expandedTitleTextAppearance
      展开状态下,Toolbar标题的样式设置
    • collapsedTitleTextAppearance
      折叠状态下,Toolbar标题的样式设置
    • contentScrim
      设置折叠时工具栏布局的颜色
    • statusBarScrim
      设置折叠时状态栏的颜色
    • toolbarId
      绑定的Toolbar的ID
    • collapsedTitleGravity
      折叠状态的标题如何放置,可选值:top、bottom等
    • expandedTitleGravity
      展开状态的标题如何放置,可选值:top、bottom等

    (3)、CollapsingToolbarLayout.LayoutParams

    直接看解析布局代码:

    private static final float DEFAULT_PARALLAX_MULTIPLIER = 0.5f;
    
    @RestrictTo(GROUP_ID)
            @IntDef({
                    COLLAPSE_MODE_OFF,
                    COLLAPSE_MODE_PIN,
                    COLLAPSE_MODE_PARALLAX
            })
            @Retention(RetentionPolicy.SOURCE)
            @interface CollapseMode {}
    
     public LayoutParams(Context c, AttributeSet attrs) {
                super(c, attrs);
    
                TypedArray a = c.obtainStyledAttributes(attrs,
                        R.styleable.CollapsingToolbarLayout_Layout);
                mCollapseMode = a.getInt(
                        R.styleable.CollapsingToolbarLayout_Layout_layout_collapseMode,
                        COLLAPSE_MODE_OFF);
                setParallaxMultiplier(a.getFloat(
                        R.styleable.CollapsingToolbarLayout_Layout_layout_collapseParallaxMultiplier,
                        DEFAULT_PARALLAX_MULTIPLIER));
                a.recycle();
            }
    

    一共有两个布局属性,app:layout_collapseMode="parallax"和app:layout_collapseParallaxMultiplier="0.7",也就是展开模式和视差因子。

    • layout_collapseMode
      1、off:这个是默认属性,布局将正常显示,没有折叠的行为。
      2、pin:CollapsingToolbarLayout折叠后,此布局将固定在顶部。
      3、parallax:CollapsingToolbarLayout折叠时,此布局也会有视差折叠效果。

    • layout_collapseParallaxMultiplier
      视差滚动因子,值为:0~1。

    (4)、简单例子

    <?xml version="1.0" encoding="utf-8"?>
    <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/main_content"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fitsSystemWindows="true">
    
        <android.support.design.widget.AppBarLayout
            android:id="@+id/appbar"
            android:layout_width="match_parent"
            android:layout_height="156dp"
            android:fitsSystemWindows="true"
            android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
    
            <android.support.design.widget.CollapsingToolbarLayout
                android:id="@+id/collapsing_toolbar"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:fitsSystemWindows="true"
                app:collapsedTitleTextAppearance="@style/TextAppearance.Title"
                app:contentScrim="#647743"
                app:expandedTitleGravity="bottom|center_horizontal"
                app:expandedTitleMarginEnd="64dp"
                app:expandedTitleMarginStart="48dp"
                app:expandedTitleTextAppearance="@style/TextAppearance.Title"
                app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
                app:statusBarScrim="@android:color/transparent">
    
                <ImageView
                    android:id="@+id/backdrop"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:fitsSystemWindows="true"
                    android:scaleType="centerCrop"
                    android:src="@mipmap/ic_launcher"
                    app:layout_collapseMode="parallax"
                    app:layout_collapseParallaxMultiplier="1" />
    
                <android.support.v7.widget.Toolbar
                    android:id="@+id/tool_bar"
                    android:layout_width="match_parent"
                    android:layout_height="?attr/actionBarSize"
                    app:layout_collapseMode="pin"
                    app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
    
            </android.support.design.widget.CollapsingToolbarLayout>
    
        </android.support.design.widget.AppBarLayout>
    
        <android.support.v4.widget.NestedScrollView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_behavior="@string/appbar_scrolling_view_behavior">
          
               ....省略
    
        </android.support.v4.widget.NestedScrollView>
    
        <android.support.design.widget.FloatingActionButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="10dp"
            android:clickable="true"
            android:src="@mipmap/ic_launcher"
            app:layout_anchor="@id/appbar"   // 设置一个锚点
            app:layout_anchorGravity="bottom|end" />
    
    </android.support.design.widget.CoordinatorLayout>
    

    四、Toolbar

    (1)、简单用法

    • 布局
    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/activity_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <android.support.v7.widget.Toolbar
            android:paddingTop="25dp"
            android:background="@color/colorPrimary"
            android:id= "@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="73dp">
        </android.support.v7.widget.Toolbar>
    </RelativeLayout>
    
    • menu布局
    <?xml version="1.0" encoding="utf-8"?>
    <menu xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto">
    
        <item
            android:id="@+id/action_edit"
            android:icon="@mipmap/ic_launcher"
            android:orderInCategory="80"
            android:title="删除"
            app:showAsAction="ifRoom" />
    
        <item
            android:id="@+id/action_share"
            android:icon="@mipmap/ic_launcher"
            android:orderInCategory="90"
            android:title="编辑"
            app:showAsAction="ifRoom" />
    
        <item
            android:id="@+id/action_settings"
            android:icon="@mipmap/ic_launcher"
            android:orderInCategory="100"
            android:title="设置"
            app:showAsAction="ifRoom" />
    </menu>
    
    
    • Theme
    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
            <!-- Customize your theme here. -->
            <item name="colorPrimary">@color/colorPrimary</item>
            <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
            <item name="colorAccent">@color/colorAccent</item>
    
            <item name="windowActionBar">false</item>
            <item name="android:windowNoTitle">true</item>
            <item name="windowNoTitle">true</item>
            <item name="android:statusBarColor">@null</item>
    
            <!--去除顶部的状态栏-->
            <item name="android:windowTranslucentStatus">true</item>
            <item name="android:windowIsTranslucent">true</item>
            <item name="android:windowAnimationStyle">@null</item>
            <item name="android:windowIsFloating">false</item>
            <item name="android:windowFrame">@null</item>
    
            <!--去除默认阴影-->
            <item name="android:elevation">0dp</item>
            <item name="android:outlineProvider">none</item>
    
            <item name="actionBarSize">48dp</item>
    
            <item name="toolbarTitleColor">@color/colorAccent</item>
            <item name="toolbarTitleSize">30sp</item>
        </style>
    
    • Activity
     @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_toolbar);
    
    
    
            Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    
            // 主标题
            toolbar.setTitle("Title");
    
            //设置toolbar
            setSupportActionBar(toolbar);
    
            //左边的小箭头(注意需要在setSupportActionBar(toolbar)之后才有效果)
            toolbar.setNavigationIcon(R.drawable.ic_back);
    
            //菜单点击事件(注意需要在setSupportActionBar(toolbar)之后才有效果)
            toolbar.setOnMenuItemClickListener(this);
        }
    
        @Override
        public boolean onMenuItemClick(MenuItem item) {
            Toast.makeText(this,item.getTitle(),Toast.LENGTH_SHORT).show();
            return true;
        }
    
        @Override
        public boolean onCreateOptionsMenu(Menu menu) {
            getMenuInflater().inflate(R.menu.menu_toolbar, menu);
            return true;
        }
    

    五、自定义Behavior

    Behavior是关联着CoordinatorLayout下面的直接子布局,让他们能够相互监听对方滑动状态。CoordinatorLayout只是相当于一个桥梁的作用。
    下面通过一个自定义android.support.design.widget.FloatingActionButton的Behavior,让向上滑动的时候,FloatingActionButton显示,向下滑动时隐藏。

    public class TranslationBehavior extends FloatingActionButton.Behavior {
        public TranslationBehavior(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        // 关注垂直滚动 ,而且向上的时候是出来,向下是隐藏
        @Override
        public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, FloatingActionButton child, View directTargetChild, View target, int nestedScrollAxes) {
            return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL;
        }
    
        private boolean isOut = false;
    
        @Override
        public void onNestedScroll(CoordinatorLayout coordinatorLayout, FloatingActionButton child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
            super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
            // 而且向上的时候是出来,向下是隐藏
            if (dyConsumed > 0) {
                if (!isOut) {
                    int translationY = ((CoordinatorLayout.LayoutParams) child.getLayoutParams()).bottomMargin + child.getMeasuredHeight();
                    child.animate().cancel();
                    child.animate().translationY(translationY).setDuration(200).start();
                    isOut = true;
                }
            } else {
                if (isOut) {
                    // 往下滑动
                    child.animate().cancel();
                    child.animate().translationY(0).setDuration(200).start();
                    isOut = false;
                }
            }
        }
    }
    

    相关文章

      网友评论

          本文标题:Android MD控件

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