在上周的时候,我在针对我想要的问题研究了一下PullToRefreshListView的使用之后,我说过要自己去实现一个下拉刷新,其实PullToRefreshListView一般来说够用了,只是当在我们的项目中出现比较私人的定制时,就显得比较复杂了,所以这周我花了点时间自己写了一个下拉刷新,由于我不怎么写界面,所以写的速度自然就没有那么快,不过至少完成了,下面就分享一下我的下拉刷新的代码吧,代码中的注释还是比较充足的
首先是布局部分layout_refresh_headview.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:orientation="horizontal" >
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center"
>
<!-- 左边的加载图标 -->
<RelativeLayout
android:layout_width="40dp"
android:layout_height="40dp"
android:paddingRight="10dp"
>
<ImageView
android:id="@+id/layout_refresh_headview_image_load"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:src="@drawable/ic_launcher"
android:visibility="visible"
/>
<ProgressBar
android:id="@+id/layout_refresh_headview_progress_load"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:visibility="gone"
/>
</RelativeLayout>
<!-- 右边的加载文字 -->
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
>
<TextView
android:id="@+id/layout_refresh_headview_text_load"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="load_text"
/>
<TextView
android:id="@+id/layout_refresh_headview_text_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="last_load_time"
/>
</LinearLayout>
</LinearLayout>
</LinearLayout>
然后是头部分包括集合的,基本的思路是让调用者可以当做一个带有下拉刷新的控件使用,调用起来独立一点
layout_my_pulltorefresh_listview.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:orientation="vertical" >
<include
android:id="@+id/pull_to_refresh_head"
layout="@layout/layout_refresh_headview"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
<ListView
android:id="@+id/layout_my_pulltorefresh_listview_listview"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:divider="@drawable/ic_launcher"
android:dividerHeight="1dp"
></ListView>
</LinearLayout>
布局文件写好之后就是关键的代码了,下面是我写的自定义类
package com.example.mypulltorefresh;
import java.util.ArrayList;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewConfiguration;
import android.view.animation.RotateAnimation;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
public class YPullToRefreshListView extends LinearLayout implements OnTouchListener{
//下拉刷新的头部视图
private View mHeadView;
//头部视图的参数
private LayoutParams mHeadViewParams;
//加载时的进度条
private ProgressBar mLoadingProgress;
//加载时的图片
private ImageView mLoadingImage;
//加载时的提示文字
private TextView mLoadingText;
//上一次加载的时间
private TextView mLastLoadingTime;
//上一次刷新的时间值
private String mLastLoadTimeValue = "";
//判定是手指有移动的最小值
private int mMoveJudgeValue;
//头部View的高度
private int mHeadViewHight;
//显示的ListView
private ListView mListView;
//支持显示的ListView的数据
private ArrayList<String> mListViewData = new ArrayList<String>();
//支持显示的adapter
private ArrayAdapter<String> mListViewAdapter;
//手指落下的y坐标
private float mDownY;
//此时放开即可刷新
private static final String PULL_TO_REFRESH = "pull_to_refresh";
//此时正在刷新
private static final String REFRESHING = "refreshing";
//此时放开取消刷新
private static final String PULL_TO_CALCEL_REFRESH = "pull_to_calcel_refresh";
//刷新结束
private static final String REFRESH_FINISHED = "refresh_finished";
private String mCurrentRefreshStatus = REFRESH_FINISHED;//当前的刷新状态
private String mLastRefreshStatus;
//刷新的回调
private PullToRefreshListener mPullToRefreshListener;
public YPullToRefreshListView(Context context, AttributeSet attrs) {
super(context, attrs);
initMyView(context);
initLoadingHead(context);
initListView(context);
}
private void initMyView(Context context){
LayoutInflater.from(context).inflate(R.layout.layout_my_pulltorefresh_listview, this, true);
}
private void initLoadingHead(Context context){
mHeadView = (View)findViewById(R.id.pull_to_refresh_head);
// mHeadView = (View)LayoutInflater.from(context).inflate(R.layout.layout_refresh_headview,null);
mLoadingProgress = (ProgressBar)mHeadView.findViewById(R.id.layout_refresh_headview_progress_load);
mLoadingImage = (ImageView)mHeadView.findViewById(R.id.layout_refresh_headview_image_load);
mLoadingText = (TextView)mHeadView.findViewById(R.id.layout_refresh_headview_text_load);
mLastLoadingTime = (TextView)mHeadView.findViewById(R.id.layout_refresh_headview_text_time);
//获取最小判定手指移动距离
mMoveJudgeValue = ViewConfiguration.get(context).getScaledTouchSlop();
}
private void initListView(Context context){
//这种方式不保险,应该用R.id来完成,只是为了适应所有项目需要自定义个R工具类,在这里就不扩展了
mListView = (ListView) findViewById(R.id.layout_my_pulltorefresh_listview_listview);
mListView.setOnTouchListener(this);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if(changed){
mHeadViewParams = (LayoutParams)mHeadView.getLayoutParams();
mHeadViewHight = mHeadView.getHeight();
mHeadViewParams.topMargin = -mHeadViewHight;//这里将高度设为负值以便于隐藏
}
}
/**
* 可以由外部指定ListView;
* @param listview
*/
public void setShowListView(ListView listview){
mListView = listview;
}
/**
* 外部设置显示的数据,这里的写法是让数据由外部管理
* @param list
*/
public void setListViewData(ArrayList<String> list){
mListViewData = list;
mListViewAdapter = new ArrayAdapter<String>(getContext(), android.R.layout.simple_list_item_1, mListViewData);
mListView.setAdapter(mListViewAdapter);
mListViewAdapter.notifyDataSetChanged();
}
/**
* 设置刷新的监听器
* @param listener
*/
public void setPullToRefreshListener(PullToRefreshListener pullToRefreshListener){
mPullToRefreshListener = pullToRefreshListener;
}
@Override
public boolean onTouch(View v, MotionEvent event) {
//在开始判断当前的手指移动对是否可以放开刷新之前,先要判断当前的状态,如果正在刷新,则不改变状态
if(!isCurrentCanChangeRefreshStatis())
return false;//封装判断当前能否改变刷新状态,方便以后扩展
switch (event.getAction()) {
//down事件负责记录手指点击坐标
case MotionEvent.ACTION_DOWN:
mDownY = event.getRawY();//存储点击下来时Y的坐标
break;
//move事件负责记录状态
case MotionEvent.ACTION_MOVE:
//模拟:-5(现在的坐标)-(-3(手指放下坐标))下移 = -2 ;-3(现在的坐标)-(-5(手指放下的坐标))上移 = 2
int distance = (int)(event.getRawY() - mDownY);
if(distance < 0){//上移
//如果有上拉加载更多则可以在这里添加
}else{//下移
int absolutedistance = 0;
if(distance<0){//先将绝对值算出来
absolutedistance = -distance;
}else{
absolutedistance = distance;
}
if(absolutedistance < mMoveJudgeValue){//小于最小移动判定
return false;
}
//这里应该要有2种状态
//1、手指还没移动到可以刷新的状态,此时mHeadViewParams.topMargin < 0
// if(absolutedistance < mHeadViewHight){
if(mHeadViewParams.topMargin < 0){
mLastRefreshStatus = mCurrentRefreshStatus;
mCurrentRefreshStatus = PULL_TO_CALCEL_REFRESH;
//更新headview的显示
if(!mLastRefreshStatus.equals(mCurrentRefreshStatus))
headViewonPullToRefresh();
//2、手指移动到了可以刷新的状态,此时mHeadViewParams.topMargin >= 0
}else{
mLastRefreshStatus = mCurrentRefreshStatus;
mCurrentRefreshStatus = PULL_TO_REFRESH;
//更新headview的显示
if(!mLastRefreshStatus.equals(mCurrentRefreshStatus))
headViewcanPullToRefresh();
}
// 通过偏移下拉头的topMargin值,来实现下拉效果 ,这里是调节headview显示的高度
mHeadViewParams.topMargin = (distance / 2) - mHeadViewHight;
mHeadView.setLayoutParams(mHeadViewParams);
}
break;
//up事件负责根据记录的状态判断对应处理
case MotionEvent.ACTION_UP:
//放开时时可以刷新的状态
if(mCurrentRefreshStatus.equals(PULL_TO_REFRESH)){
downPullToRefreshing();//下拉刷新
}else
if(mCurrentRefreshStatus.equals(PULL_TO_CALCEL_REFRESH)){
canceldownPullToRefresh();
}else{
refreshingFinished();
}
break;
}
return false;
}
/**
* 判断当前能否改变刷新状态,目前只判断是否在刷新状态
* @return
*/
private boolean isCurrentCanChangeRefreshStatis(){
//当前是否正在刷新
if(mCurrentRefreshStatus.equals(REFRESHING))
return false;//正在刷新则不作处理
View firstChild = mListView.getChildAt(0);
if(null == firstChild)//列表没有内容时可以下拉刷新
return true;
//当前是否可以下拉刷新,是否在集合最上方
int firstVisiblePos = mListView.getFirstVisiblePosition();
if(firstVisiblePos != 0)//可见的第一项不是第0个时
return false;
return true;
}
/**
* 刷新
*/
private void downPullToRefreshing(){
mLastRefreshStatus = mCurrentRefreshStatus;
mCurrentRefreshStatus = REFRESHING;
//更新头部的显示为正在刷新
headViewrefreshing();
//让外部刷新数据
mPullToRefreshListener.refresh();//让外部加载数据
}
/**
* 取消下拉刷新
*/
private void canceldownPullToRefresh(){
//更新头部的显示消失
refreshingFinished();
}
/**
* 正在向操作到可以下拉刷新的过程中操作
*/
private void headViewonPullToRefresh(){
//改变箭头,这样写是因为下拉到可以刷新后再手指回移时有一个恢复的动画
rotateArrow(mLoadingImage, 180f, 360f, 100);
//改变文字
mLoadingText.setText("继续下拉可刷新");
//设置上次刷新的时间
mLastLoadingTime.setText(mLastLoadTimeValue);
}
/**
* 已经可以放手下拉刷新
*/
private void headViewcanPullToRefresh(){
//可以刷新时,旋转180度
rotateArrow(mLoadingImage, 0f, 180f, 100);
//改变文字
mLoadingText.setText("放手刷新");
}
/**
* 正在刷新
*/
private void headViewrefreshing(){
//调整headView高度
//这里一开始纠结了一下,一开始是设为一开始记录的hight的,后来想起来其实设为0才是
//希望的视图效果
mHeadViewParams.topMargin = 0;
mHeadView.setLayoutParams(mHeadViewParams);
//隐藏箭头
mLoadingImage.clearAnimation();//不清除的话会导致动画残留
mLoadingImage.setVisibility(GONE);
//显示正在加载的进度
mLoadingProgress.setVisibility(VISIBLE);
//改变文字
mLoadingText.setText("正在刷新");
}
/**
* 刷新结束
*/
public void refreshingFinished(){
//调整headView高度
mHeadViewParams.topMargin = -mHeadViewHight;
mHeadView.setLayoutParams(mHeadViewParams);
//隐藏箭头
mLoadingImage.setVisibility(VISIBLE);
//显示正在加载的进度
mLoadingProgress.setVisibility(GONE);
//刷新数据
mListViewAdapter.notifyDataSetChanged();
mLastRefreshStatus = mCurrentRefreshStatus;
mCurrentRefreshStatus = REFRESH_FINISHED;
}
public interface PullToRefreshListener{
public void refresh();
}
/**
* 旋转图片
* @param arrow 需要旋转的图片
* @param fromAngle 旋转的开始角度
* @param toAngle 旋转的结束角度
* @param time 持续的时间
*/
private void rotateArrow(ImageView arrow,float fromAngle,float toAngle,int time) {
float pivotX = arrow.getWidth() / 2f;
float pivotY = arrow.getHeight() / 2f;
RotateAnimation animation = new RotateAnimation(fromAngle, toAngle, pivotX, pivotY);
animation.setDuration(time);
animation.setFillAfter(true);//是否动画播放完毕后保持状态
arrow.startAnimation(animation);
}
}
可以说我的代码里面注释还是比较全的,平时写代码也习惯写的比较全,一些不熟悉的方法也会写上注释
那么下面就是我的测试程序
package com.example.mypulltorefresh;
import java.util.ArrayList;
import com.example.mypulltorefresh.YPullToRefreshListView.PullToRefreshListener;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.view.Menu;
public class MainActivity extends Activity {
private ArrayList<String> mListData = new ArrayList<String>();
private int mCount = 0;
private YPullToRefreshListView mMyListView;
private Handler mHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mHandler = new Handler(this.getMainLooper());
mMyListView = (YPullToRefreshListView)findViewById(R.id.my_pulltorefresh);
mListData.add("xxxx");
mListData.add("yyyy");
initData();
}
private void initData(){
mMyListView.setListViewData(mListData);
mMyListView.setPullToRefreshListener(new PullToRefreshListener() {
@Override
public void refresh() {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
for(int i = 1;i <=10;i++){
mListData.add(mCount+"");
mCount +=1;
}
mHandler.post(new Runnable() {
@Override
public void run() {
mMyListView.refreshingFinished();
}
});
}
}).start();
}
});
}
}
可以说代码是非常齐全了
这样以后有什么特殊的定制就比较好实现了,现在放在这边备份一份