Android MD控件

2019-03-20  本文已影响0人  PuHJ

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

一、简单用法

<?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

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个选项。分别代表的是不同的行为,下面就从字面上说说具体的作用。

(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>

(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",也就是展开模式和视差因子。

(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>
<?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>

<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>
 @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;
            }
        }
    }
}
上一篇下一篇

猜你喜欢

热点阅读