Material Design
Material Design是什么?
Material Design时谷歌推出的一套UI设计规范,主要通过Design Support库来实现
Palette
Palette可用于提取颜色,让主题能够动态适应当前页面的色调,内置的色调种类有
- Vibrant / Vibrant dark / Vibrant light
- Muted / Muted datk / Muted light
implementation 'com.android.support:palette-v7:25.3.1'
如上引入依赖,根据图片的颜色修改状态栏和标题栏
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/b" />
</LinearLayout>
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ActionBar actionBar = getSupportActionBar();
Window window = getWindow();
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.b);
Palette.generateAsync(bitmap, new Palette.PaletteAsyncListener() {
@Override
public void onGenerated(@Nullable Palette palette) {
Palette.Swatch swatch = palette.getVibrantSwatch();
actionBar.setBackgroundDrawable(new ColorDrawable(swatch.getRgb()));
window.setStatusBarColor(swatch.getRgb());
}
});
}
}
效果如下
阴影
View不仅有长宽,还存在高度Z = elevatoin+translationZ,前者通过xml设置静态高度,后者用于实现动画效果,如下先展示高度的效果
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<TextView
android:id="@+id/tv1"
android:layout_width="100dp"
android:layout_height="100dp"
android:elevation="1dp"
android:background="#ff0000" />
<TextView
android:id="@+id/tv2"
android:layout_marginTop="50dp"
android:layout_width="100dp"
android:layout_height="100dp"
android:background="#00ff00" />
</RelativeLayout>
如上,在RelativeLayout左上角先设置红色,再设置绿色,正常情况下应该显示绿色覆盖红色,但设置了android:elevation=“1dp”,所以红色在上
给tv2设置点击动画,让其高度超过红色,点击后绿色变为上面
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView tv1 = findViewById(R.id.tv1);
TextView tv2 = findViewById(R.id.tv2);
float elevation1 = tv1.getElevation();
float elevation2 = tv2.getElevation();
Log.d(TAG, "onCreate: elevation1 = " + elevation1);
Log.d(TAG, "onCreate: elevation2 = " + elevation2);
tv2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
tv2.animate()
.translationZ(elevation1 + 1)
.setDuration(2000)
.start();
}
});
}
}
设置高度的时候,底部会出现阴影效果,如下设置不同高度
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffffff"
android:gravity="center"
android:orientation="vertical">
<TextView
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_margin="30dp"
android:background="#ffffff" />
<TextView
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_margin="30dp"
android:background="#ffffff"
android:elevation="1dp" />
<TextView
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_margin="30dp"
android:background="#ffffff"
android:elevation="50dp" />
</LinearLayout>
因背景相同,第一个完全不可见,第二个设置高度1,有稍微的阴影,第三个设置高度50旁边有明显阴影
Tinting
通过修改图像的Alpha遮罩来修改图像的颜色,对其重新着色
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="horizontal"
tools:context=".MainActivity">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_launcher" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_launcher"
app:tint="@android:color/holo_blue_bright" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_launcher"
app:tint="@android:color/holo_blue_bright"
app:tintMode="add" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_launcher"
app:tint="@android:color/holo_blue_bright"
app:tintMode="multiply" />
</LinearLayout>
如上设置不同的Tint和TintMode,效果如下
Clipping
Clipping可以改变视图的形状
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<TextView
android:id="@+id/tv_rect"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_margin="20dp"
android:elevation="1dp" />
<TextView
android:id="@+id/tv_circle"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_margin="20dp"
android:elevation="1dp" />
</LinearLayout>
如上,两个正方形裁剪为圆角矩形和圆形
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView rect = findViewById(R.id.tv_rect);
TextView circle = findViewById(R.id.tv_circle);
ViewOutlineProvider viewOutlineProvider1 = new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), 30);
}
};
ViewOutlineProvider viewOutlineProvider2 = new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
outline.setOval(0, 0, view.getWidth(), view.getHeight());
}
};
rect.setOutlineProvider(viewOutlineProvider1);
circle.setOutlineProvider(viewOutlineProvider2);
}
}
效果如下
Toolbar
将ActionBar取消,修改value文件夹下的style.xml,修改parent为NoActionBar:
<resources>
<!-- Base application 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>
</style>
</resources>
在res下新建menu文件夹并创建toolbar.xml,app:showAsAction指定按钮的显示位置,always表示永远显示在Toolbar中,ifRoom表示屏幕空间足够就显示在Toolbar中,never表示永远显示在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/item1"
android:icon="@mipmap/ic_launcher"
android:title="item1"
app:showAsAction="always" />
<item
android:id="@+id/item2"
android:icon="@mipmap/ic_launcher"
android:title="item2"
app:showAsAction="ifRoom" />
<item
android:id="@+id/item3"
android:icon="@mipmap/ic_launcher"
android:title="item2"
app:showAsAction="never" />
</menu>
修改activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/colorPrimary"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
</LinearLayout>
修改MainActivity,通过setSupportActionBar加载导航条和菜单,注意导包是androidx.appcompat.widget.Toolbar:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.toolbar, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.item1:
break;
case R.id.item2:
break;
case R.id.item3:
break;
default:
}
return true;
}
}
效果图:
DrawerLayout
滑动菜单就是将一些菜单选项隐藏起来,通过滑动的方式将菜单显示出来,既省空间又美观。
修改activity_main.xml,设置layout_gravity为start意为从左边滑出:
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/colorPrimary"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
</FrameLayout>
<TextView
android:layout_width="match_parent"
android:text="menu"
android:textSize="30sp"
android:background="#fff"
android:layout_height="match_parent"
android:layout_gravity="start"/>
</androidx.drawerlayout.widget.DrawerLayout>
修改MainActivity,通过actionBar.setDisplayHomeAsUpEnabled(true)让导航条显示,setHomeAsUpIndicator()设置导航图标,通过点击事件唤醒滑动菜单:
public class MainActivity extends AppCompatActivity {
private DrawerLayout drawerLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
drawerLayout = findViewById(R.id.drawer_layout);
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setHomeAsUpIndicator(R.drawable.ic_launcher_background);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.toolbar, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.item1:
break;
case R.id.item2:
break;
case R.id.item3:
break;
case android.R.id.home:
drawerLayout.openDrawer(GravityCompat.START);
default:
}
return true;
}
}
效果图:
NavigationView
添加依赖
implementation 'com.google.android.material:material:1.2.1'
implementation 'de.hdodenhof:circleimageview:2.1.0'
在menu新建nav_menu.xml,checkableBehavior="single"表示组内菜单项只能单选:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<group android:checkableBehavior="single">
<item android:id="@+id/call"
android:icon="@drawable/ic_launcher_foreground"
android:title="call"/>
<item android:id="@+id/friend"
android:icon="@drawable/ic_launcher_foreground"
android:title="friend"/>
<item android:id="@+id/location"
android:icon="@drawable/ic_launcher_foreground"
android:title="location"/>
<item android:id="@+id/task"
android:icon="@drawable/ic_launcher_foreground"
android:title="task"/>
</group>
</menu>
在layout文件夹新建nav_header.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="180dp"
android:background="?attr/colorPrimary"
android:padding="10dp">
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/icon_image"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_centerInParent="true"
android:src="@drawable/ic_launcher_foreground" />
<TextView
android:id="@+id/username"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:text="123456789@qq.com"
android:textColor="#fff"
android:textSize="14sp" />
<TextView
android:id="@+id/mail"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@+id/username"
android:text="tom"
android:textColor="#fff"
android:textSize="14sp" />
</RelativeLayout>
修改activity_main.xml,将textview换成NavigationView:
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/colorPrimary"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
</FrameLayout>
<com.google.android.material.navigation.NavigationView
android:id="@+id/nav_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="start"
app:headerLayout="@layout/nav_header"
app:menu="@menu/nav_menu" />
</androidx.drawerlayout.widget.DrawerLayout>
修改Acitivity,找到NavigationView,setCheckedItem()方法设置默认选中,setNavigationItemSelectedListener()方法设置点击监听器:
public class MainActivity extends AppCompatActivity {
private DrawerLayout drawerLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
drawerLayout = findViewById(R.id.drawer_layout);
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setHomeAsUpIndicator(R.drawable.ic_launcher_background);
}
NavigationView navigationView=findViewById(R.id.nav_view);
navigationView.setCheckedItem(R.id.call);
navigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
drawerLayout.closeDrawers();
return true;
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.toolbar, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.item1:
break;
case R.id.item2:
break;
case R.id.item3:
break;
case android.R.id.home:
drawerLayout.openDrawer(GravityCompat.START);
default:
}
return true;
}
}
效果图:
FloatingActionButton
修改activity_main.xml,添加FloatingActionButton,其中elevation代表悬浮高度,值越大,投影范围越大,投影效果越淡。
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/colorPrimary"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="16dp"
app:elevation="8dp"
android:src="@drawable/ic_launcher_foreground" />
</FrameLayout>
<com.google.android.material.navigation.NavigationView
android:id="@+id/nav_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="start"
app:headerLayout="@layout/nav_header"
app:menu="@menu/nav_menu" />
</androidx.drawerlayout.widget.DrawerLayout>
和普通按钮一样,为FloatingActionButton添加点击事件
FloatingActionButton floatingActionButton = findViewById(R.id.fab);
floatingActionButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
效果图:
Snackbar
Snackbar扩展了Toast,允许在提示中加入一个可交互按钮,用户点击后可以执行额外的一些操作,在上面的onClick()方法中加入Snackbar:
Snackbar.make(v, "data deleted", Snackbar.LENGTH_SHORT)
.setAction("undo", new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "undo successful", Toast.LENGTH_SHORT).show();
}
})
.show();
效果图:
CoordinatorLayout
CoordinatorLayout是加强版的FramLayout,可监听其所有子控件的各种事件,自动帮我们做出最为合理的响应。列如将上图中的FloatingActionButton向上移,以免被弹出的Snackbar挡住。
修改activity_main.xml,将FramLayout修改为CoordinatorLayout:
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/colorPrimary"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="16dp"
app:elevation="8dp"
android:src="@drawable/ic_launcher_foreground" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.google.android.material.navigation.NavigationView
android:id="@+id/nav_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="start"
app:headerLayout="@layout/nav_header"
app:menu="@menu/nav_menu" />
</androidx.drawerlayout.widget.DrawerLayout>
效果图:
CarView
CarView可用于实现卡片式布局,并且拥有圆角和投影效果
修改activity_main.xml,添加Recycle_view:
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/colorPrimary"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycle_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="16dp"
android:src="@drawable/ic_launcher_foreground"
app:elevation="8dp" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.google.android.material.navigation.NavigationView
android:id="@+id/nav_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="start"
app:headerLayout="@layout/nav_header"
app:menu="@menu/nav_menu" />
</androidx.drawerlayout.widget.DrawerLayout>
新建类ItemData:
public class ItemData {
private String name;
private int imageId;
public ItemData(String name, int imageId) {
this.name = name;
this.imageId = imageId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getImageId() {
return imageId;
}
public void setImageId(int imageId) {
this.imageId = imageId;
}
}
新建适配布局item.xml,最外层采用CardView,cardCornerRadius指定图片的圆角弧度:
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="45dp"
app:cardCornerRadius="4dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:id="@+id/item_iv"
android:layout_width="match_parent"
android:layout_height="100dp"
android:scaleType="centerCrop" />
<TextView
android:id="@+id/item_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_margin="5dp"
android:textSize="16sp" />
</LinearLayout>
</androidx.cardview.widget.CardView>
新建ItemDataAdapter作为RecycleView的适配器:
public class ItemDataAdapter extends RecyclerView.Adapter<ItemDataAdapter.ViewHolder> {
private Context context;
private List<ItemData> itemDataList;
public class ViewHolder extends RecyclerView.ViewHolder {
CardView cardView;
ImageView itemIv;
TextView itemTv;
public ViewHolder(@NonNull View itemView) {
super(itemView);
cardView = (CardView) itemView;
itemIv = itemView.findViewById(R.id.item_iv);
itemTv = itemView.findViewById(R.id.item_tv);
}
}
public ItemDataAdapter(List<ItemData> itemDataList) {
this.itemDataList = itemDataList;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if (context == null) {
context = parent.getContext();
}
View view = LayoutInflater.from(context).inflate(R.layout.item, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
ItemData itemData = itemDataList.get(position);
holder.itemIv.setImageResource(itemData.getImageId());
holder.itemTv.setText(itemData.getName());
}
@Override
public int getItemCount() {
return itemDataList.size();
}
}
修改MainActivity,设置布局为GridLayoutManager,添加适配器:
public class MainActivity extends AppCompatActivity {
private DrawerLayout drawerLayout;
private List<ItemData> itemDataList;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
drawerLayout = findViewById(R.id.drawer_layout);
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setHomeAsUpIndicator(R.drawable.ic_launcher_background);
}
NavigationView navigationView = findViewById(R.id.nav_view);
navigationView.setCheckedItem(R.id.call);
navigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
drawerLayout.closeDrawers();
return true;
}
});
FloatingActionButton floatingActionButton = findViewById(R.id.fab);
floatingActionButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Snackbar.make(v, "data deleted", Snackbar.LENGTH_SHORT)
.setAction("undo", new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "undo successful", Toast.LENGTH_SHORT).show();
}
})
.show();
}
});
initData();
RecyclerView recyclerView = findViewById(R.id.recycle_view);
GridLayoutManager layoutManager = new GridLayoutManager(this, 2);
recyclerView.setLayoutManager(layoutManager);
ItemDataAdapter adapter = new ItemDataAdapter(itemDataList);
recyclerView.setAdapter(adapter);
}
private void initData() {
itemDataList = new ArrayList<>();
for (int i = 0; i < 20; i++) {
ItemData itemData = new ItemData("data1", R.mipmap.ic_launcher);
itemDataList.add(itemData);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.toolbar, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.item1:
break;
case R.id.item2:
break;
case R.id.item3:
break;
case android.R.id.home:
drawerLayout.openDrawer(GravityCompat.START);
default:
}
return true;
}
}
效果图:
AppBarLayout
发现RecycleView遮挡了Toolbar,为Toolbar外层设置AppBarLayout,且指定RecycleView布局行为,同时为Toolbar设置layout_scrollFlags,scoll表示向上滑动隐藏,enterAlways表示向下滑动显示,snap表示会根据滑动距离自动选择隐藏还是显示:
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/colorPrimary"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:layout_scrollFlags="scroll|enterAlways|snap"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycle_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="16dp"
android:src="@drawable/ic_launcher_foreground"
app:elevation="8dp" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.google.android.material.navigation.NavigationView
android:id="@+id/nav_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="start"
app:headerLayout="@layout/nav_header"
app:menu="@menu/nav_menu" />
</androidx.drawerlayout.widget.DrawerLayout>
SwipeRefreshLayout
SwipeRefreshLayout可实现下拉刷新和上拉加载,添加依赖:
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
修改activity_main.xml,在RecycleView外面加SwipeRefreshLayout:
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/colorPrimary"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:layout_scrollFlags="scroll|enterAlways|snap"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipe_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycle_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="16dp"
android:src="@drawable/ic_launcher_foreground"
app:elevation="8dp" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.google.android.material.navigation.NavigationView
android:id="@+id/nav_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="start"
app:headerLayout="@layout/nav_header"
app:menu="@menu/nav_menu" />
</androidx.drawerlayout.widget.DrawerLayout>
找到SwipeRefreshLayout ,通过setColorSchemeResources()设置刷新进度条的颜色,设置刷新监听器完成数据的刷新:
public class MainActivity extends AppCompatActivity {
private DrawerLayout drawerLayout;
private List<ItemData> itemDataList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
drawerLayout = findViewById(R.id.drawer_layout);
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setHomeAsUpIndicator(R.drawable.ic_launcher_background);
}
NavigationView navigationView = findViewById(R.id.nav_view);
navigationView.setCheckedItem(R.id.call);
navigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
drawerLayout.closeDrawers();
return true;
}
});
FloatingActionButton floatingActionButton = findViewById(R.id.fab);
floatingActionButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Snackbar.make(v, "data deleted", Snackbar.LENGTH_SHORT)
.setAction("undo", new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "undo successful", Toast.LENGTH_SHORT).show();
}
})
.show();
}
});
initData();
RecyclerView recyclerView = findViewById(R.id.recycle_view);
GridLayoutManager layoutManager = new GridLayoutManager(this, 2);
recyclerView.setLayoutManager(layoutManager);
final ItemDataAdapter adapter = new ItemDataAdapter(itemDataList);
recyclerView.setAdapter(adapter);
final SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swipe_refresh);
swipeRefreshLayout.setColorSchemeResources(R.color.colorPrimary);
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (Exception e) {
e.printStackTrace();
}
runOnUiThread(new Runnable() {
@Override
public void run() {
initData();
adapter.notifyDataSetChanged();
swipeRefreshLayout.setRefreshing(false);
}
});
}
}).start();
}
});
}
private void initData() {
for (int i = 0; i < 20; i++) {
ItemData itemData = new ItemData("data" + i, R.mipmap.ic_launcher);
itemDataList.add(itemData);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.toolbar, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.item1:
break;
case R.id.item2:
break;
case R.id.item3:
break;
case android.R.id.home:
drawerLayout.openDrawer(GravityCompat.START);
default:
}
return true;
}
}
效果图:
CollapsingToolbarLayout
作为AppBarLayout的子布局使用,实现可折叠式标题栏,新建DataDetailActivity,修改activity_data_detail,其中:
- CoordinatorLayout最外层布局
- AppBarLayout必须是CoordinatorLayout的子布局
- CollapsingToolbarLayout必须是AppBarLayout的子布局,contentScrim指定折叠时的背景色:
- layout_scrollFlags的scoll表示布局会跟随内容详情一起滚动
- exitUntilCollapsed表示滚动完折叠之后就保留在界面上
- 内部定义ImageView和Toolbar控件,layout_collapseMode指定折叠过程中的模式:
- pin表示在折叠过程中位置始终不变
- parallax表示折叠过程产生一定的错位偏移
- NestedScrollView用于展示内容详情,用于滚动显示,内部通过LinearLayout垂直显示CardView中的TextView
- FloatingActionButton的属性layout_anchor将AppBarLayout作为描点,将会出现在标题栏区域内
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBar"
android:layout_width="match_parent"
android:layout_height="250dp">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/collapsing_toolbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:contentScrim="?attr/colorPrimary"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<ImageView
android:id="@+id/data_detail_iv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
app:layout_collapseMode="parallax" />
<androidx.appcompat.widget.Toolbar
android:id="@+id/data_detail_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin" />
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:src="@drawable/ic_launcher_foreground"
app:layout_anchor="@id/appBar"
app:layout_anchorGravity="bottom|end" />
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="15dp"
app:cardCornerRadius="4dp">
<TextView
android:id="@+id/data_detail_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp" />
</androidx.cardview.widget.CardView>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
修改DataDetailActivity,将Toobar设置为标题栏,唤醒HomeAsUp并在点击事件返回,通过collapsingToolbarLayout的setTitle和setImageResource设置标题栏和图片,随后设置内容详情:
public class DataDetailActivity extends AppCompatActivity {
public static final String DATA_NAME = "data_name";
public static final String DATA_ID = "data_id";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_data_detail);
Intent intent = getIntent();
String dataName = intent.getStringExtra(DATA_NAME);
int dataId = intent.getIntExtra(DATA_ID, 0);
Toolbar toolbar = findViewById(R.id.data_detail_toolbar);
CollapsingToolbarLayout collapsingToolbarLayout = findViewById(R.id.collapsing_toolbar);
ImageView dataIv = findViewById(R.id.data_detail_iv);
TextView dataTv = findViewById(R.id.data_detail_tv);
setSupportActionBar(toolbar);
ActionBar actionBar=getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
}
collapsingToolbarLayout.setTitle(dataName);
dataIv.setImageResource(dataId);
dataTv.setText(dataName);
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()){
case android.R.id.home:
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
}
修改ItemDataAdapter,为RecycleView设置点击事件,传递被点击的对象:
public class ItemDataAdapter extends RecyclerView.Adapter<ItemDataAdapter.ViewHolder> {
private Context context;
private List<ItemData> itemDataList;
public class ViewHolder extends RecyclerView.ViewHolder {
CardView cardView;
ImageView itemIv;
TextView itemTv;
public ViewHolder(@NonNull View itemView) {
super(itemView);
cardView = (CardView) itemView;
itemIv = itemView.findViewById(R.id.item_iv);
itemTv = itemView.findViewById(R.id.item_tv);
}
}
public ItemDataAdapter(List<ItemData> itemDataList) {
this.itemDataList = itemDataList;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if (context == null) {
context = parent.getContext();
}
View view = LayoutInflater.from(context).inflate(R.layout.item, parent, false);
final ViewHolder holder = new ViewHolder(view);
holder.cardView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int position = holder.getAdapterPosition();
ItemData itemData = itemDataList.get(position);
Intent intent = new Intent(context, DataDetailActivity.class);
intent.putExtra(DataDetailActivity.DATA_NAME, itemData.getName());
intent.putExtra(DataDetailActivity.DATA_ID, itemData.getImageId());
context.startActivity(intent);
}
});
return holder;
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
ItemData itemData = itemDataList.get(position);
holder.itemIv.setImageResource(itemData.getImageId());
holder.itemTv.setText(itemData.getName());
}
@Override
public int getItemCount() {
return itemDataList.size();
}
}
效果图:
向上拖动,可以将图片折叠成标题栏:
将状态栏和背景图融合
为ImageView及其父布局都设置android:fitsSystemWindows=“true”:
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBar"
android:layout_width="match_parent"
android:layout_height="250dp"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/collapsing_toolbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:contentScrim="?attr/colorPrimary"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<ImageView
android:id="@+id/data_detail_iv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:scaleType="centerCrop"
app:layout_collapseMode="parallax" />
<androidx.appcompat.widget.Toolbar
android:id="@+id/data_detail_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin" />
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:src="@drawable/ic_launcher_foreground"
app:layout_anchor="@id/appBar"
app:layout_anchorGravity="bottom|end" />
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="15dp"
app:cardCornerRadius="4dp">
<TextView
android:id="@+id/data_detail_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp" />
</androidx.cardview.widget.CardView>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
将状态栏颜色指定透明色:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
getWindow().setStatusBarColor(Color.TRANSPARENT);
}
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
效果图: