android案例---下拉刷新
通过这个文章你可以学习到:
-
listview的使用
-
触摸事件监听
-
回调函数的简单应用
-
线程的使用
-
最后肯定是实现下拉刷新啦
先让我们看看最终效果
GIF.gif那么我们就开始吧
1.因为要用到自定义的listview,我们这里先新建一个view继承listview
(ps:如果要在XML配置该View的话,我们至少要实现前面两个构造方法)
public class MyListView extends ListView {
public MyListView(Context context) {
super(context);
}
public MyListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
}
2.创建一个listview的item布局,然后我们再主布局文件中使用我们自己的View
(ps:listview_item.xml,该布局可以自由扩展,我这里就用最简单的)
<?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"
android:gravity="center_vertical">
<ImageView
android:layout_marginLeft="10dp"
android:id="@+id/listview_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/ic_launcher"/>
<TextView
android:paddingTop="15dp"
android:layout_marginLeft="10dp"
android:id="@+id/listview_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="111"
android:textSize="20dp"/>
</LinearLayout>
(ps:activity_main)
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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="com.example.a455.mypurefresh.MainActivity">
<com.example.a455.mypurefresh.MyListView
android:id="@+id/myview"
android:layout_width="368dp"
android:layout_height="wrap_content"
tools:layout_editor_absoluteY="0dp"
tools:layout_editor_absoluteX="8dp">
</com.example.a455.mypurefresh.MyListView>
</android.support.constraint.ConstraintLayout>
3.我们给listview添加适配器,这里使用的simpleadapter
(ps:我这里就直接贴代码,很简单的,就是调用了三个简单的方法)
public class MainActivity extends AppCompatActivity {
MyListView myListView;
SimpleAdapter simpleAdapter;
List<Map<String,Object>> data;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findView();
initData();
initListView();
}
/**
* @methodName: initListView
* @Description: listview添加设配器
* @param
* @return
* @throws
*/
private void initListView() {
simpleAdapter=new SimpleAdapter(
this, //上下文
data, //数据集
R.layout.listview_item, //item布局文件
new String[]{"text","image"}, //map集合中的键值
new int[]{R.id.listview_text,R.id.listview_image} //item布局文件中的控件id
myListView.setAdapter(simpleAdapter);
}
/**
* @methodName: initData
* @Description: 对数据初始化
* @param
* @return
* @throws
*/
private void initData() {
data=new ArrayList<>();
for (int i = 0; i < 20; i++) {
Map<String,Object> map=new HashMap<String,Object>();
map.put("text",i);
map.put("image",R.mipmap.ic_launcher);
data.add(map);
}
}
private void findView() {
myListView=(MyListView)findViewById(R.id.myview);
}
到这里我们就显示出一个列表了,前面都是配菜,现在开始主菜
4. 先创建我们的下拉头部布局文件
(ps:header.xml,这里主要注意的是progressBar中的visibility:gone
gone意味隐藏并且不占用空间)
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:orientation="vertical"
android:layout_centerInParent="true"
android:id="@+id/layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="下拉可刷新"/>
<TextView
android:layout_marginTop="5dp"
android:id="@+id/time"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<ImageView
android:id="@+id/header_image"
android:layout_marginRight="10dp"
android:src="@drawable/pull_down"
android:layout_toLeftOf="@id/layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<ProgressBar
android:id="@+id/progressbar"
android:layout_marginRight="10dp"
android:layout_toLeftOf="@id/layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="?android:attr/progressBarStyleSmall"
android:visibility="gone"/>
</RelativeLayout>
</LinearLayout>
5. ok,头部文件有了,那么我们要把它加到我们自定义view的顶部
(这里我就写关键代码啦,记得在构造方法中调用该方法)
View header;
int headerHight;
private void init(Context context) {
//解析header布局文件
header = LayoutInflater.from(context).inflate(R.layout.header, null);
//测量header的高宽,具体可以查看MeasureSpec包
int width = View.MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
int hight = View.MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
header.measure(width, hight);
//获取头部的高度,用于后面隐藏我下拉
headerHight=header.getMeasuredHeight();
//将header添加到listview的顶部
this.addHeaderView(header);
}
那我们的布局现在是这样的:
111.PNG
我们用一个方法把它隐藏:
/**
* 根据传入的高度设置header的paddingtop
*/
private void toPadding(int i) {
header.setPadding(
header.getPaddingLeft(),
i,
header.getPaddingRight(),
header.getPaddingBottom()
);
header.invalidate();
}
然后我们只需要在addHeaderView前面调用该方法
//隐藏头部文件
toPadding(-headerHight);
//将header添加到listview的顶部
this.addHeaderView(header);
6. 那么接下来我们就是该监听触摸下拉的事件了,这里是难点
(ps:首先添加滑动监听接口,重写它要实现的方法,定义两个变量来存储信息
不要忘了添加回调接口哟)
public class MyListView extends ListView implements AbsListView.OnScrollListener{
int scrollState; //当前view的滚动状态
int firstVisibleItem; //可见的第一个item
.....//之前的内容省略啦
private void init(Context context) {
.....//之前的内容省略啦
this.setOnScrollListener(this);
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
this.scrollState=scrollState;
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
this.firstVisibleItem=firstVisibleItem;
}
然后我们再添加几个变量用来表示下拉的状态:
public class MyListView extends ListView implements AbsListView.OnScrollListener{
int startY; //触摸屏幕时的高度
boolean isRemak; //是否可以刷新
int state=0; //当前header状态
final int NONE=0; //正常状态
final int PULL=1; //下拉状态
final int RELEASE=2; //提示刷新状态
final int REFRESHING=3; //正在刷新状态
......
重头戏来啦,我们重写onTouche方法:
/*
*触摸事件处理,分三种情况,按下,移动,抬起
* 按下:判断当前是否为listview顶部,如果是记录按下位置
* 移动:调用onMove方法处理移动情况
* 抬起:如果当前状态为提示刷新状态就对header进行更新,并调用刷新内容接口
*/
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
if(firstVisibleItem==0){
isRemak=true;
startY=(int)ev.getY();
}
break;
case MotionEvent.ACTION_MOVE:
onMove(ev); //判断state的状态
break;
case MotionEvent.ACTION_UP:
if(state==RELEASE){
state=REFRESHING;
refreshByState();//刷新header
//iRefreshen.onRefresh(); //刷新listview,这里是通过接口回调的方法来实现的我们先注释掉,后面再来实现
}else if(state==PULL){
state=NONE;
isRemak=false;
refreshByState();
}
break;
}
return super.onTouchEvent(ev);
}
onMove方法是监听当我们再滑动的时候,我们的state是处于什么状态
/*
*触摸移动的时候判断移动的位置
* 若下拉高过或低于设定的数值,则改变提示信息
*/
private void onMove(MotionEvent ev) {
if(!isRemak){
return;
}
int tempY=(int) ev.getY();
int distance=tempY-startY;
int toPadding=distance-headerHight;
switch (state){
case NONE:
if(distance>0){
state=PULL;
refreshByState();
}
break;
case PULL:
toPadding(toPadding);
if(distance>(headerHight+150) && scrollState==SCROLL_STATE_TOUCH_SCROLL){
state=RELEASE;
refreshByState();
}
break;
case RELEASE:
toPadding(toPadding);
if(distance<(headerHight+150)){
state=PULL;
refreshByState();
}
break;
}
}
refreshByState就是我们在滑动的时候根据state的状态来更新header的高度和内容信息
(ps:这里我用的是动画将图片旋转了,小伙伴也可以用两张图片,一张下拉,一张上拉
还有控件的获取要在解析header布局文件后获取)
/*
*通过当前state状态改变header的显示布局
*/
private void refreshByState() {
switch (state){
case NONE:
// imageView.clearAnimation();
toPadding(-headerHight);
imageView.setVisibility(View.VISIBLE);
progressBar.setVisibility(View.GONE);
break;
case PULL:
imageView.clearAnimation();
imageView.setVisibility(View.VISIBLE);
progressBar.setVisibility(View.GONE);
imageView.setAnimation(anim2);
tip.setText("下拉可刷新");
break;
case RELEASE:
imageView.clearAnimation();
imageView.setVisibility(View.VISIBLE);
progressBar.setVisibility(View.GONE);
imageView.setAnimation(anim1);
tip.setText("松开可刷新");
break;
case REFRESHING:
toPadding(50);
imageView.clearAnimation();
imageView.setVisibility(View.GONE);
progressBar.setVisibility(View.VISIBLE);
tip.setText("正在刷新");
break;
}
}
7. 到这里我们就实现了监听下拉刷新了,现在来做刷新后的事情
(ps:上面部分肯比较多,慢慢消化,乱的话可以看看源码)
我们写一个刷新后把headr布局,各种信息还原并且更新时间的方法
/*
*刷新完成后调用此方法重新设置参数,并设置上次刷新时间
*/
public void refreshComplete(){
state=NONE;
isRemak=false;
refreshByState();
Date date=new Date(System.currentTimeMillis());
SimpleDateFormat format=new SimpleDateFormat("yy年mm月dd日 HH:MM:SS");
String time=format.format(date);
this.time.setText(time);
}
8. 最后一部,我们预留一个接口,让外面来完成刷新后的事情
public class MyListView extends ListView implements AbsListView.OnScrollListener{
........
......
.....
public void setInterface(IRefreshen iRefreshen){
this.iRefreshen=iRefreshen;
}
public interface IRefreshen{
public void onRefresh();
}
}
主方法去实现该接口
(PS: //这里使用handler延迟2秒来模拟刷新时候的等待)
public class MainActivity extends AppCompatActivity implements MyListView.IRefreshen{
private void findView() {
......
myListView.setInterface(this);
}
@Override
public void onRefresh() {
Handler handler=new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
Map<String,Object> map=new HashMap<String,Object>();
map.put("text","刷新");
map.put("image",R.mipmap.ic_launcher);
data.add(0,map);
myListView.setAdapter(simpleAdapter);
simpleAdapter.notifyDataSetChanged();
myListView.refreshComplete();
}
},2000);
}
至此,恭喜大家实现下拉刷新了
我来总结下思路:
- 创建自定义Listview布局,在主布局中使用该布局
- 创建header布局并添加进Listview的头部,并用paddingTop来隐藏
- 触摸事件的监听和处理
- 用回调接口,实现在MainAcitivity中刷新内容
其实总体思路很简单,难点就在于触摸的时候对位置的监听和处理,这里比较繁琐,但是慢慢理解就不会觉得很难了
这是我第一次分享,希望大家支持!!!
有发现问题的可以留言,谢谢大家观赏,你的点赞是我继续分享的动力!!!
项目github地址:https://github.com/DongDian455/Android.git