requestWindowFeature(Window.FEATURE_NO_TITLE);
//去掉标题栏这一句一定要写在setContentView()前面
第二种
自定义style
<style name="AppTheme.NoActionBar" parent="AppTheme">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>
加入上面的两个属性并使用
如果是给Application属性配置,那么整个应用的主题都没有ActionBar。如果是给某个Activity设置,那么只有这个Activity没有ActionBar。
第三种:
getSupportActionBar().hide();
在Activity中调用可以实现隐藏标题栏
Style和Theme都位于values文件夹下的style.xml中,定义的方法一样,都是控制UI的一堆属性。Theme作用于Application或者单个Activity,不能单独作用于某个控件。Style是作用于局部,应用于控件级别。
Style样式优先于Theme样式
listView = findViewById(R.id.fruit_listview);
listView.setEmptyView(findViewById(R.id.test_empty));
在这里需要特别注意R.id.test_empty,通过代码来给出说明
<?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"
tools:context=".MainActivity">
<ListView
android:id="@+id/fruit_listview"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<include
android:id="@+id/test_empty"
layout="@layout/item_empty"/>
</LinearLayout>
从上面的代码中我们可以看到test_empty是和ListView处于同一布局中的一个需要在空数据使需要展示的layout的id,这一点很重要。如果我们单独建立一个新的layout使它不处于当前ListView的同一布局中,你会发现没有任何效果。
RecyclerView中没有空数据的处理方式,需要我们自己来完成。我发现最简单有效的方法是在RecyclerView的外面嵌套一个布局,然后将空数据时需要展示的图片设置为布局的背景,当发现没有数据时让背景显示就可以了。
第二点是HeaderView和FootView两种类型的视图。ListView中提供了相关的API让我们设置,当我们只想在ListView的头部或者底部添加一个View的时候,可以不用影响到Adapter的编写。
HeaderView的简单使用:
首先是头部View的layout布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
android:orientation="horizontal"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/header_image"
android:layout_width="60dp"
android:layout_height="60dp"
android:src="@mipmap/nodata"/>
<TextView
android:id="@+id/header_txt"
android:text="用来测试头部和底部View"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"/>
</LinearLayout>
然后我们生成头部view并且设置给ListView
//绑定ListView控件
listView = findViewById(R.id.fruit_listview);
View view = LayoutInflater.from(MainActivity.this).inflate(R.layout.header_item,null);
TextView test = view.findViewById(R.id.header_txt);
test.setText("没问题了");
String text = "测试";
listView.addHeaderView(view,text,true);
//生成我们前面写的适配器
Adapter adapter = new Adapter(MainActivity.this,R.layout.fruit_list_item,FruitList);
//ListView绑定适配器
listView.setAdapter(adapter);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
//在这里面实现具体操作
String s = (String) adapterView.getItemAtPosition(0);
Toast.makeText(MainActivity.this,s,Toast.LENGTH_SHORT).show();
Toast.makeText(MainActivity.this,i+"",Toast.LENGTH_SHORT).show();
}
});
我注意到addHeaderView()方法中有三个参数,但是第二个参数却不知道有什么作用。查阅了大量的文章之后,其实第二个参数Object data是Adapter中getItem获得的值。从上面的代码可以看出在item受到点击之后我会让Toast显示第一个Item,也就是HeaderView中getItem获取到的值,并且让Toast显示被点击item的当前位置,接下来我们看效果。
这下我们就知道第二个参数有什么作用了。此外,当我们点击列表中第三项的item时显示的位置是3,所以说我们的headView的位置是0,它也算作整个ListView的一项,这并不难理解。需要注意的是addHeaderView()要在ListView设置Adapter之前。
RecyclerView没有设计HeaderView和FootView,只能我们自己去完成。简单粗暴的方式就是添加ViewType,根据类型生成不同的item。从网上看到另外一种思路,通过装饰者模式扩充Adapter的功能,从而实现添加HeaderView和FooterView,并且不影响Adapter的编写工作,后面会单独学习。
接下来我们看局部刷新。在更新了ListView的数据源之后,需要通过Adapter的notifyDataSetChanged来通知视图更新变化,好处是比较简单,坏处是会重绘每个item,但实际上并不是每个Item都需要重绘。我在Adapter中加了一个方法:
public void updateItemView(ListView listView, int position, Fruit newFruit){
int index = position - listView.getFirstVisiblePosition();
if (index >= 0 && index < listView.getChildCount() ){
getItem(position).setFruitName(newFruit.getFruitName());
getItem(position).setImageId(newFruit.getImageId());
}
View view = listView.getChildAt(position);
getView(position,view,listView);
}
然后我们点击第0个Item,然后改变第1个Item的内容来模仿数据更新的过程,代码和效果如下:
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
if (i == 0){
Fruit f = new Fruit("我是牛奶",R.mipmap.cow);
adapter.updateItemView(listView,1,f);
}
}
});
RecyclerView.Adapter为我们提供了notifyItemChanged()用于更新单个ItemView的刷新。代码和使用效果如下:
dataList.get(1).setName("我是齐天大圣孙悟空");
dataList.get(1).setImageId(R.mipmap.monkey);
adapter.notifyItemChanged(1);
最后就关于动画效果,RecyclerView给我们提供了一些Item动画相关的 API,ListView则没有。下面简单看一下RecyclerView的Item动画效果。
RecyclerView.ItemAnimator是一个抽象类,该抽象类有一个子类SimpleItemAnimator,不过也是一个抽象类,我们通常用的是它的孙子DefaultItemAnimator。在DefaultItemAnimator中有Add、Remove、Move、Change四种动画效果。
需要注意的是,要实现这四种动画效果,数据更新时不能通过
Adapter.notifyDataSetChanged();
而是通过:
Adapter.notifyItemInserted(int position)
Adapter.notifyItemRemoved(int position)
Adapter.notifyItemChanged(int position)
Adapter.notifyItemMoved(int fromPosition, int toPosition)
接下来通过几行代码看一下效果:
DefaultItemAnimator defaultItemAnimator = new DefaultItemAnimator();
defaultItemAnimator.setAddDuration(1000);
recycler.setItemAnimator(defaultItemAnimator);
Fruit f = new Fruit("我是齐天大圣孙悟空",R.mipmap.monkey);
dataList.add(defaultP,f);
adapter.notifyItemInserted(defaultP);
]]> <?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"
tools:context=".MainActivity">
<ListView
android:id="@+id/fruit_listview"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
数据模型Fruit:里面只包含两个属性,FruitName代表水果名字,ImageId代表水果图片。
public class Fruit {
private String FruitName;
private int ImageId;
public Fruit(String fruitName, int imageId) {
FruitName = fruitName;
ImageId = imageId;
}
public String getFruitName() {
return FruitName;
}
public int getImageId() {
return ImageId;
}
}
item布局layout
<?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">
<ImageView
android:id="@+id/fruit_item_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/fruit_item_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
Adapter内容,代码里有详细的注释。Adapter继承了系统提供的ArrayAdapter,构造方法要获取item的布局layout。Adapter的核心方法就是getView(),在这个方法中首先将Item的布局layout转换成了View,然后获取了View的控件,最后将数据和控件绑定,再将View返回给ListView。这个方法中可以优化提升效率,我们看到每次ListView要生成一个Item时都得生成一个新的View,但是在这里所有的View样式是一样的,那么当ListView要生成新的Item的View时,能不能利用已经滑出界面的Item的View呢?答案是可以的,而且可以借助ViewHolder将已经获取到的View中的控件保存起来,避免每次都获取控件,具体的实现在后面会写出来。
public class Adapter extends ArrayAdapter<Fruit> {
int ResId;
public Adapter(@NonNull Context context, int resource, @NonNull List objects) {
super(context, resource, objects);
//item的布局layoutId(R.layout.)
ResId = resource;
}
@NonNull
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
//将item的布局layout转化为View
View view = LayoutInflater.from(parent.getContext()).inflate(ResId,parent,false);
//获取item布局中的ImageView和TextView
ImageView FruitImage = view.findViewById(R.id.fruit_item_image);
TextView FruitName = view.findViewById(R.id.fruit_item_name);
//获取对应位置的数据对象
Fruit fruit = (Fruit) getItem(position);
//布局绑定数据
FruitImage.setImageResource(fruit.getImageId());
FruitName.setText(fruit.getFruitName());
return view;
}
}
MainActivity的代码
public class MainActivity extends AppCompatActivity {
private List<Fruit> FruitList = new ArrayList<>();
private ListView listView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//初始化测试数据
initData();
//绑定ListView控件
listView = findViewById(R.id.fruit_listview);
//生成我们前面写的适配器
Adapter adapter = new Adapter(MainActivity.this,R.layout.fruit_list_item,FruitList);
//ListView绑定适配器
listView.setAdapter(adapter);
}
//初始化数据
//这里水果的名称和图片并不是对应的,只是随便找了几张图片来测试
//但是我们可以看到测试数据由三组重复数据构成,一组数据有6个数据模型对象
//只要三组的图片顺序和名称对应上就没问题
private void initData(){
for (int i = 0; i < 3 ; i++){
Fruit a = new Fruit("Apple"+i,R.drawable.a);
FruitList.add(a);
Fruit b = new Fruit("Banana"+i,R.drawable.b);
FruitList.add(b);
Fruit c = new Fruit("Orange"+i,R.drawable.c);
FruitList.add(c);
Fruit d = new Fruit("Demon"+i,R.drawable.d);
FruitList.add(d);
Fruit e = new Fruit("Lemon"+i,R.drawable.e);
FruitList.add(e);
Fruit f = new Fruit("Watermon"+i,R.drawable.f);
FruitList.add(f);
}
}
}
最后效果如下图所示
接下来是对之前提到过的Adapter的优化,具体代码如下。
public class Adapter extends ArrayAdapter<Fruit> {
int ResId;
public Adapter(@NonNull Context context, int resource, @NonNull List objects) {
super(context, resource, objects);
//item的布局layoutId(R.layout.)
ResId = resource;
}
class ViewHolder {
ImageView FruitImage;
TextView FruitName;
}
@NonNull
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
Fruit fruit = getItem(position);
View view;
ViewHolder holder;
//先判断convertView是否为空
if (convertView == null){
//将item的布局layout转化为View
view = LayoutInflater.from(parent.getContext()).inflate(ResId,parent,false);
holder = new ViewHolder();
holder.FruitImage = view.findViewById(R.id.fruit_item_image);
holder.FruitName = view.findViewById(R.id.fruit_item_name);
view.setTag(holder);
}else {
view = convertView;
holder = (ViewHolder) view.getTag();
}
holder.FruitImage.setImageResource(fruit.getImageId());
holder.FruitName.setText(fruit.getFruitName());
return view;
}
}
ListView的Item的点击事件有OnItemClickListener,简单贴一下代码
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
//在这里面实现具体操作
}
});
我们可以看到ListView的使用并不复杂,在不优化的情况下就更加简单了。但是它只能以列表的形式展示内容,而且不适合用于多种类型item的展示,效率较低,在最后会统一对比ListView和RecyclerView的缓存机制。
RecyclerView的Demo展示和ListView同样的内容。接下来开始看代码。
同样的配方,同样的主界面布局(只是ListView换成RecyclerView)、item布局和展示内容。
主界面布局:
<?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"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/fruit_recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
item布局:
<?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="wrap_content"
android:orientation="vertical"
android:layout_margin="5dp">
<ImageView
android:id="@+id/fruit_item_image"
android:layout_width="150dp"
android:layout_height="150dp"
android:layout_gravity="center_horizontal"/>
<TextView
android:layout_gravity="left"
android:id="@+id/fruit_item_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
Fruit数据模型:
public class Fruit {
private String name;
private int imageId;
public Fruit(String name, int imageId) {
this.name = name;
this.imageId = imageId;
}
public String getName() {
return name;
}
public int getImageId() {
return imageId;
}
}
Adapter中的内容不一样了:
public class Adapter extends RecyclerView.Adapter<Adapter.ViewHolder> {
private List<Fruit> fruitList;
public Adapter(List<Fruit> dataList) {
fruitList = dataList;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.fruit_list_item,parent,false);
ViewHolder viewHolder = new ViewHolder(view);
return viewHolder;
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
Fruit fruit = fruitList.get(position);
holder.fruitName.setText(fruit.getName());
holder.fruitImage.setImageResource(fruit.getImageId());
}
@Override
public int getItemCount() {
return fruitList.size();
}
class ViewHolder extends RecyclerView.ViewHolder{
ImageView fruitImage;
TextView fruitName;
public ViewHolder(@NonNull View itemView) {
super(itemView);
fruitImage = itemView.findViewById(R.id.fruit_item_image);
fruitName = itemView.findViewById(R.id.fruit_item_name);
}
}
}
MainActivity代码:
public class MainActivity extends AppCompatActivity {
private List<Fruit> dataList = new ArrayList<>();
RecyclerView recycler;
LinearLayoutManager linearManager;
StaggeredGridLayoutManager staggerManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initData();
recycler = findViewById(R.id.fruit_recycler);
linearManager = new LinearLayoutManager(this);
staggerManager = new StaggeredGridLayoutManager(3,StaggeredGridLayoutManager.VERTICAL);
Adapter adapter = new Adapter(dataList);
recycler.setLayoutManager(linearManager);
recycler.setAdapter(adapter);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main,menu);
return true;
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == R.id.action_add){
recycler.setLayoutManager(staggerManager);
return true;
}
if (item.getItemId() == R.id.action_settings){
recycler.setLayoutManager(linearManager);
return true;
}
return super.onOptionsItemSelected(item);
}
private void initData(){
for (int i = 0; i < 3 ; i++){
//用于瀑布流展示
Fruit a = new Fruit(getRandomLengthName("Apple"),R.drawable.a);
dataList.add(a);
Fruit b = new Fruit(getRandomLengthName("Banana"),R.drawable.b);
dataList.add(b);
Fruit c = new Fruit(getRandomLengthName("Orange"),R.drawable.c);
dataList.add(c);
Fruit d = new Fruit(getRandomLengthName("Demon"),R.drawable.d);
dataList.add(d);
Fruit e = new Fruit(getRandomLengthName("Lemon"),R.drawable.e);
dataList.add(e);
Fruit f = new Fruit(getRandomLengthName("Watermon"),R.drawable.f);
dataList.add(f);
}
}
//将水果名字的长度随机变长,使瀑布流效果更明显一些
private String getRandomLengthName(String name){
Random random = new Random();
int length = random.nextInt(10)+1;
StringBuilder builder = new StringBuilder();
for (int i = 0; i < length; i++){
builder.append(name);
}
return builder.toString();
}
}
这里我们展示两种效果,分别是列表形式和瀑布流形式,效果如下:
从基础使用上看,我们可以明显看出,RecyclerView相比ListView的区别如下:
ssh-keygen -t ed25519 -C "email@example.com(个人邮箱)"
Could not open a connection to your authentication agent.
ssh-agent bash
ssh -T git@服务器域名
将会出现Welcome to GitLab, @个人的用户名!
Save private key
进行存储,最后在~\Users.ssh中我们会得到一个.ppk文件