上一期我们有学到listview的使用,还记得listview的用处吗,我们使用listview实现了一个模拟聊天的效果,所以可以看到listview可以实现数据的一个排列,而且还可以是一种布局,那么我们怎么实现的呢? 还记得吗,我们创建了一个list
我们讲过了很多自定义适配器了,相信大家已经是耳熟能详了,这一期我们同样适用到了自定义适配器和listview,不同的是,我们结合了前面有学到的线程,handler的知识,大家可以提高的同时又复习了一边,那么,废话不多来,来看看今天的项目:
效果呢,也是比较简单,看到后面的加载中吗?你们会想到什么呢?之前我们讲过动画吧,诶,对哦,就是结合了动画,是不是类似于大家在看一些商品,或者消息的时候会有类似的效果呢?只不过我这里呢没有通过网络来刷新而已,好了,那么我们一步一步来:
1、首先创建好我们的界面,这个界面很简单,就只有一个标题和listview:
当然,界面简单的话我们就是用一下约束布局,要是界面复杂一点我们就使用线性,这里的背景设置为灰色“#c0c0c0”纯属是为了好看一点,还有下划线的颜色和字体颜色,其实这个按照大家的喜好就可以了;
2、布局文件,我们的布局文件比较多,有页脚的,有一条学生数据的,还有一个就是动画,当然这个动画不属于布局文件,由于都是xml文件,所以我就放在一起讲好了:
学生数据的布局:
这个文件我花了比较多的时间,为什么呢?因为他是在太麻烦了,一行数据太多内容,头像、昵称、身高和年龄,光是Textview就一大堆,然后我还要去调一下间距,字号,虽然麻烦,但大家再调的过程中看到效果还是挺高兴的,因为比较简单,所以效果也比较容易出来,为了避免更多的麻烦我就直接使用线性布局;头像就是Imageview,文字就是Textview,所以这个大家看这调就行,我就不多讲了;
页脚的布局:
既然是一个布局文件,那么我们一样和其他的一样放在layout中,这个布局相信大家如果看过前面几期的动画内容就很好做了,我们先到阿里巴巴矢量图标库中下载一个图标,然后在drawable中创建一个xml文件创建自定义动画:
这是一个旋转动画,从中心开始,然后从0度转到360,抗锯齿什么的,图标路径,这些应该一眼就能看懂;
然后创建一个progressbar,设置这个自定义图片的属性为android:indeterminateDrawable=“自定义动画路径”;然后提示文字,这个大家自己组织;
3、学生类,这个是Java很典型的特点就是面向对象,我们可以将一条数据看成一个的对象,于是我们创建一个带有头像,升高,昵称,年龄的学生对象:
package com.example.mylistviewacitivity;
public class Students {
//1.1 先创建一个学生对象,带有名称,身高,年龄、头像的属性
int img;
String name,high,age;
//1.2 初始化这些属性,构造方法和get,set
public Students(String name, String high, String age,int img) {
this.name = name;
this.high = high;
this.age = age;
this.img = img;
}
public int getImg() {
return img;
}
public void setImg(int img) {
this.img = img;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getHigh() {
return high;
}
public void setHigh(String high) {
this.high = high;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
}
很明显,这个对象就是一个桥梁的作用,存储对应的数据,我们就只是设置好属性,然后创建getset方法和构造方法就ok;当然这里设置的身高年龄为字符串是为了简单,我们好处理;
4、自定义适配器,这个东西我们用得太多了:
package com.example.mylistviewacitivity;
import android.content.Context;
import android.media.Image;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Filterable;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.List;
import java.util.logging.FileHandler;
public class StudentAdapter extends BaseAdapter {
//2.1 写一个自定义适配器,用于将数据显示到已经创建好的布局文件上
Context context;
List list;
public StudentAdapter(Context context, List list) {
this.context = context;
this.list = list;
}
@Override
public int getCount() {
return list.size();
}
@Override
public Object getItem(int i) {
return list.get(i);
}
@Override
public long getItemId(int i) {
return i;
}
@Override
public View getView(int i, View view, ViewGroup viewGroup) {
//2.2 拿到当前数据
Students student = list.get(i);
//2.3 拿到自定义布局文件的组件对象
//先拿到自定义布局文件的view对象
view = LayoutInflater.from(context).inflate(R.layout.listviewlayout,null);
ImageView img_image = view.findViewById(R.id.img_image);
TextView tv_name ,tv_high,tv_age;
tv_name = view.findViewById(R.id.tv_name);
tv_high = view.findViewById(R.id.tv_high);
tv_age = view.findViewById(R.id.tv_age);
//2.4 使用学生对象设置好各个组建的资源
img_image.setImageResource(student.getImg());
tv_name.setText(student.getName());
tv_high.setText(student.getHigh());
tv_age.setText(student.getAge());
return view;
}
}
继承一下Baseadpater然后添加方法重写就ok,当然最重要的还是最后一个获取view的方法,我这里也是为了简单就直接这样设置数据;
一般我们需要一个上下文属性和存储数据用得数列,因为我们创建了对象,所以就直接使用list
5、主逻辑代码,现在我们来啃这块硬骨头,大家一定要理清楚思路,首先我们定义好需要使用的变量:
//3.1 创建好各个变量
ListView lv_students;
int[] images;
List list;
Random random;
StudentAdapter studentAdapter;
//页脚
View view;
//需要一个记录现在是否到达最后一个数据的变量
int visible_last_index = 0;
//4.4 需要定义一个handler来进行数据更新
Handler handler = new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
//如果收到了为1的信息数据,我们更新ul
if(msg.what==1){
studentAdapter.notifyDataSetChanged();
}
}
};
//创建一个序号
int count;
我们界面代码中的listview,头像的id数组,还有存储数据的数列,因为要随机产生出在约定范围内的身高和体重,所以我们定义一个随机数对象,页脚的转换视图,visible_last_index这个索引是我们实现数据刷新的关键,为什么呢?因为我们要知道刷到那里我们就添加数据,当然刚开始是0,看字面意思就是到表示的是最后一个数据的索引;然后就是handler对象,我们要在后面重写handleMessage方法,这个方法会接受子线程发过来的信息,通过判断来执行对应的操作,这个执行了一个方法 studentAdapter.notifyDataSetChanged();其实就是用来更新适配器数据的,大家按照这样写就可以更行适配器了,但一定要注意一定是先添加了数据,在更新;最后一个是需要,就是学生名称后面的数字;
然后是初始化一些变量:
//3.2 编写数据初始化变量
private void init_datas(){
random = new Random();
list = new ArrayList();
lv_students = findViewById(R.id.lv_students);
images = new int[]{R.drawable.image1,R.drawable.image2,R.drawable.image3,R.drawable.image4,R.drawable.image5,
R.drawable.image6,R.drawable.image7,R.drawable.image8,R.drawable.image9,R.drawable.image10,
R.drawable.image11,R.drawable.image12,R.drawable.image13,R.drawable.image14,R.drawable.image15};
studentAdapter = new StudentAdapter(this,list);
}
我们的老习惯,就是写一个初始化函数,然后将我们的数列,随机数,组件对象,适配器,头像id全部都准备一下,要注意顺序,毕竟我们适配器拿到的数据一定要在数列之前;
再就是添加数据的放法:
//4.5 需要创建一个添加数据的方法
private void addStudent(){
count = list.size();
for (int i = 0; i < 15; i++) {
//将对象添加进数列
list.add(new Students("学生"+(count+i+1),String.format("%.2fcm",(150+random.nextDouble()*40)),
String.format("%s",(16+random.nextInt(5))),images[i]));
}
}
可以看到我们在初始的时候并没有添加学生信息进去,为什么要重新创建一个add方法来添加数据呢?其实道理很简单,就是我们数据是会刷新添加的,如果在初始化的时候写了那么在这里又要重复一次,毕竟我们不可能在子线程中添加数据时还初始化,这样的话实现不了效果,所以我们为了简单就直接创建一个添加数据的方法来添加数据,大不了程序开始的时候我们就调用这个添加数据就好了;这里一次性添加了15条,当然,随机数的使用大家看看好了,毕竟这也是Java基础内容,会用会可以了,关于序号的话,我们来看这里巧妙的手法,我们没次调用这个函数的时候会先获取到数列的大小,然后在加上i这个会滚动的数值是不是就会有一个数据升序的效果,当然毕竟索引从0开始,我们的数据一般从1开始,所以这里加了1;
再后就是我们的线程了:
//4.6 需要创建一个线程类来实现数据的添加
class MyThread extends Thread{
@Override
public void run() {
super.run();
try {
sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
addStudent();
handler.sendEmptyMessage(1);
}
}
可以看到我们创建了一个内部类,继承了Thread类,重写了它的run方法,然后在里面休眠了2ms,是为了模仿那种请求网络的方式,然后添加数据,可以看到我还将发送信息放到这里来了,大家知道,一旦执行了这条数据那么上面的handler里面就会进行处理,更新适配器数据,所以在里发送是最适合不过了;
再后就是监听我们的滚动动作了:
public class MainActivity extends AppCompatActivity implements AbsListView.OnScrollListener {
实现这个接口,从写里面的方法:
@Override
public void onScrollStateChanged(AbsListView absListView, int i) {
//4.3 如果滚动到了最后一个数据,我们需要子线程来添加数据
if((studentAdapter.getCount() <= visible_last_index)&&
(i == SCROLL_STATE_IDLE)){
new MyThread().start();
}
}
@Override
public void onScroll(AbsListView absListView, int i, int i1, int i2) {
//4.2 需要根据用户滚动操作来实现索引的改变
visible_last_index = i+ i1-1;
}
这里就使用到了我们的最后数据索引了,我们在onscroll里面对这个索引的值进行更新,是不是就可以在最后索引大于或者等于数组的大小时候,也就是适配器的数据长度的时候就可以开启子线程添加数据了;onscroll的i就是第一条学生数据的索引,第二条参数i1就是当前现实的所有学生数据的条数,最后一个参数i2是所有数据的条数;
而onScrollStateChanged的i有三个取值:
SCROLL_STATE_TOUCH_SCROLL: SCROLL_STATE_FLING: SCROLL_STATE_IDLE:
第一个表示手指还在滑动的时候触发,第二个表示自己惯性滑动的时候触发,最后一个就是停下的时候触发,当然我们在这个方法里面要判断是否是最后一条数据和是否停下来,然后在启动线程;
最最最后的一个就是调用这些方法和添加适配器:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//调用函数
init_datas();
addStudent();
//4.1 需要给lv添加页脚
view = LayoutInflater.from(this).inflate(R.layout.wait_progressbar,null);
lv_students.addFooterView(view);
//不要忘记设置监听器
lv_students.setOnScrollListener(this);
//3.4 设置适配器
lv_students.setAdapter(studentAdapter);
}
但是不要忘了需要对滚动监听添加监听器;ok,完成了这些就可以,下面是所以逻辑代码:
package com.example.mylistviewacitivity;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.AbsListView;
import android.widget.ListView;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class MainActivity extends AppCompatActivity implements AbsListView.OnScrollListener {
//3.1 创建好各个变量
ListView lv_students;
int[] images;
List list;
Random random;
StudentAdapter studentAdapter;
//页脚
View view;
//需要一个记录现在是否到达最后一个数据的变量
int visible_last_index = 0;
//4.4 需要定义一个handler来进行数据更新
Handler handler = new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
//如果收到了为1的信息数据,我们更新ul
if(msg.what==1){
studentAdapter.notifyDataSetChanged();
}
}
};
//创建一个序号
int count;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//调用函数
init_datas();
addStudent();
//4.1 需要给lv添加页脚
view = LayoutInflater.from(this).inflate(R.layout.wait_progressbar,null);
lv_students.addFooterView(view);
//不要忘记设置监听器
lv_students.setOnScrollListener(this);
//3.4 设置适配器
lv_students.setAdapter(studentAdapter);
}
//3.2 编写数据初始化变量
private void init_datas(){
random = new Random();
list = new ArrayList();
lv_students = findViewById(R.id.lv_students);
images = new int[]{R.drawable.image1,R.drawable.image2,R.drawable.image3,R.drawable.image4,R.drawable.image5,
R.drawable.image6,R.drawable.image7,R.drawable.image8,R.drawable.image9,R.drawable.image10,
R.drawable.image11,R.drawable.image12,R.drawable.image13,R.drawable.image14,R.drawable.image15};
studentAdapter = new StudentAdapter(this,list);
}
//4.5 需要创建一个添加数据的方法
private void addStudent(){
count = list.size();
for (int i = 0; i < 15; i++) {
//将对象添加进数列
list.add(new Students("学生"+(count+i+1),String.format("%.2fcm",(150+random.nextDouble()*40)),
String.format("%s",(16+random.nextInt(5))),images[i]));
}
}
//4.6 需要创建一个线程类来实现数据的添加
class MyThread extends Thread{
@Override
public void run() {
super.run();
try {
sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
addStudent();
handler.sendEmptyMessage(1);
}
}
@Override
public void onScrollStateChanged(AbsListView absListView, int i) {
//4.3 如果滚动到了最后一个数据,我们需要子线程来添加数据
if((studentAdapter.getCount() <= visible_last_index)&&
(i == SCROLL_STATE_IDLE)){
new MyThread().start();
}
}
@Override
public void onScroll(AbsListView absListView, int i, int i1, int i2) {
//4.2 需要根据用户滚动操作来实现索引的改变
visible_last_index = i+ i1-1;
}
}
有点多,咱们来理一下思路,先创建变量,然后将一些不需要改变的数据先初始化,写在初始化函数里面,然后在创建添加数据的函数,因为我们在自线程里面添加数据的时候需要调用,然后就是线程类,在里面模拟一下数据的刷新,再然后就是滚动监听的一些方法了,也就是我们在滚动到最后一个数据的时候就开启子线程然后添加数据然后更新到页面上,在重写的方法里面判断,然后在handler里面更新数据,在子线程里面添加数据;
那么,这一期就到这里了,技术含量较多,建议大家多练习!



