在上周的时候,我在针对我想要的问题研究了一下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(); } }); } }
可以说代码是非常齐全了
这样以后有什么特殊的定制就比较好实现了,现在放在这边备份一份