简介
介绍了 toolbar 滑动菜单栏 卡片式布局 下拉刷新 可折叠toolbar
添加了日志打印管理类
Material Design
优化 Android UI 界面风格,Android5.0 之后内置的应用都使用该风格设计,目的是想解决 Android 平台界面风格不统一的问题。
Toolbar
继承了 ActionBar 的所有功能,并且灵活性很高。
修改 Application 的 theme 属性,将其设置为 NoActionBar
在 AndroidManifest.xml 文件中,找到 application 的 android:theme 设置
<application
//其他属性
android:theme="@style/AppTheme">
这个属性的具体定义在 res/values/styles.xml, 修改这个文件为:
<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>
<style name="FruitActivityTheme" parent="AppTheme"/>
</resources>
用 Toolbar 代替 ActionBar
在界面布局文件中添加 Toolbar 控件
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
app:layout_scrollFlags="scroll|enterAlways|snap">
</androidx.appcompat.widget.Toolbar>
在代码中获取实例
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
其中 Toolbar 的文字内容会从当前 Activity 的 android:label 属性获取,如果没有指定,则会从该 application 的 android:label 属性获取。
为 Toolbar 添加菜单功能
在 res 目录下面新建 menu 目录,并且新建一个 toolbar.xml 文件
<?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/backup"
android:icon="@drawable/back"
android:title="Backup"
app:showAsAction="always"/>
<item
android:id="@+id/delete"
android:icon="@drawable/delete"
android:title="Delete"
app:showAsAction="ifRoom"/>
<item
android:id="@+id/settings"
android:icon="@drawable/settings"
android:title="Settings"
app:showAsAction="never"/>
</menu>
<!--always 图标总是显示 ifRoom 屏幕空间足够的情况下显示 never 永远只显示在菜单中-->
在代码中获取实例并且实现点击监听功能
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.backup:
Toast.makeText(this, "back", Toast.LENGTH_SHORT).show();
break;
case R.id.delete:
Toast.makeText(this, "delete", Toast.LENGTH_SHORT).show();
break;
case R.id.settings:
Toast.makeText(this, "settings", Toast.LENGTH_SHORT).show();
break;
default:break;
}
return true;
}
滑动菜单
借助 DrawerLayout 控件可以简单方便的去实现滑动菜单
修改布局
<?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">
<!--忽略其他的一些控件-->
<!--下面这个是用来显示滑动的菜单, layout_gravity 表示滑动菜单在屏幕的哪一边-->
<!--这个滑动菜单包含 menu 的菜单项和 headerLayout 的头部布局-->
<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:menu="@menu/nav_menu"
app:headerLayout="@layout/nav_header">
</com.google.android.material.navigation.NavigationView>
</androidx.drawerlayout.widget.DrawerLayout>
添加 nav_menu.xml 布局作为滑动菜单的菜单项,在 menu 目录下添加
<?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/nav_call"
android:icon="@drawable/phone"
android:title="Call"/>
<item
android:id="@+id/nav_friends"
android:icon="@drawable/friends"
android:title="Friends"/>
<item
android:id="@+id/nav_location"
android:icon="@drawable/location"
android:title="Location"/>
<item
android:id="@+id/nav_mail"
android:icon="@drawable/mail"
android:title="Mail"/>
<item
android:id="@+id/nav_task"
android:icon="@drawable/task"
android:title="Tasks"/>
</group>
</menu>
添加 nav_header.xml 布局作为滑动菜单的头部项,在 layout 目录下添加
需要注意的是这里用到了 CircleImageView 控件,也就是圆形头像框,这个需要自己添加依赖
在 build.gradle 添加 implementation 'de.hdodenhof:circleimageview:2.1.0'
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="180dp"
android:padding="10dp"
android:background="?attr/colorPrimary">
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/icon_image"
android:layout_width="70dp"
android:layout_height="70dp"
android:src="@drawable/avatar"
android:layout_centerInParent="true"/>
<TextView
android:id="@+id/username"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:text="James@gmail.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="Lebron James"
android:textColor="#fff"
android:textSize="14sp"/>
</RelativeLayout>
在代码中获取实例并监听
//初始化控件及控件监听
mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
ActionBar actionBar = getSupportActionBar();
NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
if(actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setHomeAsUpIndicator(R.drawable.menu_32px);
}
navigationView.setItemIconTintList(null);
navigationView.setCheckedItem(R.id.nav_call);//默认选项
navigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) {
switch (menuItem.getItemId()) {
case R.id.nav_call:
Toast.makeText(MainActivity.this, "Call", Toast.LENGTH_SHORT).show();
break;
default:break;
}
mDrawerLayout.closeDrawers();
return true;
}
});
CircleImageView circleImageView = (CircleImageView) navigationView.getHeaderView(0).findViewById(R.id.icon_image);
circleImageView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "circleImageView", Toast.LENGTH_SHORT).show();
}
});
//设置打开滑动菜单的操作
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()) {
//... 其他菜单控件
case android.R.id.home:
mDrawerLayout.openDrawer(GravityCompat.START);
break;
default:break;
}
return true;
}
悬浮按钮
使用 FloatingActionButton 控件可以轻松地实现悬浮按钮的效果。
添加控件布局
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="16dp"
android:src="@drawable/cloud_done"
app:backgroundTint="#fff"
app:elevation="8dp"
/>
处理点击事件
Snackbar一个提示工具,使用方法可以参考下面
FloatingActionButton fab = (FloatingActionButton)findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Snackbar.make(view, "Data Deleted", Snackbar.LENGTH_SHORT)
.setAction("Undo", new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(MainActivity.this, "Data restored", Toast.LENGTH_SHORT).show();
}
}).show();
}
});
卡片式布局
CardView 是用来实现卡片式布局的重要空间,实质上也是一个 FrameLayout,额外的提供了圆角和阴影效果,看上去更立体;
新建 CardView 布局
<?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="5dp"
app:cardCornerRadius="4dp">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="100dp">
<ImageView
android:id="@+id/fruit_image"
android:layout_width="100dp"
android:layout_height="100dp"
android:scaleType="center"/>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="100dp">
<TextView
android:id="@+id/data"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="24sp"/>
<TextView
android:id="@+id/fruit_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"/>
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
利用 RecyclerView 去装 CardView,所以需要为 RecyclerView 准备一个适配器
public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> {
private Context mContext;
private List<Fruit> mFruitList;
static class ViewHolder extends RecyclerView.ViewHolder{
CardView cardView;
ImageView fruitImage;
TextView fruitName;
TextView date;
public ViewHolder(@NonNull View itemView) {
super(itemView);
cardView = (CardView) itemView;
fruitImage = (ImageView) itemView.findViewById(R.id.fruit_image);
fruitName = (TextView) itemView.findViewById(R.id.fruit_name);
date = (TextView) itemView.findViewById(R.id.data);
}
}
public FruitAdapter(List<Fruit> fruitList) {
mFruitList = fruitList;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if(mContext == null) {
mContext = parent.getContext();
}
View view = LayoutInflater.from(mContext).inflate(R.layout.fruit_item,
parent, false);
final ViewHolder holder = new ViewHolder(view);
holder.cardView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
int position = holder.getAdapterPosition();
Fruit fruit = mFruitList.get(position);
Intent intent = new Intent(mContext, FruitActivity.class);
intent.putExtra(FruitActivity.FRUIT_NAME,fruit.getName());
intent.putExtra(FruitActivity.FRUIT_IMAGE_ID,fruit.getImageId());
mContext.startActivity(intent);
}
});
return holder;
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
Fruit fruit = mFruitList.get(position);
holder.fruitName.setText(fruit.getName());
Glide.with(mContext).load(fruit.getImageId()).into(holder.fruitImage);
/**
* add date
*/
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy年MM月dd日");
// 获取当前时间
Date date = new Date(System.currentTimeMillis());
holder.date.setText(simpleDateFormat.format(date));
}
@Override
public int getItemCount() {
return mFruitList.size();
}
}
初始化 FruitAdapter 去实例 recyclerView
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
GridLayoutManager gridLayoutManager = new GridLayoutManager(this, 1);
recyclerView.setLayoutManager(gridLayoutManager);
adapter = new FruitAdapter(fruitList);
recyclerView.setAdapter(adapter);
下拉刷新
利用 SwipeRefreshLayout 实现下拉刷新;
将 RecyclerView 放到该布局下,既可让 RecyclerView 具有下拉刷新功能。
在代码中添加处理逻辑
swipeRefresh = (SwipeRefreshLayout) findViewById(R.id.swipe_refresh);
swipeRefresh.setColorSchemeResources(R.color.colorPrimary);
swipeRefresh.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
refreshFruits();
}
});
可折叠式标题栏
CollapsingToolbarLayout 是在 Toolbar 基础上让其更加丰富,不仅仅展示标题栏,更能实现非常华丽的效果。
CollapsingToolbarLayout 只能作为 AppBarLayout 的直接子布局使用,而 AppBarLayout 又必须是 CoordinatorLayout 的子布局。
<?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:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
android:fitsSystemWindows="true"
app:contentScrim="?attr/colorPrimary"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<ImageView
android:id="@+id/fruit_image_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="center"
android:fitsSystemWindows="true"
app:layout_collapseMode="parallax"/>
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar1"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin">
</androidx.appcompat.widget.Toolbar>
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="15dp"
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp"
android:layout_marginTop="35dp"
app:cardCornerRadius="4dp">
<TextView
android:id="@+id/fruit_content_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"/>
</androidx.cardview.widget.CardView>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fruit_fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:src="@drawable/content"
app:backgroundTint="#fff"
app:layout_anchor="@id/appBar"
app:layout_anchorGravity="bottom|end"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
控件获取实例初始化
Intent intent = getIntent();
String fruitName = intent.getStringExtra(FRUIT_NAME);
int fruitImageId = intent.getIntExtra(FRUIT_IMAGE_ID, 0);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar1);
CollapsingToolbarLayout collapsingToolbarLayout = (CollapsingToolbarLayout)
findViewById(R.id.collapsing_toolbar);
ImageView fruitImageView = (ImageView) findViewById(R.id.fruit_image_view);
TextView fruitContentText = (TextView) findViewById(R.id.fruit_content_text);
setSupportActionBar(toolbar);
ActionBar actionBar = getSupportActionBar();
if(actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
}
collapsingToolbarLayout.setTitle(fruitName);
Glide.with(this).load(fruitImageId).into(fruitImageView);
String fruitContent = generateFruitContent(fruitName);
fruitContentText.setText(fruitContent);
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fruit_fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(FruitActivity.this, "Comment this", Toast.LENGTH_SHORT).show();
}
});
LogUtil
管理 Log 的工具
public class LogUtil {
public static final String tag = "zlLog";
public static final int VERBOSE = 1;
public static final int DEBUG = 2;
public static final int INFO = 3;
public static final int WARN = 4;
public static final int ERROR = 5;
public static final int NOTHING = 6;
public static int level = VERBOSE;
public static void v(String msg) {
if(level <= VERBOSE) {
Log.v(tag, msg);
}
}
public static void d(String msg) {
if(level <= DEBUG) {
Log.v(tag, msg);
}
}
public static void i(String msg) {
if(level <= INFO) {
Log.v(tag, msg);
}
}
public static void w(String msg) {
if(level <= WARN) {
Log.v(tag, msg);
}
}
public static void e(String msg) {
if(level <= ERROR) {
Log.v(tag, msg);
}
}
}