ListView

ListView

几乎每个APP都会用到滚动控件,淘宝的商品页,知乎的信息页等等,这是最重要而最难用的一种控件,所以从常用控件中单独提出来写用法
滚动控件主要有两种,一种是安卓原带的ListView,另外一种是新增的RecyclerView,当然,在8012年的今天,我们更应该多关注RecyclerView。

初始化

<ListView
    android:id="@+id/list_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    />

ListView的数据传入需要适配器完成,比较好用的适配器之一是ArrayAdapter

String[] data = {"some data"};
ArrayAdapter<String> adapter = new ArrayAdapter<String>(MainActivity.this, android.R.layout.simple_list_item_1, data);

以字符串数据为例,这里由于数据为字符串,故将ArrayAdapter的泛型指定为String,ArrayAdapter有多个构造函数的重载,这里使用的第一个参数为上下文,第二个参数为布局的id,simple_list_item_1是一个安卓内置的布局文件,里面只有一个TextView,用于显示一段文本,第三个是数据

最后将适配器传入

ListView listView = findViewById(R.id.list_view);
listView.setAdapter(adapter);

定制界面

和《第一行代码》一样,以一个显示水果的界面来作为例子

首先我们要有一个实体类来描述水果的信息

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;
    }
}

然后需要一个布局文件去确定信息怎么展示,创建fruit_item.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <ImageView
        android:id="@+id/fruit_image"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/fruit_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:layout_maginStart="10dp" />
</LinearLayout>

接下来创建一个自定义适配器去管理要传入的信息

public class FruitAdapter extends ArrayAdapter<Fruit> {

    private int resourceId;

    public FruitAdapter (Context context, int FruitViewResourceId, List<Fruit> objects) {
        super(context, FruitViewResourceId,objects);
        resourceId = FruitViewResourceId;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        Fruit fruit = getItem(position);
        View view = LayoutInflater.from(getContext()).inflate(resourceId,parent,false);

        ImageView fruitImage = view.findViewById(R.id.fruit_image);
        TextView fruitName = view.findViewById(R.id.fruit_name);

        fruitImage.setImageResource(fruit.getImageId());
        fruitName.setText(fruit.getName());

        return view;
    }
}

这段代码理解起来比较困难,首先从FruitAdapter看起,作为继承ArrayAdapter的构造函数,在这里将FruitViewResourceId记录下来,这是每一个水果的布局文件,也就是fruit_item.xml,以便后面动态加载布局文件

getView()方法在每一个Fruit Item被划到屏幕内时会被触发,这个时候,首先通过通过getItem(position)得到被划进来的是哪个Fruit实体,然后利用LayoutInflater.from().inflate()方法动态加载对应的布局,在ListView中应使用getContext()得到Context,inflate的第一个参数是我们自定义的布局文件,而第三个参数为false,表示不为这个生成的View添加父布局,因为这个View是要放到ListView中的。

最后为这个View添加好信息,需要注意,这里应该调用view的findViewById()方法,因为每一个Fruit实体的图像和名称是不同的

总结一下就是,先取得水果布局文件ID,然后得到水果信息,根据水果信息构造出水果展示的布局,再把它加入到ListView中

最后在onCreate中使用FruitAdapter

Fruit apple = new Fruit("apple", R.drawable.apple_pic);
fruitList.add(apple);
//etc..
FruitAdapter adapter = new FruitAdapter(MainActivity.this, R.layout.fruit_item, fruitList);
listView.setAdapter(adapter);

优化

在动态加载布局时,假如用户是重复划动的,可以重用之前加载好的View

View view;
if(convertView != null){
    view = convertView;
} else {
    view = LayoutInflater.from(getContext()).inflate(resourceId,parent,false);
}

利用ViewHolder缓存控件实例

class ViewHolder {
    ImageView fruitImage;
    TextView fruitName;
}

View view;
ViewHolder viewHolder;
if(convertView != null){
    view = convertView;
    viewHolder = (ViewHolder)view.getTag();
} else {
    view = LayoutInflater.from(getContext()).inflate(resourceId,parent,false);
    viewHolder = new ViewHolder();
    viewHolder.fruitImage.view.findViewById(R.id.fruit_image);
    viewHolder.fruitName.view.findViewById(R.id.fruit_name);
    view.setTag(viewHolder);
}
viewHolder.fruitImage.setImageResource(fruit.getImageId());
viewHolder.fruitName.setText(fruit.getName());

注意,setImageResource和setText仍然需要执行,因为fruit_item.xml中并没有这些信息

点击事件

ListView的点击监听器类似于Button的

listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        Fruit fruit = fruitList.get(position);
        //TODO
    }
});

