Material Design

中文名:材料设计语言
是一套UI设计标准,
材料指的就是控件

什么是Material Design

  1. 中文名:材料设计语言
    是由Google推出的全新的设计语言。
    谷歌表示,这种设计语言旨在为手机、平板电脑、台式机和“其他平台” \color{#0099ff}{提供更一致、更广泛的外观和感觉}
  2. 是一套UI设计标准;它是android5.0后推出的,本质上来讲多了个Z轴,相当于从2D转换成3D,它不仅可以用在android上,也可以用在网页上。

Material Design能为我们实现什么

  1. Toolbar
  2. CollapsingToolbarLayout
  3. AppBarLayout
  4. CoordinatorLayout
  5. DrawerLayout
  6. NavigationView
  7. ActionBarDrawerToggle
  8. Recyclerview
  9. CardView
    等等

Material Design怎么用

提供了以下主题

@android:style/Theme.Material(深色版本)
@android:style/Theme.Material.Light(浅色版本)
@android:style/Theme.Material.Light.DarkActionBar

在系统默认生成的颜色中

colorPrimary: APP主要颜色,一般用于ActionBar,ToolBar的背景色设置。
colorPrimaryDark:比colorPrimary要深一些的颜色,如果状态栏颜色android:statusBarColor 没有设置固定值将默认为colorPrimaryDark的颜色。
colorAccent: 强调色,用于如FloatingActionButton小控件上起强调突出作用的色彩。

AppCompatActivity 是什么

从Android 21之后引入Material Design的设计方式,为了支持Material Color 、调色板、toolbar等各种新特性,AppCompatActivity就应用而生。代替了原有的ActionBarActivity。在AppCompatActivity中,更是引入了AppCompatDelegate类的设计,可以在普通的Acitivity中使用AppCompate的相关特性。

在平常开发中,Activity继承的是AppCompatActivity,在这个类下面,我们在xml布局文件中缩写的控件,都会被替换成AppCompatXXX控件,以下源码分析
首先进入setContentView方法,实质是delegate的setContentView,delegate是个抽象类AppCompatDelegate,实际实现类是AppCompatDelegateImpl,相当于调用的是AppCompatDelegateImpl的setContentView

# AppCompatActivity
public void setContentView(@LayoutRes int layoutResID) {
    getDelegate().setContentView(layoutResID);
}

public AppCompatDelegate getDelegate() {
    if (mDelegate == null) {
        mDelegate = AppCompatDelegate.create(this, this);
    }
    return mDelegate;
}

# AppCompatDelegate
public abstract class AppCompatDelegate{}

# AppCompatDelegateImpl

class AppCompatDelegateImpl extends AppCompatDelegate
        implements MenuBuilder.Callback, LayoutInflater.Factory2 {
    @Override
    public View createView(View parent, final String name, @NonNull Context context,
                           @NonNull AttributeSet attrs) {
        ...
        //注释 1
        return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,
                IS_PRE_LOLLIPOP, /* Only read android:theme pre-L (L+ handles this anyway) */
                true, /* Read read app:theme as a fallback at all times for legacy reasons */
                VectorEnabledTintResources.shouldBeUsed() /* Only tint wrap the context if enabled */
        );
    }
}

Factory2 监听xml 的生成过程,拦截xml 的生成过程

/**
 * 监听xml 的生成过程,拦截xml 的生成过程
 */
public interface Factory2 extends Factory {
    public View onCreateView(View parent, String name, Context context, AttributeSet attrs);
}

AppCompatDelegateImpl在重写onCreateView中,注释 1处又返回AppCompatViewInflater的createView,实际上就通过名字,创建了一个view,但是,createTextView又是创建了一个AppCompatTextView

# AppCompatViewInflater
final View createView(View parent, final String name, @NonNull Context context,
                      @NonNull AttributeSet attrs, boolean inheritContext,
                      boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
    ...
    switch (name) {
        case "TextView":
            view = createTextView(context, attrs);
            verifyNotNull(view, name);
            break;
        case "ImageView":
            view = createImageView(context, attrs);
            verifyNotNull(view, name);
            break;
        case "Button":
            view = createButton(context, attrs);
            verifyNotNull(view, name);
            break;
            ...
    }
}

@NonNull
protected AppCompatTextView createTextView(Context context, AttributeSet attrs) {
    return new AppCompatTextView(context, attrs);
}

