最佳UI体验
其实长久以来,大多数人都认为Android系统的UI并不算美观,至少没有 iOS系统的美观。以至于很多IT公司在进行应用界面设计的时候,为了保证双平台的统一性,强制要求Android端的界面风格必须和 iOS端一致。同一个操作系统中各个应用之间的界面统一性要远比一个应用在双平台的界面统一性重要得多,只有这样,才能给使用者带来更好的用户体验。
但问题在于,Android标准的界面设计风格并不是特别被大众所接受,很多公司都觉得自己完全可以设计出更加好看的界面,从而导致Android平台的界面风格长期难以得到统一。在2014年Google IO大会上重磅推出了一套全新的界面设计语言-—-—Material Design。
本章我们就将对Material Design进行一次深入的学习。
一、什么是 Material Design
Material Design是由谷歌的设计工程师基于优秀的设计原则,结合丰富的创意和科学技术所发明的一套全新的界面设计语言,包含了视觉、运动、互动效果等特性。
二、布局设计
1.布局代码分别如下
代码如下:
<androidx.drawerlayout.widget.DrawerLayout 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:id="@+id/drawerLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<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:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:id="@+id/toolbar"
android:background="?attr/colorPrimary"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
app:layout_scrollFlags="scroll|enterAlways|snap"
/>
<!-- layout_scrollFlags :scroll表示向上滚动时Toolbar滚动并实现隐藏,enenterAlways表示向下滚动时Toolabr向下滚动并重现
snap表示当Toolbar还没有完全隐藏或显示时,根据当前滚动的距离,选择隐藏还是显示 -->
</com.google.android.material.appbar.AppBarLayout>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/srf"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/recyclerview"
/>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<!-- 指定布局行为 -->
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/fab"
android:src="@drawable/pear"
android:layout_gravity="bottom|right"
android:layout_margin="16dp"
app:elevation="8dp"
/>
<!-- elevation给FloatingActionButton指定一个高度值,高度值越大,投影范围越大,投影效果越淡 -->
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.google.android.material.navigation.NavigationView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/nav_view"
android:layout_gravity="start"
app:menu="@menu/circleimagemenu"
app:headerLayout="@layout/headerlayout"/>
</androidx.drawerlayout.widget.DrawerLayout>
activity_main2.xml代码如下:
<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:layout_width="match_parent"
android:layout_height="250dp"
android:id="@+id/appbar">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/collapsing_toolbar"
android:layout_width="match_parent"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:contentScrim="@color/colorPrimary"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<!--exitUntilCollapsed随着滚动完成折叠之后就保留在界面上,不在移出屏幕 -->
<ImageView
android:id="@+id/image2"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
app:layout_collapseMode="parallax"/>
<androidx.appcompat.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:id="@+id/toolbar2"
app:layout_collapseMode="pin"/>
</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: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_marginBottom="15dp"
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp"
android:layout_marginTop="35dp"
app:cardCornerRadius="4dp">
<TextView
android:id="@+id/textview2"
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:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:src="@drawable/pear"
app:layout_anchor="@id/toolbar2"
app:layout_anchorGravity="bottom|end"/>
<!--layout_anchor设置瞄点 -->
</androidx.coordinatorlayout.widget.CoordinatorLayout>
headerlayout.xml代码如下:
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="180dp"> <!-- 180dp是NavigationView比较合适的高度 -->
<de.hdodenhof.circleimageview.CircleImageView
android:layout_width="70dp"
android:layout_height="70dp"
android:id="@+id/header_image"
android:src="@drawable/pear"
android:layout_centerInParent="true"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/header_textView1"
android:text="嘿嘿"
android:textSize="14sp"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:textColor="#191515"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/header_textView2"
android:text="哎呀"
android:textSize="14sp"
android:layout_above="@id/header_textView1"
android:textColor="#150A0A"
/>
</RelativeLayout>
item.xml代码
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_margin="5dp"
app:cardCornerRadius="4dp"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ImageView
android:layout_width="match_parent"
android:layout_height="100dp"
android:id="@+id/item_image"
android:scaleType="centerCrop"/> <!-- 指定图片的缩放模式,他可以让图片保持原有比例填充ImageView,并裁掉超出部分 -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/item_textview"
android:layout_gravity="center_horizontal"
android:layout_margin="5dp"
android:textSize="16sp"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
三、程序设计
1.分别创建3个文件夹如(Fruit)、(MainActivity2)、 (Mydapt)
MainActivity代码
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.core.view.GravityCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Toast;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.navigation.NavigationView;
import com.google.android.material.snackbar.Snackbar;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class MainActivity extends AppCompatActivity {
DrawerLayout drawerLayout;
private SwipeRefreshLayout swipeRefreshLayout;
private Mydapt adapt;
private List<Fruit> list = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
NavigationView navigationView = (NavigationView)findViewById(R.id.nav_view);
Toolbar toolbar = (Toolbar)findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
drawerLayout = (DrawerLayout)findViewById(R.id.drawerLayout);
ActionBar actionBar = getSupportActionBar();
if(actionBar != null){
actionBar.setDisplayHomeAsUpEnabled(true); //让导航按钮显示出来
actionBar.setHomeAsUpIndicator(R.mipmap.ic_launcher); //设置导航按钮图标,默认是返回的图标
}
navigationView.setCheckedItem(R.id.item_cgl1); //设置菜单项的默认选中
//对菜单项设置监听
navigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) {
drawerLayout.closeDrawers(); //将滑动菜单关闭
return true;
}
});
//FloatingActionButton点击事件
FloatingActionButton floatingActionButton = (FloatingActionButton)findViewById(R.id.fab);
floatingActionButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Snackbar.make(v,"Data delete",Snackbar.LENGTH_SHORT)
.setAction("Undo", new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this,"click",Toast.LENGTH_SHORT).show();
}
})
.show();
}
});
//RecyclerView
addlist();
RecyclerView recyclerView = (RecyclerView)findViewById(R.id.recyclerview);
GridLayoutManager manager = new GridLayoutManager(MainActivity.this,2);
adapt = new Mydapt(list);
recyclerView.setLayoutManager(manager);
recyclerView.setAdapter(adapt);
//下拉刷新:
swipeRefreshLayout = (SwipeRefreshLayout)findViewById(R.id.srf);
swipeRefreshLayout.setColorSchemeResources(R.color.colorPrimary); //设置颜色
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
refreshFruits();
}
});
}
private void refreshFruits() {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
runOnUiThread(new Runnable() {
@Override
public void run() {
addlist();
adapt.notifyDataSetChanged();
swipeRefreshLayout.setRefreshing(false); //刷新事件结束,并隐藏刷新进度条
}
});
}
}).start();
}
private void addlist() {
list.clear();
Fruit[] fruits = {new Fruit("grape",R.drawable.grape),new Fruit("orange",R.drawable.orange),
new Fruit("pear",R.drawable.pear),new Fruit("watemelon",R.drawable.watemelon)};
for(int i =0; i < 50 ;i++){
Random random = new Random();
int index = random.nextInt(fruits.length);
list.add(fruits[index]);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.toolbarmenu,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);
}
return true;
}
}
Fruit代码代码如下:
public class Fruit {
private String Name;
private int imageId;
public Fruit(String name, int imageId) {
Name = name;
this.imageId = imageId;
}
public String getName() {
return Name;
}
public int getImageId() {
return imageId;
}
}
MainActivity2代码
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import android.content.Intent;
import android.os.Bundle;
import android.view.MenuItem;
import android.widget.ImageView;
import android.widget.TextView;
import com.bumptech.glide.Glide;
import com.google.android.material.appbar.CollapsingToolbarLayout;
public class MainActivity2 extends AppCompatActivity {
public static final String FRUIT_NAME = "fruit_name";
public static final String FRUIT_IMAGE_ID = "fruit_image_id";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
Intent intent = getIntent();
String fruitName = intent.getStringExtra(FRUIT_NAME);
int imageId = intent.getIntExtra(FRUIT_IMAGE_ID,0);
Toolbar toolbar = (Toolbar)findViewById(R.id.toolbar2);
CollapsingToolbarLayout collapsingToolbarLayout = (CollapsingToolbarLayout)findViewById(R.id.collapsing_toolbar);
ImageView imageView = (ImageView)findViewById(R.id.image2);
TextView textView = (TextView)findViewById(R.id.textview2);
setSupportActionBar(toolbar);
ActionBar actionBar = getSupportActionBar();
if(actionBar != null){
actionBar.setDisplayHomeAsUpEnabled(true);
}
collapsingToolbarLayout.setTitle(fruitName);
Glide.with(this).load(imageId).into(imageView);
String fruitContent = geterateFruitContent(fruitName);
textView.setText(fruitContent);
}
private String geterateFruitContent(String fruitName) {
StringBuilder fruitContent = new StringBuilder();
for(int i = 0; i < 500; i++){
fruitContent.append(fruitName);
}
fruitContent.reverse();
return fruitContent.toString();
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()){
case android.R.id.home:
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
}
Mydapt代码
import android.content.Context;
import android.content.Intent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.cardview.widget.CardView;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import org.w3c.dom.Text;
import java.util.List;
public class Mydapt extends RecyclerView.Adapter<Mydapt.ViewHolder> {
private List<Fruit> list;
private Context mcontext;
public Mydapt(List<Fruit> list) {
this.list = list;
}
class ViewHolder extends RecyclerView.ViewHolder{
CardView cardView;
ImageView imageView;
TextView textView;
public ViewHolder(@NonNull View itemView) {
super(itemView);
cardView = (CardView) itemView;
imageView = itemView.findViewById(R.id.item_image);
textView = itemView.findViewById(R.id.item_textview);
}
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if(mcontext==null)
mcontext = parent.getContext();
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item,parent,false);
final ViewHolder viewHolder = new ViewHolder(view);
viewHolder.cardView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int position = viewHolder.getAdapterPosition();
Fruit fruit = list.get(position);
Intent intent = new Intent(mcontext,MainActivity2.class);
intent.putExtra(MainActivity2.FRUIT_NAME,fruit.getName());
intent.putExtra(MainActivity2.FRUIT_IMAGE_ID,fruit.getImageId());
mcontext.startActivity(intent);
}
});
return viewHolder;
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
Fruit fruit = list.get(position);
holder.textView.setText(fruit.getName());
Glide.with(mcontext).load(fruit.getImageId()).into(holder.imageView); //用Glide加载图片
}
@Override
public int getItemCount() {
return list.size();
}
}
(一)修改标题栏显示文字的内容
1、修改AndroidManifest中< activity>的label属性
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.revise_12_1">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity2"></activity>
<activity
android:name=".MainActivity"
android:label="this ok">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
(二)下拉刷新
在 Meterial Design 中,SwipeRefreshLayout 是用于实现下拉刷新的核心类,它由 support-v4 库提供,把要实现下拉刷新功能的控件放置到 SwipeRefreshLayout 中,就能让这个控件支持下拉刷新。
在内容外嵌套:
<android.support.v4.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">
<!--appbar_scrolling_view_behavior 唯一的作用是把自己(使用者)放到AppBarLayout的下面-->
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</android.support.v4.widget.SwipeRefreshLayout>
上述代码值得注意的是,由于 RecyclerView 变成了 SwipeRefreshLayout 的子控件,因此之前用 app:layout_behavior 声明布局行为要移到 SwipeRefreshLayout 中才行。
修改活动:
// 下拉刷新
swipeRefresh = (SwipeRefreshLayout) findViewById(R.id.swipe_refresh);
swipeRefresh.setColorSchemeResources(R.color.colorPrimary);//设置刷新进度条颜色
swipeRefresh.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
// 处理刷新逻辑
refreshPartner();
}
});
处理刷新逻辑(模拟)
/**
* 下拉刷新数据(为简单起见没和网络交互)
*/
private void refreshPartner() {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
runOnUiThread(new Runnable() {
@Override
public void run() {
initPartner();//重新生成数据
adapter.notifyDataSetChanged();//通知数据变化
swipeRefresh.setRefreshing(false);//隐藏刷新进度条
}
});
}
}).start();
}
四.程序运行
1.虚拟机上运行结果
2.真实机上运行结果
总结
学完了本章的所有知识,你有没有觉得无比兴奋呢?反正我是这么觉得的。本章我们的收获实在是太多了,从一个什么都没有的空项目,经过一章的学习,最后实现了一个功能如此丰富、界面如此华丽的应用,还有什么事情比这个更让我们有成就感吗?
本章中我们充分利用了Design Support库、support-v4库、appcompat-v7库,以及一些开源项目来实现一个了高度Material化的应用程序。