RecyclerView

RecyclerView相当于一个增强版的ListView,属于新增控件,要使用RecyclerView首先打开app/build.gradle文件,在denpendencies闭包中添加compile 'com.android.support:recyclerview-v7:xx',这里的xx填入你自己的版本号,然后就可以使用了

<android.support.v7.widget.RecyclerView
    android:id="@+id/recyclerView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    />

适配器

通常RecyclerView使用的适配器为自带的RecyclerView.Adapter。以《第一行代码》中的示例为例

public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> {

    private List<Fruit> mFruitList;

    //内部类ViewHolder
    static class ViewHolder extends RecyclerView.ViewHolder{
        View fruitView;
        ImageView fruitImage;
        TextView fruitName;

        public ViewHolder(View view){
            super(view);
            fruitView = view;
            fruitImage = view.findViewById(R.id.fruitImage);
            fruitName = view.findViewById(R.id.fruitName);
        }
    }

    public FruitAdapter(List<Fruit> fruitList){
        mFruitList = fruitList;
    }

    //以下是三个RecyclerView.Adapter的方法的重写
    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate
                (R.layout.fruit_item,parent,false);

        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position){
        Fruit fruit = mFruitList.get(position);
        holder.fruitName.setText(fruit.getName());
        holder.fruitImage.setImageResource(fruit.getImageID());
    }

    @Override
    public int getItemCount(){
        return mFruitList.size();
    }
}

ViewHolder的作用在上面就已经提过了。onCreateViewHolder()onBindViewHolder()getItemCount()三个方法必须被重写。
onCreateViewHolder()中,构造了一个view并返回一个viewHolder,注意,这里的LayoutInflater有第三个参数false,原理同ListView。
onBindViewHolder()会在每个子项被滑到屏幕内时被执行,可以通过position参数得到当前项的实例,并设置实例的Name和Image getItemCount()用于获取Recycler有多少项

RecyclerView的使用和ListView类似

RecyclerView recyclerView = findViewById(R.id.recyclerView);

LinearLayoutManager layoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(layoutManager);

FruitAdapter fruitAdapter = new FruitAdapter(fruitList);
recyclerView.setAdapter(fruitAdapter);

LinearLayoutManager用于指定RecyclerView的布局方式,他的参数就是this,因为RecyclerView是在当前页面呈现的。将LayoutManager传入recyclerView,再将携带了信息的Adapter也传入,就实现了类似ListView的视觉效果

LayoutManager

之所以说RecyclerView强于ListView,其中一点就是LayoutManager的多样性 LinearLayoutManager为线性布局,默认为纵向滚动,还可以实现横向滚动,只需要在设置了LayoutManager之前

LinearLayoutManager layoutManager = new LinearLayoutManager(this);
layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL)
recyclerView.setLayoutManager(layoutManager);

StaggeredGridLayoutManager可以实现瀑布流布局,瀑布流布局子项的宽度是根据布局的列数来确定的,所以宽度需要设置为match_parent,在代码中

RecyclerView recyclerView = findViewById(R.id.recyclerView);

StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(3,StaggeredGridLayoutManager.VERTICAL);
recyclerView.setLayoutManager(layoutManager);

FruitAdapter fruitAdapter = new FruitAdapter(fruitList);
recyclerView.setAdapter(fruitAdapter);

StaggeredGridLayoutManager的构造函数接收两个参数,第一个参数是布局的列数,第二个参数是布局的方向

GridLayoutManager是网格布局,使用则同上。但是网格布局没有瀑布流布局好用

点击事件

与ListView很不一样的一点是,RecyclerView不能用setOnItemClickListener()注册子项的监听器,而只能在子项的View中去注册
在RecyclerView的onCreateViewHolder()中注册点击事件

@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    View view = LayoutInflater.from(parent.getContext()).inflate
                (R.layout.fruit_item,parent,false);

    final ViewHolder holder = new ViewHolder(view);

    //给整个View设置监听器
    holder.fruitView.setOnClickListener(new View.OnClickListener(){
        @Override
        public void onClick(View v) {
            int position = holder.getAdapterPosition();
            Fruit fruit = mFruitList.get(position);
            //TODO
        }
    });

    //给Image组件设置监听器
    holder.fruitImage.setOnClickListener(new View.OnClickListener(){
        @Override
        public void onClick(View v) {
            //TODO
        }
    });

    return new holder;
}

在监听器中,可以通过ViewHolder的getAdapterPosition()得到被点击的子项的位置,调用List的get(position)方法可以得到被点击的实例

posted @ 2020-04-06 21:52  HermitG  阅读(195)  评论(0编辑  收藏  举报