所以,只要是继承了AppCompatActivity,布局中所创建的控件,都是AppCompatxxx,这样又会有MaterialDesign的主题,也会兼容5.0之前

CoordinatorLayout 是什么

CoordinatorLayout是一个\color{#0099ff}{“加强版”FrameLayout},它主要有两个用途:

  1. 用作应用的顶层布局管理器,也就是作为用户界面中所有UI控件的容器
  2. 用作相互之间距有特定交互行为的UI控件的容器;
    通过为CoordinatorLayout的直接子View指定Behavior,就可以实现它们之间的交互行为。
    Behavior可以用来实现一系列的交互行为和布局变化,比如说侧滑菜单、可滑动删除的UI元素,以及跟随着其他
    UI控件移动的按钮等。

Behavior 是什么

Beahvior是CoordinatorLayout的核心组件,使用Behavior可以实现CoordinatorLayout内直接子控件之间的交互。(PS:直接子控件是指CoordinatorLayout的直接子控件)

Behavior就是一个观察者,
重写Behavior,需要重写2个参数的构造方法,因为

/**
 * @param name 这个就是xml中写的名字
 */
static Behavior parseBehavior(Context context, AttributeSet attrs, String name) {
    
    if (name.startsWith(".")) {
        // 这个就是全类名,也就是自己重写的Behavior
        fullName = context.getPackageName() + name;
    } else if (name.indexOf('.') >= 0) {
        fullName = name;
    } else {
        fullName = !TextUtils.isEmpty(WIDGET_PACKAGE_NAME)
                ? (WIDGET_PACKAGE_NAME + '.' + name)
                : name;
    }

    try {
        Map<String, Constructor<Behavior>> constructors = sConstructors.get();
        if (constructors == null) {
            constructors = new HashMap<>();
            sConstructors.set(constructors);
        }
        // 通过反射,获取到自己写的Behavior
        Constructor<Behavior> c = constructors.get(fullName);
        if (c == null) {
            final Class<Behavior> clazz = (Class<Behavior>) context.getClassLoader()
                    .loadClass(fullName);
            // 这里构造方法是2个参数,所以必须要重写2个参数的构造方法
            c = clazz.getConstructor(CONSTRUCTOR_PARAMS);
            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);
    }
}

static final Class<?>[] CONSTRUCTOR_PARAMS = new Class<?>[] {
        Context.class,
        AttributeSet.class
};

/**
 * 在布局的时候,会循环CoordinatorLayout的子控件,如果包含behavior,则进行摆放,
 */
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    final int layoutDirection = ViewCompat.getLayoutDirection(this);
    final int childCount = mDependencySortedChildren.size();
    for (int i = 0; i < childCount; i++) {
        final CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) child.getLayoutParams();
        final Behavior behavior = lp.getBehavior();
        // 如果包含behavior,则进行摆放
        if (behavior == null || !behavior.onLayoutChild(this, child, layoutDirection)) {
            onLayoutChild(child, layoutDirection);
        }
    }
}


/**
 * 通过拦截,调用 resetTouchBehaviors
 */
public boolean onInterceptTouchEvent(MotionEvent ev) {
    final int action = ev.getActionMasked();

    // Make sure we reset in case we had missed a previous important event.
    if (action == MotionEvent.ACTION_DOWN) {
        resetTouchBehaviors(true);
    }

    final boolean intercepted = performIntercept(ev, TYPE_ON_INTERCEPT);

    if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
        resetTouchBehaviors(true);
    }

    return intercepted;
}

/**
 * 获取到child.getLayoutParams()
 * 又通过lp 获得了 Behavior 又进行了时间的拦截和 监听
 */
private void resetTouchBehaviors(boolean notifyOnInterceptTouchEvent) {
    final int childCount = getChildCount();
    for (int i = 0; i < childCount; i++) {
        final View child = getChildAt(i);
        final CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) child.getLayoutParams();
        final Behavior b = lp.getBehavior();
        if (b != null) {
            final long now = SystemClock.uptimeMillis();
            final MotionEvent cancelEvent = MotionEvent.obtain(now, now,
                    MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
            if (notifyOnInterceptTouchEvent) {
                b.onInterceptTouchEvent(this, child, cancelEvent);
            } else {
                b.onTouchEvent(this, child, cancelEvent);
            }
            cancelEvent.recycle();
        }
    }
    ...
}

参考资料

Material Design官网
Material Design说明简书

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。