在谷歌提出 material design 之后,终于推出了 android.support.design 这个官方的material design库,这几天我也简单浏览了下这个库,基本上我们常用的组件都有了,从今天开始,就可以一步步替换掉
以前使用的github上的那些开源控件了,毕竟谷歌出品 才属精品~~另外分析这个design库的源码我认为是非常有意义的,android上的app 在以前各家都有各家的风格,但是在谷歌出了material design这门新的
设计语言以及官方的库以后,相信越来越多的app 会逐步优化自己的ui 来符合官方的标准,学习这个design库的源码可以让我们以后改写自定义控件的时候更加柔韧有余。
首先,来看一下这个官方的介绍。http://www.google.com/design/spec/components/snackbars-toasts.html#
这个文章系统的阐述了 snackbar和toast的区别和正确使用snackbar的方式。
我简单归纳如下:
1.比toast更加好,毕竟snackbar 可以响应点击事件
2.snackbar 同一时间有且只有一个在显示。
3.snackbar 上不要有图标
4.snackbar上action 只能有一个。
5.如果有悬浮按钮 floating action button的话,snackbar 在弹出的时候 不要覆盖这个button.
6.此外我个人认为snackbar 在一定程度上可以替代dialog的某些应用场景。比如以前网络不通的情况下 我们登陆失败,会给一个dialog提示,现在就可以用snackbar 来做这个有action的提示 更加方便快捷。
使用snackbar:
1.导入support design 库 (这一步在以后的design库的 控件文章里都会舍去)
首先找到你app的build gradle文件
然后增加一个compile语句即可
compile 'com.android.support:design:22.2.0'
2.编写xml文件以及java文件
1 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 xmlns:tools="http://schemas.android.com/tools" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" 5 android:paddingBottom="@dimen/activity_vertical_margin" 6 android:paddingLeft="@dimen/activity_horizontal_margin" 7 android:paddingRight="@dimen/activity_horizontal_margin" 8 android:paddingTop="@dimen/activity_vertical_margin" 9 tools:context=".MainActivity" 10 android:id="@+id/layout"> 11 12 13 <!-- 因为snackbar 需要有一个父控件所以 我们暂时就用tv 来做他的父控件--> 14 <TextView 15 android:id="@+id/tv" 16 android:layout_width="match_parent" 17 android:layout_height="30dp" 18 android:layout_centerVertical="true" 19 android:gravity="center" 20 android:text="Bottom layout" /> 21 22 </RelativeLayout>
1 package com.example.burning.myapplication; 2 3 import android.os.Bundle; 4 import android.support.design.widget.Snackbar; 5 import android.support.v7.app.ActionBarActivity; 6 import android.view.Menu; 7 import android.view.MenuItem; 8 import android.view.View; 9 import android.widget.TextView; 10 11 12 public class MainActivity extends ActionBarActivity { 13 14 private TextView tv; 15 16 @Override 17 protected void onCreate(Bundle savedInstanceState) { 18 super.onCreate(savedInstanceState); 19 setContentView(R.layout.activity_main); 20 tv = (TextView) this.findViewById(R.id.tv); 21 tv.setOnClickListener(new View.OnClickListener() { 22 @Override 23 public void onClick(View v) { 24 //这个地方第一个参数 传进去的是tv 但是实际上你无论传进去什么值 snackbar都一定是从屏幕的最底端出现的 原因在源码 25 //分析那边可以看到 26 Snackbar.make(tv, "connection error", Snackbar.LENGTH_LONG).setAction("retry", new View.OnClickListener() { 27 @Override 28 public void onClick(View v) { 29 tv.setText("aleady click snackbar"); 30 } 31 }).show(); 32 33 } 34 }); 35 } 36 37 @Override 38 public boolean onCreateOptionsMenu(Menu menu) { 39 // Inflate the menu; this adds items to the action bar if it is present. 40 getMenuInflater().inflate(R.menu.menu_main, menu); 41 return true; 42 } 43 44 @Override 45 public boolean onOptionsItemSelected(MenuItem item) { 46 // Handle action bar item clicks here. The action bar will 47 // automatically handle clicks on the Home/Up button, so long 48 // as you specify a parent activity in AndroidManifest.xml. 49 int id = item.getItemId(); 50 51 //noinspection SimplifiableIfStatement 52 if (id == R.id.action_settings) { 53 return true; 54 } 55 56 return super.onOptionsItemSelected(item); 57 } 58 }
最后我们来看下效果
然后我们来看一下 如果和正常的FAB(悬浮按钮)在一起会有什么效果(注意这里的悬浮按钮我们也使用design库里的并不使用github上开源的)
先看一下xml文件
1 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 xmlns:app="http://schemas.android.com/apk/res-auto" 3 xmlns:tools="http://schemas.android.com/tools" 4 android:layout_width="match_parent" 5 android:layout_height="match_parent" 6 android:paddingBottom="@dimen/activity_vertical_margin" 7 android:paddingLeft="@dimen/activity_horizontal_margin" 8 android:paddingRight="@dimen/activity_horizontal_margin" 9 android:paddingTop="@dimen/activity_vertical_margin" 10 tools:context=".MainActivity"> 11 12 13 <FrameLayout 14 android:id="@+id/layout" 15 android:layout_width="match_parent" 16 android:layout_height="match_parent"> 17 18 <android.support.design.widget.FloatingActionButton 19 android:id="@+id/btnFloatingAction" 20 android:layout_width="wrap_content" 21 android:layout_height="wrap_content" 22 android:layout_gravity="bottom|right" 23 android:src="@drawable/ic_plus" 24 app:borderWidth="0dp" 25 app:fabSize="normal" /> 26 </FrameLayout> 27 </RelativeLayout>
activity代码
1 package com.example.burning.myapplication; 2 3 import android.os.Bundle; 4 import android.support.design.widget.Snackbar; 5 import android.support.v7.app.ActionBarActivity; 6 import android.view.Menu; 7 import android.view.MenuItem; 8 import android.view.View; 9 import android.view.ViewGroup; 10 11 12 public class MainActivity extends ActionBarActivity { 13 14 private ViewGroup layout; 15 16 17 @Override 18 protected void onCreate(Bundle savedInstanceState) { 19 super.onCreate(savedInstanceState); 20 setContentView(R.layout.activity_main); 21 layout = (ViewGroup) this.findViewById(R.id.layout); 22 layout.setOnClickListener(new View.OnClickListener() { 23 @Override 24 public void onClick(View v) { 25 Snackbar.make(layout, "connection error", Snackbar.LENGTH_LONG).setAction("retry", new View.OnClickListener() { 26 @Override 27 public void onClick(View v) { 28 } 29 }).show(); 30 31 } 32 }); 33 } 34 35 @Override 36 public boolean onCreateOptionsMenu(Menu menu) { 37 // Inflate the menu; this adds items to the action bar if it is present. 38 getMenuInflater().inflate(R.menu.menu_main, menu); 39 return true; 40 } 41 42 @Override 43 public boolean onOptionsItemSelected(MenuItem item) { 44 // Handle action bar item clicks here. The action bar will 45 // automatically handle clicks on the Home/Up button, so long 46 // as you specify a parent activity in AndroidManifest.xml. 47 int id = item.getItemId(); 48 49 //noinspection SimplifiableIfStatement 50 if (id == R.id.action_settings) { 51 return true; 52 } 53 54 return super.onOptionsItemSelected(item); 55 } 56 }
来看一下运行效果
大家可以看到当我们的snackbar在弹出的时候 会覆盖到我们的FAB,那这体验是非常糟糕的,这里也给出一个完美的解决方案
其实也很简单用一下design库里的layout即可 java代码不需要改变 只要稍微改一下布局文件即可
1 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 xmlns:app="http://schemas.android.com/apk/res-auto" 3 xmlns:tools="http://schemas.android.com/tools" 4 android:layout_width="match_parent" 5 android:layout_height="match_parent" 6 android:paddingBottom="@dimen/activity_vertical_margin" 7 android:paddingLeft="@dimen/activity_horizontal_margin" 8 android:paddingRight="@dimen/activity_horizontal_margin" 9 android:paddingTop="@dimen/activity_vertical_margin" 10 tools:context=".MainActivity"> 11 12 13 <android.support.design.widget.CoordinatorLayout 14 android:id="@+id/layout" 15 android:layout_width="match_parent" 16 android:layout_height="match_parent"> 17 18 <android.support.design.widget.FloatingActionButton 19 android:id="@+id/btnFloatingAction" 20 android:layout_width="wrap_content" 21 android:layout_height="wrap_content" 22 android:layout_gravity="bottom|right" 23 android:src="@drawable/ic_plus" 24 app:borderWidth="0dp" 25 app:fabSize="normal" /> 26 </android.support.design.widget.CoordinatorLayout> 27 </RelativeLayout>
就是换了一个新的layout而已。
来看下运行的效果。
当然了 你要改变这个snackbar的背景色也是可以的 只需要
1 Snackbar sb=Snackbar.make(layout, "connection error", Snackbar.LENGTH_LONG).setAction("retry", new View.OnClickListener() { 2 @Override 3 public void onClick(View v) { 4 } 5 }); 6 //红色 7 sb.getView().setBackgroundColor(0xfff44336); 8 sb.show();
基本的用法 要点就是这些,我们来看一下这个snackbar的源码 看看谷歌官方是如何编写 material design 风格控件的
这里我无法贴上全部源码 因为太多,只挑重要的流程说 可以从make开始。
1 //这个地方就是构造函数 2 Snackbar(ViewGroup parent) { 3 this.mParent = parent; 4 this.mContext = parent.getContext(); 5 LayoutInflater inflater = LayoutInflater.from(this.mContext); 6 this.mView = (Snackbar.SnackbarLayout)inflater.inflate(layout.layout_snackbar, this.mParent, false); 7 } 8 9 //这个地方就是我们调用的make函数 也就是整个类的一个入口 在这里可以看到 我们的代码转入了snackbar的构造函数 10 public static Snackbar make(View view, CharSequence text, int duration) { 11 //注意看这个地方调用了 findsuitableparent这个函数 12 Snackbar snackbar = new Snackbar(findSuitableParent(view)); 13 snackbar.setText(text); 14 snackbar.setDuration(duration); 15 return snackbar; 16 } 17 18 public static Snackbar make(View view, int resId, int duration) { 19 return make(view, view.getResources().getText(resId), duration); 20 } 21 22 //这个函数其实作用就是无论你传进去的是什么view 我最终都会遍历到framlayout为止,因为activity的最外层实际上就是一个framlayout 23 //所以你在调用make函数的时候无论传什么值进去 snackabr都会从最底部弹出来 就是因为这个函数做了这样的工作 但是!!!! 24 //这个地方CoordinatorLayout的优先级比framlayout还要高 所以你如果穿进去的view是CoordinatorLayout的话 这个snackbar 就一定会从 25 //CoordinatorLayout 底部弹出了。如果你CoordinatorLayout的最底部恰好在屏幕中间 那么snackbar 就会从屏幕中间弹出 而不会从底部弹出 这一点一定要注意 26 @Nullable 27 private static ViewGroup findSuitableParent(View view) { 28 ViewGroup fallback = null; 29 30 do { 31 if(view instanceof CoordinatorLayout) { 32 return (ViewGroup)view; 33 } 34 35 if(view instanceof FrameLayout) { 36 if(view.getId() == 16908290) { 37 return (ViewGroup)view; 38 } 39 40 fallback = (ViewGroup)view; 41 } 42 43 if(view != null) { 44 ViewParent parent = view.getParent(); 45 view = parent instanceof View?(View)parent:null; 46 } 47 } while(view != null); 48 49 return fallback; 50 }
大家可以看一下第六行。实际上这个mView就是一个内部类的对象
1 private final Snackbar.SnackbarLayout mView;
然后接着看第六行的xml文件(到这里其实我们也能猜到了 真正自定义view的snackbar是由snackbar的内部类snackbarlayout来完成的)
1 <?xml version="1.0" encoding="utf-8"?> 2 <!-- 3 ~ Copyright (C) 2015 The Android Open Source Project 4 ~ 5 ~ Licensed under the Apache License, Version 2.0 (the "License"); 6 ~ you may not use this file except in compliance with the License. 7 ~ You may obtain a copy of the License at 8 ~ 9 ~ http://www.apache.org/licenses/LICENSE-2.0 10 ~ 11 ~ Unless required by applicable law or agreed to in writing, software 12 ~ distributed under the License is distributed on an "AS IS" BASIS, 13 ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 ~ See the License for the specific language governing permissions and 15 ~ limitations under the License. 16 --> 17 18 <!--注意看class那边的写法 那个$就是代表内部类的一个符号 这个技巧 以后我们自己自定义控件的时候也可以学习--> 19 <view xmlns:android="http://schemas.android.com/apk/res/android" 20 class="android.support.design.widget.Snackbar$SnackbarLayout" 21 android:layout_width="match_parent" 22 android:layout_height="wrap_content" 23 android:layout_gravity="bottom" 24 style="@style/Widget.Design.Snackbar" /><!-- From: file:/usr/local/google/buildbot/repo_clients/https___googleplex-android.googlesource.com_a_platform_manifest.git/lmp-mr1-supportlib-release/frameworks/support/design/res/layout/layout_snackbar.xml -->
继续看下内部类
找到我们真正的snackbar的布局文件 注意这个地方讨巧的使用了merge标签 这是一个比较好的优化xml的 写法
1 <?xml version="1.0" encoding="utf-8"?> 2 <!-- 3 ~ Copyright (C) 2015 The Android Open Source Project 4 ~ 5 ~ Licensed under the Apache License, Version 2.0 (the "License"); 6 ~ you may not use this file except in compliance with the License. 7 ~ You may obtain a copy of the License at 8 ~ 9 ~ http://www.apache.org/licenses/LICENSE-2.0 10 ~ 11 ~ Unless required by applicable law or agreed to in writing, software 12 ~ distributed under the License is distributed on an "AS IS" BASIS, 13 ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 ~ See the License for the specific language governing permissions and 15 ~ limitations under the License. 16 --> 17 18 <merge xmlns:android="http://schemas.android.com/apk/res/android"> 19 20 <TextView 21 android:id="@+id/snackbar_text" 22 android:layout_width="wrap_content" 23 android:layout_height="wrap_content" 24 android:layout_weight="1" 25 android:paddingTop="@dimen/snackbar_padding_vertical" 26 android:paddingBottom="@dimen/snackbar_padding_vertical" 27 android:paddingLeft="@dimen/snackbar_padding_horizontal" 28 android:paddingRight="@dimen/snackbar_padding_horizontal" 29 android:textAppearance="@style/TextAppearance.Design.Snackbar.Message" 30 android:maxLines="@integer/snackbar_text_max_lines" 31 android:layout_gravity="center_vertical|left|start" 32 android:ellipsize="end"/> 33 34 <TextView 35 android:id="@+id/snackbar_action" 36 android:layout_width="wrap_content" 37 android:layout_height="wrap_content" 38 android:layout_marginLeft="@dimen/snackbar_extra_spacing_horizontal" 39 android:layout_marginStart="@dimen/snackbar_extra_spacing_horizontal" 40 android:layout_gravity="center_vertical|right|end" 41 android:background="?attr/selectableItemBackground" 42 android:paddingTop="@dimen/snackbar_padding_vertical" 43 android:paddingBottom="@dimen/snackbar_padding_vertical" 44 android:paddingLeft="@dimen/snackbar_padding_horizontal" 45 android:paddingRight="@dimen/snackbar_padding_horizontal" 46 android:visibility="gone" 47 android:textAppearance="@style/TextAppearance.Design.Snackbar.Action"/> 48 49 </merge><!-- From: file:/usr/local/google/buildbot/repo_clients/https___googleplex-android.googlesource.com_a_platform_manifest.git/lmp-mr1-supportlib-release/frameworks/support/design/res/layout/layout_snackbar_include.xml -->
到这里 主要的snackbar的一个加载流程就分析完毕了,很多诸如动画的代码部分 我就暂时不去分析他了,大家可以自己仔细分析。
另外有心的同学可能发现了这么一个代码
实际上Behavior 我个人认为是这次support design库里面最重要的一个东西,以后我会单独出来讲一下。基本上support design包里 每一个控件都有她的身影出没。