1.ListView
1.1ListView的简单使用
listView在开发中经常用到,首先看一下listView的实现效果
- 新建activity_main.xml和list_item.xml布局文件
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:background="@color/white"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
<?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">
<TextView
android:id="@+id/tv"
android:textSize="20sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
在activity_main中添加ListView控件,list_item就是列表中每一项的布局(只添加了一个Textview,可以通过修改list_item来定制不同的ListView界面) 2. 新建一个JavaBean类,作为适配器的适配类型
public class Bean {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
在Bean类中只有一个属性name,用于设置textView的文本 3. 新建一个MyAdapter继承自BaseAdapter
public class MyAdapter extends BaseAdapter {
private List<Bean> data;
private Context mContext;
public MyAdapter(List<Bean> data, Context mContext) {
this.data = data;
this.mContext = mContext;
}
@Override
public int getCount() {
return data.size();
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
convertView= LayoutInflater.from(mContext).inflate(R.layout.list_item,parent,false);
TextView textView=convertView.findViewById(R.id.tv);
textView.setText(data.get(position).getName());
return convertView;
}
}
在MyAdapter中重写了父类的构造方法,用于将context(上下文)和ListView子项的数据传递进来;然后又重写了getView()方法,这个方法是在listView中每个子项滚动到屏幕中时就会调用,在这个方法中通过LayoutInflater.from()方法加载listView子项的布局,并且设置textView需要显示的文本内容。 4. 最后在MainActivity中修改代码,通过setAdapter()方法将创建好的适配器对象传递进去,建立ListView和数据之间的关联。
public class MainActivity extends AppCompatActivity {
private List<Bean> data=new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
for(int i=1;i<=50;i++){
Bean bean=new Bean();
bean.setName("hello world"+i);
data.add(bean);
}
ListView listView=findViewById(R.id.listView);
listView.setAdapter(new MyAdapter(data,this));
}
}
当然这个只显示一个TextView的ListView比较简单,可以更为简单的实现(直接使用ArrayAdapter) 修改MainActivity中的代码:
public class MainActivity extends AppCompatActivity {
private List<Bean> data=new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
for(int i=1;i<=50;i++){
Bean bean=new Bean();
bean.setName("hello world"+i);
data.add(bean);
}
ArrayAdapter<Bean> adapter=new ArrayAdapter<Bean>(this,android.R.layout.simple_list_item_1,data);
ListView listView=findViewById(R.id.listView);
listView.setAdapter(adapter);
}
}
这里的android.R.layout.simple_list_item_1是Android内置的一个布局文件,里面只有一个Textview,当然也可以使用我们创建的list_item.xml,最后实现的效果是相同的。
1.2 ListView的优化
我们可以分析一下BaseAdapter中的getView()方法,每次都要重新绘制子项view和通过findViewById()方法来获取控件。
- 通过对getView()方法的观察,convertView这个参数我们只是粗略的使用,并没有发挥它的效果,convertView这个参数时用于将之前加载好的布局进行缓存,这样我们就可以通过判断convertView是否为null,如果为null,在调用LayoutInflater来进行绘制View,如果不为null,就可以直接复用。
public class MyAdapter extends BaseAdapter {
private List<Bean> data;
private Context mContext;
public MyAdapter(List<Bean> data, Context mContext) {
this.data = data;
this.mContext = mContext;
}
@Override
public int getCount() {
return data.size();
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
MyViewHolder viewHolder;
if(convertView ==null){
convertView=LayoutInflater.from(mContext).inflate(R.layout.list_item,parent,false);
viewHolder=new MyViewHolder();
viewHolder.textView=convertView.findViewById(R.id.tv);
convertView.setTag(viewHolder);
}else{
viewHolder= (MyViewHolder) convertView.getTag();
}
viewHolder.textView.setText(data.get(position).getName());
return convertView;
}
class MyViewHolder{
TextView textView;
}
}
2.在MyAdapter中我们新建了一个MyViewHolder类(见名思意,就是view的持有者),用于对控件的实例进行缓存,当convertView 为null时,创建一个viewHolder对象,并将控件的实例都存放在该对象中,然后通过view.setTag()方法,将viewHolder对象存储在convertView 中,当convertView 不为null时,通过view的getTag()方法取出viewHolder对象,这样就不用每次都通过findViewById来获取控件的实例了,同时也大大提高的ListView的运行效率。
1.3 ListView的点击事件
ListView的点击事件主要通过setOnItemClickListener方法实现
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
for(int i=1;i<=50;i++){
Bean bean=new Bean();
bean.setName("hello world"+i);
data.add(bean);
}
ListView listView=findViewById(R.id.listView);
listView.setAdapter(new MyAdapter(data,this));
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Toast.makeText(MainActivity.this,data.get(position).getName(),Toast.LENGTH_LONG)
.show();
}
});
}
当我们进行点击的时候通过position判断出我们点击的是哪一项,这里用Toast进行显示。
2.RecyclerView
上面介绍了ListView的简单使用和性能优化,接下来介绍一下一个更为强大的控件RecyclerView,并且Android官方也更加推荐使用RecyclerView。
2.1 RecyclerView的简单使用
- 将上方activity_main中的listView替换为RecyclerView。
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
- 修改MyAdapter继承自RecyclerView.Adapter,并且新建一个内部类ViewHolder继承自RecyclerView.ViewHolder
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
private List<Bean> data;
private Context mContext;
public MyAdapter(List<Bean> data, Context mContext) {
this.data = data;
this.mContext = mContext;
}
@NonNull
@Override
public MyAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view= LayoutInflater.from(mContext).inflate(R.layout.recycler_item,parent,false);
ViewHolder holder=new ViewHolder(view);
return holder;
}
@Override
public void onBindViewHolder(@NonNull MyAdapter.ViewHolder holder, int position) {
holder.textView.setText(data.get(position).getName());
}
@Override
public int getItemCount() {
return data.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
TextView textView;
public ViewHolder(@NonNull View itemView) {
super(itemView);
textView=itemView.findViewById(R.id.recycler_text);
}
}
}
重写的三个方法:
- onCreateViewHolder:这个方法是用于创建ViewHolder实例的,在这个方法中通过LayoutInflater绘制recycler_item(recyclerview的item布局),然后创建viewHolder实例并且将绘制的view加载到viewholder中去。
- onBindViewHolder:这个方法就类似于ListView中的getView()方法,会在每个item滚动到屏幕中时执行,通过position得到当前项的数据,然后在设置到viewHolder保存的控件实例中
- getItemCount:返回recyclerView子项的个数
- 修改MainActivity中的代码
public class MainActivity extends AppCompatActivity {
private List<Bean> data=new ArrayList<>();
private RecyclerView mRecyclerView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
for(int i=1;i<=50;i++){
Bean bean=new Bean();
bean.setName("hello world"+i);
data.add(bean);
}
mRecyclerView=(RecyclerView) findViewById(R.id.recyclerView);
LinearLayoutManager manager=new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(manager);
mRecyclerView.setAdapter(new MyAdapter(data,this));
}
}
LayoutManager用于指定RecyclerView的布局,我们在这里使用的是LinearLayoutManager (线性布局管理器),所以实现效果和ListView相同。
2.2 实现网格布局和瀑布流布局
- 网格布局其实非常简单,只要改变我们所使用的布局管理器就行了,这里使用GridLayoutManager
修改MainActivity中的代码:
mRecyclerView=(RecyclerView) findViewById(R.id.recyclerView);
GridLayoutManager manager=new GridLayoutManager(this,4);
mRecyclerView.setLayoutManager(manager);
mRecyclerView.setAdapter(new MyAdapter(data,this));
2. 瀑布流布局也是如此
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
for(int i=1;i<=50;i++){
Random random=new Random();
int length=random.nextInt(20)+1;
StringBuilder builder=new StringBuilder();
for(int j=0;j<length;j++){
builder.append("安卓,");
}
Bean bean=new Bean();
bean.setName(builder.toString()+"---"+i);
data.add(bean);
}
mRecyclerView=(RecyclerView) findViewById(R.id.recyclerView);
StaggeredGridLayoutManager manager=new StaggeredGridLayoutManager(3,StaggeredGridLayoutManager.VERTICAL);
mRecyclerView.setLayoutManager(manager);
mRecyclerView.setAdapter(new MyAdapter(data,this));
}
}
在这里使用了StaggeredGridLayoutManager ,两个参数分别是:列数;排列方向。 为了更直观的可以看出瀑布流和网格布局的差别(需要每个子项的高度不一样才能看出来),在这里使用了random函数,随机的将参数中传入的字符串重复N遍。
2.3 点击事件
RecyclerView和ListView不同,RecyclerView没有提供Item的点击事件,所以需要我们自己进行设置。
- 在onBindViewHolder方法中通过setOnClickListener实现
@Override
public void onBindViewHolder(@NonNull @org.jetbrains.annotations.NotNull MyAdapter.MyViewHolder holder, int position) {
holder.textView.setText(data.get(position).getName());
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(context,"你点击了第"+position+"项",Toast.LENGTH_SHORT).show();
}
});
holder.textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(context,"你点击了第"+position+"项",Toast.LENGTH_SHORT).show();
}
});
}
通过 holder.itemView设置的点击事件是RecyclerView单个子项整体的点击事件,而通过holder.textView设置的点击事件是Item子项的点击事件;比如list_item中有button和textView时,可以通过这个方法分别设置button和textView的点击事件。 2. 通过接口回调的方式进行实现 (1)在MyAdapter中定义一个接口,专门用于设置点击事件
public interface OnItemListener{
void onItemClick(View view,int position);
}
(2)定义这个接口的set方法并且声明变量,便于调用
private OnItemListener mListener;
public void setOnItemClickListener(OnItemListener listener){
this.mListener=listener;
}
(3)在onBindViewHolder中设置点击事件
@Override
public void onBindViewHolder(@NonNull @org.jetbrains.annotations.NotNull MyAdapter.MyViewHolder holder, int position) {
holder.textView.setText(data.get(position).getName());
holder.imageView.setImageResource(data.get(position).getImageId());
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(mListener!=null){
mListener.onItemClick(holder.itemView, holder.getAdapterPosition());
}
}
});
holder.textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(mListener!=null){
mListener.onItemClick(holder.textView, holder.getAdapterPosition());
}
}
});
}
(4)在MainActivity中设置点击事件的具体效果(通过调用adapter中的setOnItemClickListener)
myAdapter.setOnItemClickListener(new MyAdapter.OnItemListener() {
@Override
public void onItemClick(View view, int position) {
Toast.makeText(MainActivity.this,"点击了第"+position+"项",Toast.LENGTH_SHORT).show();
}
});
这样就可以实现RecyclerView的点击事件了。
|