前言
这几天自己想搞个微博玩玩,仔细研究了一下微博的功能,各种强大啊。一口吃不了一个胖子,所以功能还是要一个一个实现的,就先从下拉刷新入手了,其实自己对实现下拉刷新一点思路也没有,正巧在慕课网上看到了相关视频,所以才会有本篇文章。
正文
好,国际惯例,有图有真相。
以上是效果图。做的太糙大家见谅。
首先自定义一个下拉刷新头
header_layout.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="wrap_content"
android:orientation="vertical" >
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="10dp"
android:paddingTop="10dp" >
<LinearLayout
android:id="@+id/layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:gravity="center"
android:orientation="vertical" >
<TextView
android:id="@+id/tip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="下拉可以刷新"
android:textColor="#000"
android:textStyle="bold" />
</LinearLayout>
<ImageView
android:id="@+id/arrow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="20dp"
android:layout_toLeftOf="@id/layout"
android:src="@drawable/pull_refresh_down" />
<ProgressBar
android:id="@+id/progress"
style="@style/PullToRefreshProgressStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="20dp"
android:layout_toLeftOf="@id/layout"
android:visibility="gone" />
</RelativeLayout>
</LinearLayout>
头部布局很简单,大家可能会发现我的ProgressBar的样式是自定义的。关于自定义ProgressBar在这里简单提一下,因为我也是初学。
在drawable文件夹下新建pull_to_refresh_progress_bg.xml 下面是代码
pull_to_refresh_progress_bg.xml
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/pull_refresh_loading"
android:fromDegrees="0"
android:pivotX="50%"
android:pivotY="50%"
android:toDegrees="360" >
</rotate>
可以看到,这就是一个简单的旋转动画,pull_refresh_loading 是一个圆圈图片,fromDegrees="0"的意思是从0度开始旋转,toDegrees="360"是到360度,这两个属性合起来就是旋转一周,pivotX="50%" pivotY="50%"是以图片的中心旋转。就介绍这么多了,想了解更多大家可以去网上搜Animation的用法,很多的。
这只是一个旋转动画,并不是style,所以还需要在values/styles.xml中增加以下代码。
<style name="PullToRefreshProgressStyle">
<item name="android:indeterminateDrawable">@drawable/pull_to_refresh_progress_bg</item>
<item name="android:minWidth">30dip</item>
<item name="android:maxWidth">30dip</item>
<item name="android:minHeight">30dip</item>
<item name="android:maxHeight">30dip</item>
</style>
这个就没什么说的了。首先是引用之前的动画配置文件,剩下的四条是设置他的宽高的。PullToRefreshProgressStyle就是我们在header_layout的ProgressBar中的使用的样式。
好,下面进入来看看我们的下拉刷新的具体代码实现
PullToRefreshListView.java
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.RotateAnimation;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.wkl.pulltorefreshdemo.R;
/**
* 下拉刷新ListView
*
* @author wkl
*
*/
public class PullToRefreshListView extends ListView implements OnScrollListener {
// 文字提示
private TextView tip;
// 箭头
private ImageView arrow;
// 圆形进度条
private ProgressBar progress;
// 普通状态
private static final int NORMAL = 0;
// 下拉状态
private static final int PULL = 1;
// 提示释放
private static final int RELEASE = 2;
// 正在刷新
private static final int REFRESHING = 3;
// 当前状态
private int state;
private LayoutInflater inflater;
// header布局
private View header;
// header的高度
private int headerHeight;
// 第一个可见的item
private int firstVisibleItem;
// 逆时针旋转动画
private RotateAnimation animNi;
// 顺时针旋转动画
private RotateAnimation animShun;
// 滚动状态
private int scrollState;
// 手指按下标记
private boolean flag = false;
// 手指按下起始点
private int startY;
private OnRefreshListener listener;
// 标记
private int count = 0;
public PullToRefreshListView(Context context) {
super(context);
init(context);
}
public PullToRefreshListView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public PullToRefreshListView(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
init(context);
}
/**
* 初始化
*
* @param context
*/
private void init(Context context) {
animNi = new RotateAnimation(0, -180, RotateAnimation.RELATIVE_TO_SELF,
0.5f, RotateAnimation.RELATIVE_TO_SELF, 0.5f);
animNi.setFillAfter(true);
animNi.setDuration(250);
animShun = new RotateAnimation(-180, 0,
RotateAnimation.RELATIVE_TO_SELF, 0.5f,
RotateAnimation.RELATIVE_TO_SELF, 0.5f);
animShun.setFillAfter(true);
animShun.setDuration(250);
inflater = LayoutInflater.from(context);
header = inflater.inflate(R.layout.header_layout, this, false);
tip = (TextView) header.findViewById(R.id.tip);
arrow = (ImageView) header.findViewById(R.id.arrow);
progress = (ProgressBar) header.findViewById(R.id.progress);
addHeaderView(header);
measureView(header);
headerHeight = header.getMeasuredHeight();
setTopPadding(-headerHeight);
setOnScrollListener(this);
}
/**
* 设置header的TopPadding
*
* @param topPadding
*/
private void setTopPadding(int topPadding) {
header.setPadding(header.getPaddingLeft(), topPadding,
header.getPaddingRight(), header.getPaddingBottom());
}
/**
* 通知父布局占用的空间
*
* @param child
*/
private void measureView(View child) {
ViewGroup.LayoutParams p = child.getLayoutParams();
if (p == null) {
p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
}
int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0, p.width);
int lpHeight = p.height;
int childHeightSpec;
if (lpHeight > 0) {
childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight,
MeasureSpec.EXACTLY);
} else {
childHeightSpec = MeasureSpec.makeMeasureSpec(0,
MeasureSpec.UNSPECIFIED);
}
child.measure(childWidthSpec, childHeightSpec);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
if (firstVisibleItem == 0) {
flag = true;
startY = (int) ev.getY();
}
break;
case MotionEvent.ACTION_MOVE:
onMove(ev);
break;
case MotionEvent.ACTION_UP:
count = 0;
if (state == RELEASE) {
state = REFRESHING;
changeViewByState();
// 预留加载数据
listener.onRefresh();
// 预留加载数据
} else if (state == PULL) {
state = NORMAL;
flag = false;
changeViewByState();
}
break;
}
return super.onTouchEvent(ev);
}
/**
* 根据state改变header
*/
private void changeViewByState() {
switch (state) {
case NORMAL:
arrow.clearAnimation();
setTopPadding(-headerHeight);
break;
case PULL:
arrow.setVisibility(View.VISIBLE);
progress.setVisibility(View.GONE);
if (count != 0) {
arrow.clearAnimation();
arrow.startAnimation(animShun);
tip.setText("下拉可以刷新");
}
break;
case RELEASE:
arrow.setVisibility(View.VISIBLE);
progress.setVisibility(View.GONE);
arrow.clearAnimation();
count = 1;
arrow.startAnimation(animNi);
tip.setText("释放可以刷新");
break;
case REFRESHING:
setTopPadding(10);
arrow.clearAnimation();
arrow.setVisibility(View.GONE);
progress.setVisibility(View.VISIBLE);
tip.setText("正在加载...");
break;
}
}
/**
* 移动事件
*
* @param ev
*/
private void onMove(MotionEvent ev) {
if (!flag) {
return;
}
int tempY = (int) ev.getY();
int space = tempY - startY;
int topPadding = space - headerHeight;
switch (state) {
case NORMAL:
if (space > 0) {
state = PULL;
changeViewByState();
}
break;
case PULL:
setTopPadding(topPadding);
if (space > headerHeight + 20
&& scrollState == SCROLL_STATE_TOUCH_SCROLL) {
state = RELEASE;
changeViewByState();
}
break;
case RELEASE:
setTopPadding(topPadding);
if (space < headerHeight + 20) {
state = PULL;
changeViewByState();
} else if (space <= 0) {
state = NORMAL;
flag = false;
changeViewByState();
}
break;
}
}
@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 void refreshComplete() {
state = NORMAL;
flag = false;
changeViewByState();
}
/**
* 设置数据更新回调
*/
public void setOnRefreshListener(OnRefreshListener listener) {
this.listener = listener;
}
/**
* 刷新数据回调接口
*
* @author wkl
*
*/
public interface OnRefreshListener {
/**
* 通知activity进行更新数据
*/
public void onRefresh();
}
}
上面的代码都有注释,其实不难,主要就是在下拉时的状态的判断以及更新header的显示。这里使用的是接口回调的方式来通知activity进行加载最新数据的。
下面来看如何来使用我们自定义的ListView
activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >
<com.wkl.view.PullToRefreshListView
android:id="@+id/pull_to_refresh_listview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#fff"
android:cacheColorHint="#00000000"
android:dividerHeight="2dp" />
</RelativeLayout>
可以看到我们是直接引用的。需要注意的是,引用的时候必须写上类名的全称,否则会找不到类。
下面是activity的代码。也很简单。
MainActivity.java
import java.util.ArrayList;
import java.util.List;
import com.wkl.adapter.MyAdapter;
import com.wkl.view.PullToRefreshListView;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
public class MainActivity extends Activity implements
PullToRefreshListView.OnRefreshListener {
private PullToRefreshListView listview;
private MyAdapter adapter;
private List<String> data;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
data = new ArrayList<String>();
data.add("aaa");
data.add("bbb");
data.add("ccc");
data.add("ddd");
data.add("eee");
data.add("fff");
data.add("ggg");
data.add("hhh");
data.add("iii");
data.add("jjj");
data.add("kkk");
data.add("lll");
data.add("mmm");
data.add("nnn");
data.add("ooo");
data.add("ppp");
data.add("qqq");
adapter = new MyAdapter(this, data);
listview = (PullToRefreshListView) findViewById(R.id.pull_to_refresh_listview);
listview.setOnRefreshListener(this);
listview.setAdapter(adapter);
}
@Override
public void onRefresh() {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
// 1获取最新数据
data.add(0, "新数据");
// 2更新界面
adapter.notifyDataSetChanged();
// 3通知listview
listview.refreshComplete();
}
}, 2000);
}
}
这里需要注意的是需要用到下拉刷新的activity或者fragment需要实现PullToRefreshListView.OnRefreshListener,否则在刷新的时候会报空指针。onRefresh()方法中的handler是为了模拟加载时间延迟用的,效果更加逼真。好了,下拉刷新就介绍这么多。
最后
由于本人技术能力有限,毕竟我也是初学者,对代码有任何疑问或者改进的意见,请大家在下面回复。