这篇文章主要围绕android组件SwipeRefreshLayout下拉小球式刷新效果和android下拉刷新框架展开,旨在为您提供一份详细的参考资料。我们将全面介绍android组件SwipeRe
这篇文章主要围绕android组件SwipeRefreshLayout下拉小球式刷新效果和android下拉刷新框架展开,旨在为您提供一份详细的参考资料。我们将全面介绍android组件SwipeRefreshLayout下拉小球式刷新效果的优缺点,解答android下拉刷新框架的相关问题,同时也会为您带来Android PullToRefreshLayout下拉刷新控件的终结者、Android RefreshLayout实现下拉刷新布局、Android SwipeRefreshLayout、android SwipeRefreshLayout 与 ListView 结合实现下拉刷新,加载功能的实用方法。
本文目录一览:- android组件SwipeRefreshLayout下拉小球式刷新效果(android下拉刷新框架)
- Android PullToRefreshLayout下拉刷新控件的终结者
- Android RefreshLayout实现下拉刷新布局
- Android SwipeRefreshLayout
- android SwipeRefreshLayout 与 ListView 结合实现下拉刷新,加载功能
android组件SwipeRefreshLayout下拉小球式刷新效果(android下拉刷新框架)
swiperefreshlayout实现下拉小球式的刷新,供大家参考,具体内容如下
布局文件:
<?xml version="1.0" encoding="utf-8"?> <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" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity"> <android.support.v4.widget.SwipeRefreshLayout android:id="@+id/swipeRefreshLayout" android:layout_width="wrap_content" android:layout_height="wrap_content" > <ListView android:id="@+id/listView" android:layout_width="match_parent" android:layout_height="wrap_content"> </ListView> </android.support.v4.widget.SwipeRefreshLayout> </RelativeLayout>
MainActivity:
public class MainActivity extends AppCompatActivity implements SwipeRefreshLayout.OnRefreshListener{ private static final int REFRESH_STATUS =0; private ListView myListView; private SwipeRefreshLayout mySwipeRefreshLayout; private ArrayAdapter<String> listadapter; private List<String> listIDE = new ArrayList<String>(Arrays.asList("Visual Studio","Android Studio","Eclipse","Xcode")); private Handler refreshHandler = new Handler() { public void handleMessage(android.os.Message msg) { switch (msg.what) { case REFRESH_STATUS: listIDE.removeAll(listIDE); listIDE.addAll(Arrays.asList("C#","Java","C++","Object-C")); listadapter.notifyDataSetChanged(); mySwipeRefreshLayout.setRefreshing(false); break; } }; }; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); myListView = (ListView) findViewById(R.id.listView); mySwipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipeRefreshLayout); mySwipeRefreshLayout.setonRefreshListener(this); mySwipeRefreshLayout.setColorSchemeResources(android.R.color.holo_blue_bright,android.R.color.holo_green_light,android.R.color.holo_orange_light,android.R.color.holo_red_light); listadapter = new ArrayAdapter(this,android.R.layout.simple_list_item_1,listIDE); myListView.setAdapter(listadapter); } @Override public void onRefresh() { refreshHandler.sendEmptyMessageDelayed(REFRESH_STATUS,1500); } }
效果图:
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持编程小技巧。
Android PullToRefreshLayout下拉刷新控件的终结者
说到下拉刷新控件,网上版本有很多,很多软件也都有下拉刷新功能。有一个叫XListView的,我看别人用过,没看过是咋实现的,看这名字估计是继承自ListView修改的,不过效果看起来挺丑的,也没什么扩展性,太单调了。看了QQ2014的列表下拉刷新,发现挺好看的,我喜欢,贴一下图看一下qq的下拉刷新效果:
不错吧?嗯,是的。一看就知道实现方式不一样。咱们今天就来实现一个下拉刷新控件。由于有时候不仅仅是ListView需要下拉刷新,ExpandableListView和GridView也有这个需求,由于ListView,GridView都是AbsListView的子类,ExpandableListView是ListView的子类所以也是AbsListView的子类。所以我的思路是自定义一个对所有AbsListView的子类通用的下拉管理布局,叫PullToRefreshLayout,如果需要GridView,只需要在布局文件里将ListView换成GridView就行了,ExpandableListView也一样,不需要再继承什么GridView啊ListView啊乱七八糟的。
看上图,主要工作就是定义黑色大布局,红色部分是不下拉的时候的可见部分,可以是任意的AbsListView的子类(GridView,ListView,ExpandableListView等等)。其实我已经写好了,先看一下效果图:
正常拉法:
强迫症拉法:
上面是ListView的,下面是GridView的
再来看一下ExpandableListView的下拉刷新效果:
可以看到,点击事件和长按事件都能正常触发而不会误触发,在使用ExpandableListView的时候需要注意禁止展开时自动滚动,否则会出现bug。后面会提供demo源码下载,可以根据自己的需求去修改。
下面讲解PullToRefreshLayout的实现,在贴完整的源码之前先理解整个类的大概思路:
public class PullToRefreshLayout extends RelativeLayout implements OnTouchListener { // 下拉的距离 public float moveDeltaY = 0; // 是否可以下拉 private boolean canPull = true; private void hideHead() { // 在这里开始异步隐藏下拉头,在松手的时候或这刷新完毕的时候隐藏 } public void refreshFinish(int refreshResult) { // 完成刷新操作,显示刷新结果 } private void changeState(int to) { // 改变当前所处的状态,有四个状态:下拉刷新、释放刷新、正在刷新、刷新完成 } /* * (非 Javadoc)由父控件决定是否分发事件,防止事件冲突 * * @see android.view.ViewGroup#dispatchTouchEvent(android.view.MotionEvent) */ @Override public boolean dispatchTouchEvent(MotionEvent ev) { switch (ev.getActionMasked()) { case MotionEvent.ACTION_DOWN: /*手指按下的时候,无法判断是否将要下拉,所以这时候break让父类把down事件分发给子View 记录按下的坐标*/ break; case MotionEvent.ACTION_MOVE: /*如果往上滑动且moveDetaY==0则说明不在下拉,break继续将move事件分发给子View 如果往下拉,则计算下拉的距离moveDeltaY,根据moveDeltaY重新Layout子控件。但是 由于down事件传到了子View,如果不清除子View的事件,会导致子View误触发长按事件和点击事件。所以在这里清除子View的事件回调。 下拉超过一定的距离时,改变当前状态*/ break; case MotionEvent.ACTION_UP: //根据当前状态执行刷新操作或者hideHead default: break; } // 事件分发交给父类 return super.dispatchTouchEvent(ev); } /* * (非 Javadoc)绘制阴影效果,颜色值可以修改 * * @see android.view.ViewGroup#dispatchDraw(android.graphics.Canvas) */ @Override protected void dispatchDraw(Canvas canvas) { //在这里用一个渐变绘制分界线阴影 } @Override protected void onLayout(boolean changed,int l,int t,int r,int b) { //这个方法就是重新Layout子View了,根据moveDeltaY来定位子View的位置 } @Override public boolean onTouch(View v,MotionEvent event) { //这个是OnTouchListener的方法,只判断AbsListView的状态来决定是否canPull,除此之外不做其他处理 } }
可以看到,这里复写了ViewGroup的dispatchTouchEvent,这样就可以掌控事件的分发,如果不了解这个方法可以看一下这篇Android事件分发、View事件Listener全解析。之所以要控制事件分发是因为我们不可能知道手指down在AbsListView上之后将往上滑还是往下拉,所以down事件会分发给AbsListView的,但是在move的时候就需要看情况了,因为我们不想在下拉的同时AbsListView也在滑动,所以在下拉的时候不分发move事件,但这样问题又来了,前面AbsListView已经接收了down事件,如果这时候不分发move事件给它,它会触发长按事件或者点击事件,所以在这里还需要清除AbsListView消息列表中的callback。
onLayout用于重新布置下拉头和AbsListView的位置的,这个不难理解。
理解了大概思路之后,看一下PullToRefreshLayout完整的源码吧~
package com.jingchen.pulltorefresh; import java.lang.reflect.Field; import java.util.Timer; import java.util.TimerTask; import android.content.Context; import android.graphics.Canvas; import android.graphics.LinearGradient; import android.graphics.Paint; import android.graphics.Paint.Style; import android.graphics.RectF; import android.graphics.Shader.TileMode; import android.os.Handler; import android.os.Message; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.animation.AnimationUtils; import android.view.animation.LinearInterpolator; import android.view.animation.RotateAnimation; import android.widget.AbsListView; import android.widget.RelativeLayout; import android.widget.TextView; /** * 整个下拉刷新就这一个布局,用来管理两个子控件,其中一个是下拉头,另一个是包含内容的contentView(可以是AbsListView的任何子类) * * @author 陈靖 */ public class PullToRefreshLayout extends RelativeLayout implements OnTouchListener { public static final String TAG = "PullToRefreshLayout"; // 下拉刷新 public static final int PULL_TO_REFRESH = 0; // 释放刷新 public static final int RELEASE_TO_REFRESH = 1; // 正在刷新 public static final int REFRESHING = 2; // 刷新完毕 public static final int DONE = 3; // 当前状态 private int state = PULL_TO_REFRESH; // 刷新回调接口 private OnRefreshListener mListener; // 刷新成功 public static final int REFRESH_SUCCEED = 0; // 刷新失败 public static final int REFRESH_FAIL = 1; // 下拉头 private View headView; // 内容 private View contentView; // 按下Y坐标,上一个事件点Y坐标 private float downY,lastY; // 下拉的距离 public float moveDeltaY = 0; // 释放刷新的距离 private float refreshdist = 200; private Timer timer; private MyTimerTask mTask; // 回滚速度 public float MOVE_SPEED = 8; // 第一次执行布局 private boolean isLayout = false; // 是否可以下拉 private boolean canPull = true; // 在刷新过程中滑动操作 private boolean isTouchInRefreshing = false; // 手指滑动距离与下拉头的滑动距离比,中间会随正切函数变化 private float radio = 2; // 下拉箭头的转180°动画 private RotateAnimation rotateAnimation; // 均匀旋转动画 private RotateAnimation refreshingAnimation; // 下拉的箭头 private View pullView; // 正在刷新的图标 private View refreshingView; // 刷新结果图标 private View stateImageView; // 刷新结果:成功或失败 private TextView stateTextView; /** * 执行自动回滚的handler */ Handler updateHandler = new Handler() { @Override public void handleMessage(Message msg) { // 回弹速度随下拉距离moveDeltaY增大而增大 MOVE_SPEED = (float) (8 + 5 * Math.tan(Math.PI / 2 / getMeasuredHeight() * moveDeltaY)); if (state == REFRESHING && moveDeltaY <= refreshdist && !isTouchInRefreshing) { // 正在刷新,且没有往上推的话则悬停,显示"正在刷新..." moveDeltaY = refreshdist; mTask.cancel(); } if (canPull) moveDeltaY -= MOVE_SPEED; if (moveDeltaY <= 0) { // 已完成回弹 moveDeltaY = 0; pullView.clearanimation(); // 隐藏下拉头时有可能还在刷新,只有当前状态不是正在刷新时才改变状态 if (state != REFRESHING) changeState(PULL_TO_REFRESH); mTask.cancel(); } // 刷新布局,会自动调用onLayout requestLayout(); } }; public void setonRefreshListener(OnRefreshListener listener) { mListener = listener; } public PullToRefreshLayout(Context context) { super(context); initView(context); } public PullToRefreshLayout(Context context,AttributeSet attrs) { super(context,attrs); initView(context); } public PullToRefreshLayout(Context context,AttributeSet attrs,int defStyle) { super(context,attrs,defStyle); initView(context); } private void initView(Context context) { timer = new Timer(); mTask = new MyTimerTask(updateHandler); rotateAnimation = (RotateAnimation) AnimationUtils.loadAnimation(context,R.anim.reverse_anim); refreshingAnimation = (RotateAnimation) AnimationUtils.loadAnimation(context,R.anim.rotating); // 添加匀速转动动画 LinearInterpolator lir = new LinearInterpolator(); rotateAnimation.setInterpolator(lir); refreshingAnimation.setInterpolator(lir); } private void hideHead() { if (mTask != null) { mTask.cancel(); mTask = null; } mTask = new MyTimerTask(updateHandler); timer.schedule(mTask,5); } /** * 完成刷新操作,显示刷新结果 */ public void refreshFinish(int refreshResult) { refreshingView.clearanimation(); refreshingView.setVisibility(View.GONE); switch (refreshResult) { case REFRESH_SUCCEED: // 刷新成功 stateImageView.setVisibility(View.VISIBLE); stateTextView.setText(R.string.refresh_succeed); stateImageView.setBackgroundResource(R.drawable.refresh_succeed); break; case REFRESH_FAIL: // 刷新失败 stateImageView.setVisibility(View.VISIBLE); stateTextView.setText(R.string.refresh_fail); stateImageView.setBackgroundResource(R.drawable.refresh_Failed); break; default: break; } // 刷新结果停留1秒 new Handler() { @Override public void handleMessage(Message msg) { state = PULL_TO_REFRESH; hideHead(); } }.sendEmptyMessageDelayed(0,1000); } private void changeState(int to) { state = to; switch (state) { case PULL_TO_REFRESH: // 下拉刷新 stateImageView.setVisibility(View.GONE); stateTextView.setText(R.string.pull_to_refresh); pullView.clearanimation(); pullView.setVisibility(View.VISIBLE); break; case RELEASE_TO_REFRESH: // 释放刷新 stateTextView.setText(R.string.release_to_refresh); pullView.startAnimation(rotateAnimation); break; case REFRESHING: // 正在刷新 pullView.clearanimation(); refreshingView.setVisibility(View.VISIBLE); pullView.setVisibility(View.INVISIBLE); refreshingView.startAnimation(refreshingAnimation); stateTextView.setText(R.string.refreshing); break; default: break; } } /* * (非 Javadoc)由父控件决定是否分发事件,防止事件冲突 * * @see android.view.ViewGroup#dispatchTouchEvent(android.view.MotionEvent) */ @Override public boolean dispatchTouchEvent(MotionEvent ev) { switch (ev.getActionMasked()) { case MotionEvent.ACTION_DOWN: downY = ev.getY(); lastY = downY; if (mTask != null) { mTask.cancel(); } /* * 触碰的地方位于下拉头布局,由于我们没有对下拉头做事件响应,这时候它会给咱返回一个false导致接下来的事件不再分发进来。 * 所以我们不能交给父类分发,直接返回true */ if (ev.getY() < moveDeltaY) return true; break; case MotionEvent.ACTION_MOVE: // canPull这个值在底下onTouch中会根据ListView是否滑到顶部来改变,意思是是否可下拉 if (canPull) { // 对实际滑动距离做缩小,造成用力拉的感觉 moveDeltaY = moveDeltaY + (ev.getY() - lastY) / radio; if (moveDeltaY < 0) moveDeltaY = 0; if (moveDeltaY > getMeasuredHeight()) moveDeltaY = getMeasuredHeight(); if (state == REFRESHING) { // 正在刷新的时候触摸移动 isTouchInRefreshing = true; } } lastY = ev.getY(); // 根据下拉距离改变比例 radio = (float) (2 + 2 * Math.tan(Math.PI / 2 / getMeasuredHeight() * moveDeltaY)); requestLayout(); if (moveDeltaY <= refreshdist && state == RELEASE_TO_REFRESH) { // 如果下拉距离没达到刷新的距离且当前状态是释放刷新,改变状态为下拉刷新 changeState(PULL_TO_REFRESH); } if (moveDeltaY >= refreshdist && state == PULL_TO_REFRESH) { changeState(RELEASE_TO_REFRESH); } if (moveDeltaY > 8) { // 防止下拉过程中误触发长按事件和点击事件 clearContentViewEvents(); } if (moveDeltaY > 0) { // 正在下拉,不让子控件捕获事件 return true; } break; case MotionEvent.ACTION_UP: if (moveDeltaY > refreshdist) // 正在刷新时往下拉释放后下拉头不隐藏 isTouchInRefreshing = false; if (state == RELEASE_TO_REFRESH) { changeState(REFRESHING); // 刷新操作 if (mListener != null) mListener.onRefresh(); } else { } hideHead(); default: break; } // 事件分发交给父类 return super.dispatchTouchEvent(ev); } /** * 通过反射修改字段去掉长按事件和点击事件 */ private void clearContentViewEvents() { try { Field[] fields = AbsListView.class.getDeclaredFields(); for (int i = 0; i < fields.length; i++) if (fields[i].getName().equals("mPendingCheckForLongPress")) { // mPendingCheckForLongPress是AbsListView中的字段,通过反射获取并从消息列表删除,去掉长按事件 fields[i].setAccessible(true); contentView.getHandler().removeCallbacks((Runnable) fields[i].get(contentView)); } else if (fields[i].getName().equals("mTouchMode")) { // TOUCH_MODE_REST = -1, 这个可以去除点击事件 fields[i].setAccessible(true); fields[i].set(contentView,-1); } // 去掉焦点 ((AbsListView) contentView).getSelector().setState(new int[] { 0 }); } catch (Exception e) { Log.d(TAG,"error : " + e.toString()); } } /* * (非 Javadoc)绘制阴影效果,颜色值可以修改 * * @see android.view.ViewGroup#dispatchDraw(android.graphics.Canvas) */ @Override protected void dispatchDraw(Canvas canvas) { super.dispatchDraw(canvas); if (moveDeltaY == 0) return; RectF rectF = new RectF(0,getMeasuredWidth(),moveDeltaY); Paint paint = new Paint(); paint.setAntiAlias(true); // 阴影的高度为26 LinearGradient linearGradient = new LinearGradient(0,moveDeltaY,moveDeltaY - 26,0x66000000,0x00000000,TileMode.CLAMP); paint.setShader(linearGradient); paint.setStyle(Style.FILL); // 在moveDeltaY处往上变淡 canvas.drawRect(rectF,paint); } private void initView() { pullView = headView.findViewById(R.id.pull_icon); stateTextView = (TextView) headView.findViewById(R.id.state_tv); refreshingView = headView.findViewById(R.id.refreshing_icon); stateImageView = headView.findViewById(R.id.state_iv); } @Override protected void onLayout(boolean changed,int b) { if (!isLayout) { // 这里是第一次进来的时候做一些初始化 headView = getChildAt(0); contentView = getChildAt(1); // 给AbsListView设置OnTouchListener contentView.setonTouchListener(this); isLayout = true; initView(); refreshdist = ((ViewGroup) headView).getChildAt(0).getMeasuredHeight(); } if (canPull) { // 改变子控件的布局 headView.layout(0,(int) moveDeltaY - headView.getMeasuredHeight(),headView.getMeasuredWidth(),(int) moveDeltaY); contentView.layout(0,(int) moveDeltaY,contentView.getMeasuredWidth(),(int) moveDeltaY + contentView.getMeasuredHeight()); }else super.onLayout(changed,l,t,r,b); } class MyTimerTask extends TimerTask { Handler handler; public MyTimerTask(Handler handler) { this.handler = handler; } @Override public void run() { handler.sendMessage(handler.obtainMessage()); } } @Override public boolean onTouch(View v,MotionEvent event) { // 第一个item可见且滑动到顶部 AbsListView alv = null; try { alv = (AbsListView) v; } catch (Exception e) { Log.d(TAG,e.getMessage()); return false; } if (alv.getCount() == 0) { // 没有item的时候也可以下拉刷新 canPull = true; } else if (alv.getFirstVisiblePosition() == 0 && alv.getChildAt(0).getTop() >= 0) { // 滑到AbsListView的顶部了 canPull = true; } else canPull = false; return false; } }
代码中的注释已经写的很清楚了。
既然PullToRefreshLayout已经写好了,接下来就来使用这个Layout实现下拉刷新~
首先得写个OnRefreshListener接口来回调刷新操作:
public interface OnRefreshListener { void onRefresh(); }
就一个刷新操作的方法,待会儿让Activity实现这个接口就可以在Activity中执行刷新操作了。
看一下MainActivity的布局:
<com.jingchen.pulltorefresh.PullToRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/refresh_view" android:layout_width="match_parent" android:layout_height="match_parent" > <include layout="@layout/refresh_head" /> <!-- 支持AbsListView的所有子类 --> <ListView android:id="@+id/content_view" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/white" android:divider="@color/gray" android:dividerHeight="1dp" > </ListView> </com.jingchen.pulltorefresh.PullToRefreshLayout>
PullToRefreshLayout只能包含两个子控件:refresh_head和content_view。
看一下refresh_head的布局:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/head_view" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/light_blue" > <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:paddingBottom="20dp" android:paddingTop="20dp" > <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_centerInParent="true" > <ImageView android:id="@+id/pull_icon" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_marginLeft="60dp" android:background="@drawable/pull_icon_big" /> <ImageView android:id="@+id/refreshing_icon" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_marginLeft="60dp" android:background="@drawable/refreshing" android:visibility="gone" /> <TextView android:id="@+id/state_tv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:text="@string/pull_to_refresh" android:textColor="@color/white" android:textSize="16sp" /> <ImageView android:id="@+id/state_iv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_marginRight="8dp" android:layout_toLeftOf="@id/state_tv" android:visibility="gone" /> </RelativeLayout> </RelativeLayout> </RelativeLayout>
可以根据需要修改refresh_head的布局然后在PullToRefreshLayout中处理,但是相关View的id要和PullToRefreshLayout中用到的保持同步!
接下来是MainActivity的代码:
package com.jingchen.pulltorefresh; import java.util.ArrayList; import java.util.List; import android.app.Activity; import android.content.Context; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.support.v4.view.PagerAdapter; import android.support.v4.view.ViewPager; import android.support.v4.view.ViewPager.OnPagechangelistener; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.view.animation.AnimationUtils; import android.view.animation.LinearInterpolator; import android.view.animation.RotateAnimation; import android.widget.AbsListView; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.AdapterView.OnItemLongClickListener; import android.widget.Baseexpandablelistadapter; import android.widget.ExpandableListView; import android.widget.ExpandableListView.OnChildClickListener; import android.widget.ExpandableListView.OnGroupClickListener; import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; /** * 除了下拉刷新,在contenview为ListView的情况下我给ListView增加了FooterView,实现点击加载更多 * * @author 陈靖 * */ public class MainActivity extends Activity implements OnRefreshListener,OnClickListener { private AbsListView alv; private PullToRefreshLayout refreshLayout; private View loading; private RotateAnimation loadingAnimation; private TextView loadTextView; private MyAdapter adapter; private boolean isLoading = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); init(); } private void init() { alv = (AbsListView) findViewById(R.id.content_view); refreshLayout = (PullToRefreshLayout) findViewById(R.id.refresh_view); refreshLayout.setonRefreshListener(this); initListView(); loadingAnimation = (RotateAnimation) AnimationUtils.loadAnimation(this,R.anim.rotating); // 添加匀速转动动画 LinearInterpolator lir = new LinearInterpolator(); loadingAnimation.setInterpolator(lir); } /** * ListView初始化方法 */ private void initListView() { List<String> items = new ArrayList<String>(); for (int i = 0; i < 30; i++) { items.add("这里是item " + i); } // 添加head View headView = getLayoutInflater().inflate(R.layout.listview_head,null); ((ListView) alv).addHeaderView(headView,null,false); // 添加footer View footerView = getLayoutInflater().inflate(R.layout.load_more,null); loading = footerView.findViewById(R.id.loading_icon); loadTextView = (TextView) footerView.findViewById(R.id.loadmore_tv); ((ListView) alv).addFooterView(footerView,false); footerView.setonClickListener(this); adapter = new MyAdapter(this,items); alv.setAdapter(adapter); alv.setonItemLongClickListener(new OnItemLongClickListener() { @Override public boolean onItemLongClick(AdapterView<?> parent,View view,int position,long id) { Toast.makeText(MainActivity.this,"LongClick on " + parent.getAdapter().getItemId(position),Toast.LENGTH_SHORT).show(); return true; } }); alv.setonItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent," Click on " + parent.getAdapter().getItemId(position),Toast.LENGTH_SHORT).show(); } }); } /** * GridView初始化方法 */ private void initGridView() { List<String> items = new ArrayList<String>(); for (int i = 0; i < 30; i++) { items.add("这里是item " + i); } adapter = new MyAdapter(this,Toast.LENGTH_SHORT).show(); } }); } /** * ExpandableListView初始化方法 */ private void initExpandableListView() { ((ExpandableListView) alv).setAdapter(new expandablelistadapter(this)); ((ExpandableListView) alv).setonChildClickListener(new OnChildClickListener() { @Override public boolean onChildClick(ExpandableListView parent,View v,int groupPosition,int childPosition," Click on group " + groupPosition + " item " + childPosition,Toast.LENGTH_SHORT).show(); return true; } }); ((ExpandableListView) alv).setonItemLongClickListener(new OnItemLongClickListener() { @Override public boolean onItemLongClick(AdapterView<?> parent,Toast.LENGTH_SHORT).show(); return true; } }); ((ExpandableListView) alv).setonGroupClickListener(new OnGroupClickListener() { @Override public boolean onGroupClick(ExpandableListView parent,long id) { if (parent.isGroupExpanded(groupPosition)) { // 如果展开则关闭 parent.collapseGroup(groupPosition); } else { // 如果关闭则打开,注意这里是手动打开不要默认滚动否则会有bug parent.expandGroup(groupPosition); } return true; } }); } @Override public void onRefresh() { // 下拉刷新操作 new Handler() { @Override public void handleMessage(Message msg) { refreshLayout.refreshFinish(PullToRefreshLayout.REFRESH_SUCCEED); } }.sendEmptyMessageDelayed(0,5000); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.loadmore_layout: if (!isLoading) { loading.setVisibility(View.VISIBLE); loading.startAnimation(loadingAnimation); loadTextView.setText(R.string.loading); isLoading = true; } break; default: break; } } class expandablelistadapter extends Baseexpandablelistadapter { private String[] groupsstrings;// = new String[] { "这里是group 0",// "这里是group 1","这里是group 2" }; private String[][] groupItems; private Context context; public expandablelistadapter(Context context) { this.context = context; groupsstrings = new String[8]; for (int i = 0; i < groupsstrings.length; i++) { groupsstrings[i] = new String("这里是group " + i); } groupItems = new String[8][8]; for (int i = 0; i < groupItems.length; i++) for (int j = 0; j < groupItems[i].length; j++) { groupItems[i][j] = new String("这里是group " + i + "里的item " + j); } } @Override public int getGroupCount() { return groupsstrings.length; } @Override public int getChildrenCount(int groupPosition) { return groupItems[groupPosition].length; } @Override public Object getGroup(int groupPosition) { return groupsstrings[groupPosition]; } @Override public Object getChild(int groupPosition,int childPosition) { return groupItems[groupPosition][childPosition]; } @Override public long getGroupId(int groupPosition) { return groupPosition; } @Override public long getChildId(int groupPosition,int childPosition) { return childPosition; } @Override public boolean hasstableIds() { return true; } @Override public View getGroupView(int groupPosition,boolean isExpanded,View convertView,ViewGroup parent) { View view = LayoutInflater.from(context).inflate(R.layout.list_item_layout,null); TextView tv = (TextView) view.findViewById(R.id.name_tv); tv.setText(groupsstrings[groupPosition]); return view; } @Override public View getChildView(int groupPosition,boolean isLastChild,null); TextView tv = (TextView) view.findViewById(R.id.name_tv); tv.setText(groupItems[groupPosition][childPosition]); return view; } @Override public boolean isChildSelectable(int groupPosition,int childPosition) { return true; } } }
在MainActivity中判断contentView是ListView的话给ListView添加了FooterView实现点击加载更多的功能。这只是一个演示PullToRefreshLayout使用的demo,可以参照一下修改。我已经在里面写了ListView,GridView和ExpandableListView的初始化方法,根据自己使用的是哪个来调用吧。那么这是ListView的下拉刷新和加载更多。如果我要GridView也有下拉刷新功能呢?那就把MainActivity的布局换成这样:
<com.jingchen.pulltorefresh.PullToRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/refresh_view" android:layout_width="match_parent" android:layout_height="match_parent" > <include layout="@layout/refresh_head" /> <!-- 支持AbsListView的所有子类 --> <GridView android:id="@+id/content_view" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/white" android:columnWidth="90dp" android:gravity="center" android:horizontalSpacing="10dp" android:numColumns="auto_fit" android:stretchMode="columnWidth" android:verticalSpacing="15dp" > </GridView> </com.jingchen.pulltorefresh.PullToRefreshLayout>
如果是ExpandableListView则把布局改成这样:
<com.jingchen.pulltorefresh.PullToRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/refresh_view" android:layout_width="match_parent" android:layout_height="match_parent" > <include layout="@layout/refresh_head" /> <!-- 支持AbsListView的所有子类 --> <ExpandableListView android:id="@+id/content_view" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/white" > </ExpandableListView> </com.jingchen.pulltorefresh.PullToRefreshLayout>
怎么样?很简单吧?简单易用,不用再去继承修改了。
本文已经被整理到《Android下拉刷新上拉加载效果》,欢迎大家学习研究。
希望本文所述对大家学习Android下拉刷新控件有所帮助。
Android RefreshLayout实现下拉刷新布局
项目中需要下拉刷新的功能,但是这个View不是ListView这类的控件,需要ViewGroup实现这个功能,一开始网上大略找了一下,没发现特别合适的,代码也是没怎么看懂,所以决定还是自己写一个。
于是翻出XlistView的源码看是一点一点看,再大致理解了XLisview源码,终于决定自己动手啦
为了省事,headView还是用了XListView的HeadView,省了很多事:)
下拉刷新,下拉刷新,肯定是先实现下拉功能,最开始我是打算通过 extends ScrollView 来实现,因为有现成的滚动效果嘛,可是实际因为两个原因放弃了:
1、ScrollView下只能有一个子控件View,虽然在 Scroll下添加一个ViewGroup,然后讲headView动态添加进前面的ViewGroup,但是我还是比较习惯studio的可视化预览,总觉得不直观!
2、 ScrollView内嵌ListView时会发生 冲突,还需要去重写ListView。于是放弃换个思路!
关于上面的原因1:动态添加headView进ScrollView的中GroupView中,可以在重写ScrollView的onViewAdded()方法,将初始化时解析的headView添加进子GroupView
@Override public void onViewAdded(View child) { super.onViewAdded(child); //因为headView要在最上面,最先想到的就是Vertical的LinearLayout LinearLayout linearLayout = (LinearLayout) getChildAt(0); linearLayout.addView(view,0); }
换个思路,通过extends LinearLayout来实现吧!
先做准备工作,我们需要一个HeaderView以及要获取到HeaderView的高度,还有初始时Layout的高度
private void initView(Context context) { mHeaderView = new SRefreshHeader(context); mHeaderViewContent = (RelativeLayout) mHeaderView.findViewById(R.id.slistview_header_content); setorientation(VERTICAL); addView(mHeaderView,0); getHeaderViewHeight(); getViewHeight(); }
mHeaderView = new SRefreshHeader(context);
通过构造方法实例化HeaderView
mHeaderViewContent = (RelativeLayout)
mHeaderView.findViewById(R.id.slistview_header_content);
这是解析headerView内容区域iew,等会儿要获取这个view的高度,你肯定会问为啥不用上面的mHeaderView来获取高度,点进构造方法里可以看到如下代码
// 初始情况,设置下拉刷新view高度为0 LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT,0); mContainer = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.listview_head_view_layout,null); w(mContainer,lp);
如果直接获取mHeaderView的高度 那肯定是0
getHeaderViewHeight();
getViewHeight();
分别是获取HeaderView的高度和Layout的初始高度
/** * 获取headView高度 */ private void getHeaderViewHeight() { ViewTreeObserver vto2 = mHeaderViewContent.getViewTreeObserver(); vto2.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { mHeaderViewHeight = mHeaderViewContent.getHeight(); mHeaderViewContent.getViewTreeObserver().removeGlobalOnLayoutListener(this); } }); } /** * 获取SRefreshLayout当前实例的高度 */ private void getViewHeight() { ViewTreeObserver thisView = getViewTreeObserver(); thisView.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { SRefreshLayout.this.mHeight = SRefreshLayout.this.getHeight(); SRefreshLayout.this.getViewTreeObserver().removeGlobalOnLayoutListener(this); } }); }
准备工作完成了,接下来就是要成下拉操作了
到这里,肯定一下就想到了onTouchEvent()方法,是的!现在就开始在这里施工
实现下拉一共 会经历三个过程
ACTION_UP→ACTION_MOVE→ACTION_UP
在ACTION_UP事件中,也就是手指按下的时候,我们需要做的只是记录按下时候的坐标
switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: //记录起始高度 mLastY = ev.getRawY();//记录按下时的Y坐标 break;
然后就是ACTION_MOVE事件了,这里是最重要的,因为下拉时HeadView和Layout的高度变化都在这里进行
case MotionEvent.ACTION_MOVE: if (!isRefreashing) isRefreashing = true; final float deltaY = ev.getRawY() - mLastY; mLastY = ev.getRawY(); updateHeaderViewHeight(deltaY / 1.8f);//按一定比例缩小移动距离 updateHeight(); break;
里面的updateHeaderViewHeight和updateHeight分别是改变HeaderView的高度和Layout的高度
private void updateHeight() { ViewGroup.LayoutParams lp = getLayoutParams(); //更新当前layout实例高度为headerView高度加上最初的layout高度 //如果不更新layout 会造成内容高度压缩 无法保持比例 lp.height = (mHeight + mHeaderView.getVisiableHeight()); setLayoutParams(lp); } private void updateHeaderViewHeight(float space) { // if (space < 0) // space = 0; // int factHeight = (int) (space - mHeaderViewHeight); if (mHeaderView.getStatus() != SRefreshHeader.STATE_REFRESHING) { //如果不处于刷新中同时如果高度 if (mHeaderView.getVisiableHeight() < mHeaderViewHeight * 2 && mHeaderView.getStatus() != SRefreshHeader.STATE_norMAL) { mHeaderView.setState(SRefreshHeader.STATE_norMAL); } if (mHeaderView.getVisiableHeight() > mHeaderViewHeight * 2 && mHeaderView.getStatus() != SRefreshHeader.STATE_READY) { mHeaderView.setState(SRefreshHeader.STATE_READY); } } mHeaderView.setVisiableHeight((int) space + mHeaderView.getVisiableHeight()); }
更新Header高度时,通过下拉的距离来判断是否到达刷新的距离,上面代码中我设定的是到达mHeaderView初始高度的两倍,就进入“释放刷新”的状态,如果没有达到则保持“下拉刷新”的状态
HeaderView中的状态一共设定了3个分别是
public final static int STATE_norMAL = 0;//下拉刷新 public final static int STATE_READY = 1;//释放刷新 public final static int STATE_REFRESHING = 2;//刷新中
更新高度的方法headerView和layout都是相同的,就是原高度加上移动的距离重新赋给headerView或者layout
mHeaderView.setVisiableHeight((int) space
+ mHeaderView.getVisiableHeight());
最后就是ACTION_UP事件了就是手指离开屏幕的时候,在这里我们需要根据headerView目前状态来决定headerView的最终状态!
case MotionEvent.ACTION_UP: //松开时 //避免点击事件触发 if (!isRefreashing) break; //如果headView状态处于READY状态 则说明松开时应该进入REFRESHING状态 if (mHeaderView.getStatus() == SRefreshHeader.STATE_READY) { mHeaderView.setState(SRefreshHeader.STATE_REFRESHING); } //根据状态重置SrefreshLayout当前实例和headView高度 resetHeadView(mHeaderView.getStatus()); reset(mHeaderView.getStatus()); mLastY = -1;//重置坐标 break;
resetHeadView和reset分别是重置headerView高度和layout高度的方法
private void reset(int status) { ViewGroup.LayoutParams lp = getLayoutParams(); switch (status) { case SRefreshHeader.STATE_REFRESHING: lp.height = mHeight + mHeaderViewHeight; break; case SRefreshHeader.STATE_norMAL: lp.height = mHeight; break; } setLayoutParams(lp); } private void resetHeadView(int status) { switch (status) { case SRefreshHeader.STATE_REFRESHING: mHeaderView.setVisiableHeight(mHeaderViewHeight); break; case SRefreshHeader.STATE_norMAL: mHeaderView.setVisiableHeight(0); break; } }
实现方式也是一样的。根据状态来判断,如果是处于刷新中,那headerView应该正常显示,并且高度是初始的高度,如果处于norMAL,也就是"下拉刷新"状态,那么说未触发刷新,重置时,headerView应该被隐藏,也就是高度重置为0
到这里下拉刷新操作也基本完成了,还需要加一个回调接口进行通知
interface OnRefreshListener { void onRefresh(); }
case MotionEvent.ACTION_UP: //松开时 //避免点击事件触发 if (!isRefreashing) break; //如果headView状态处于READY状态 则说明松开时应该进入REFRESHING状态 if (mHeaderView.getStatus() == SRefreshHeader.STATE_READY) { mHeaderView.setState(SRefreshHeader.STATE_REFRESHING); if (mOnRefreshListener != null) mOnRefreshListener.onRefresh(); } //根据状态重置SrefreshLayout当前实例和headView高度 resetHeadView(mHeaderView.getStatus()); reset(mHeaderView.getStatus()); mLastY = -1;//重置坐标 break;
好,到这里就基本完成了,试试效果吧。咦,发现一个问题,嵌套ListView的时候为什么这个Layout不能执行下拉刷新!仔细想想应该是事件分发的问题,还需要处理一下事件的拦截!
关于事件拦截的处理,阅读了鸿洋大神写的viewgroup事件分发的博客和Android-Ultra-Pull-To-Refresh的部分源码,从中找到了解决办法:
@Override public boolean onInterceptTouchEvent(MotionEvent ev) { AbsListView absListView = null; for (int n = 0; n < getChildCount(); n++) { if (getChildAt(n) instanceof AbsListView) { absListView = (ListView) getChildAt(n); Logs.v("查找到listView"); } } if (absListView == null) return super.onInterceptTouchEvent(ev); switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: mStartY = ev.getRawY(); break; case MotionEvent.ACTION_MOVE: float space = ev.getRawY() - mStartY; Logs.v("space:" + space); if (space > 0 && !absListView.canScrollVertically(-1) && absListView.getFirstVisiblePosition() == 0) { Logs.v("拦截成功"); return true; } else { Logs.v("不拦截"); return false; } } return super.onInterceptTouchEvent(ev); }
其中
if (space > 0 && !absListView.canScrollVertically(-1) && absListView.getFirstVisiblePosition() == 0)
space即移动的距离 canScrollVertically()是判断ListView能否在垂直方向上滚动,参数为负数时代表向上,为正数时代码向下滚动,最后一个就是ListView第一个可见的item的postion
加上上面的事件拦截处理,一个可以满足开头提到的需求的Viewgroup也就完成了!
下面贴上Layout的源码和HeaderView(直接使用的XlistView的HeaderView)的源码
public class SRefreshLayout extends LinearLayout { private SRefreshHeader mHeaderView; private RelativeLayout mHeaderViewContent; private boolean isRefreashing; private float mLastY = -1;//按下的起始高度 private int mHeaderViewHeight;//headerView内容高度 private int mHeight;//布局高度 private float mStartY; interface OnRefreshListener { void onRefresh(); } public OnRefreshListener mOnRefreshListener; public SRefreshLayout(Context context) { super(context); initView(context); } public SRefreshLayout(Context context,AttributeSet attrs) { super(context,attrs); initView(context); } public SRefreshLayout(Context context,AttributeSet attrs,int defStyleAttr) { super(context,attrs,defStyleAttr); initView(context); } private void initView(Context context) { mHeaderView = new SRefreshHeader(context); mHeaderViewContent = (RelativeLayout) mHeaderView.findViewById(R.id.slistview_header_content); setorientation(VERTICAL); addView(mHeaderView,0); getHeaderViewHeight(); getViewHeight(); } /** * 获取headView高度 */ private void getHeaderViewHeight() { ViewTreeObserver vto2 = mHeaderViewContent.getViewTreeObserver(); vto2.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { mHeaderViewHeight = mHeaderViewContent.getHeight(); mHeaderViewContent.getViewTreeObserver().removeGlobalOnLayoutListener(this); } }); } /** * 获取SRefreshLayout当前实例的高度 */ private void getViewHeight() { ViewTreeObserver thisView = getViewTreeObserver(); thisView.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { SRefreshLayout.this.mHeight = SRefreshLayout.this.getHeight(); SRefreshLayout.this.getViewTreeObserver().removeGlobalOnLayoutListener(this); } }); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { AbsListView absListView = null; for (int n = 0; n < getChildCount(); n++) { if (getChildAt(n) instanceof AbsListView) { absListView = (ListView) getChildAt(n); Logs.v("查找到listView"); } } if (absListView == null) return super.onInterceptTouchEvent(ev); switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: mStartY = ev.getRawY(); break; case MotionEvent.ACTION_MOVE: float space = ev.getRawY() - mStartY; Logs.v("space:" + space); if (space > 0 && !absListView.canScrollVertically(-1) && absListView.getFirstVisiblePosition() == 0) { Logs.v("拦截成功"); return true; } else { Logs.v("不拦截"); return false; } } return super.onInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent ev) { if (mLastY == -1) mLastY = ev.getRawY(); switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: //记录起始高度 mLastY = ev.getRawY();//记录按下时的Y坐标 break; //手指离开屏幕时 case MotionEvent.ACTION_UP: //松开时 //避免点击事件触发 if (!isRefreashing) break; //如果headView状态处于READY状态 则说明松开时应该进入REFRESHING状态 if (mHeaderView.getStatus() == SRefreshHeader.STATE_READY) { mHeaderView.setState(SRefreshHeader.STATE_REFRESHING); if (mOnRefreshListener != null) mOnRefreshListener.onRefresh(); } //根据状态重置SrefreshLayout当前实例和headView高度 resetHeadView(mHeaderView.getStatus()); reset(mHeaderView.getStatus()); mLastY = -1;//重置坐标 break; case MotionEvent.ACTION_MOVE: if (!isRefreashing) isRefreashing = true; final float deltaY = ev.getRawY() - mLastY; mLastY = ev.getRawY(); updateHeaderViewHeight(deltaY / 1.8f);//按一定比例缩小移动距离 updateHeight(); break; } return super.onTouchEvent(ev); } private void reset(int status) { ViewGroup.LayoutParams lp = getLayoutParams(); switch (status) { case SRefreshHeader.STATE_REFRESHING: lp.height = mHeight + mHeaderViewHeight; break; case SRefreshHeader.STATE_norMAL: lp.height = mHeight; break; } setLayoutParams(lp); } private void resetHeadView(int status) { switch (status) { case SRefreshHeader.STATE_REFRESHING: mHeaderView.setVisiableHeight(mHeaderViewHeight); break; case SRefreshHeader.STATE_norMAL: mHeaderView.setVisiableHeight(0); break; } } private void updateHeight() { ViewGroup.LayoutParams lp = getLayoutParams(); //更新当前layout实例高度为headerView高度加上最初的layout高度 //如果不更新layout 会造成内容高度压缩 无法保持比例 lp.height = (mHeight + mHeaderView.getVisiableHeight()); setLayoutParams(lp); } private void updateHeaderViewHeight(float space) { // if (space < 0) // space = 0; // int factHeight = (int) (space - mHeaderViewHeight); if (mHeaderView.getStatus() != SRefreshHeader.STATE_REFRESHING) { //如果不处于刷新中同时如果高度 if (mHeaderView.getVisiableHeight() < mHeaderViewHeight * 2 && mHeaderView.getStatus() != SRefreshHeader.STATE_norMAL) { mHeaderView.setState(SRefreshHeader.STATE_norMAL); } if (mHeaderView.getVisiableHeight() > mHeaderViewHeight * 2 && mHeaderView.getStatus() != SRefreshHeader.STATE_READY) { mHeaderView.setState(SRefreshHeader.STATE_READY); } } mHeaderView.setVisiableHeight((int) space + mHeaderView.getVisiableHeight()); } public void stopRefresh() { if (mHeaderView.getStatus() == SRefreshHeader.STATE_REFRESHING) { mHeaderView.setState(SRefreshHeader.STATE_norMAL); resetHeadView(SRefreshHeader.STATE_norMAL); reset(SRefreshHeader.STATE_norMAL); } } public void setonRefreshListener(OnRefreshListener onRefreshListener) { this.mOnRefreshListener = onRefreshListener; } }
public class SRefreshHeader extends LinearLayout { private LinearLayout mContainer; private int mState = STATE_norMAL; private Animation mRotateUpAnim; private Animation mRotateDownAnim; private final int ROTATE_ANIM_DURATION = 500; public final static int STATE_norMAL = 0;//下拉刷新 public final static int STATE_READY = 1;//释放刷新 public final static int STATE_REFRESHING = 2;//刷新中 private ImageView mHeadArrowImage; private TextView mHeadLastRefreashTimeTxt; private TextView mHeadHintTxt; private TextView mHeadLastRefreashTxt; private ProgressBar mRefreshingProgress; public SRefreshHeader(Context context) { super(context); initView(context); } /** * @param context * @param attrs */ public SRefreshHeader(Context context,attrs); initView(context); } private void initView(Context context) { // 初始情况,设置下拉刷新view高度为0 LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT,0); mContainer = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.listview_head_view_layout,null); addView(mContainer,lp); setGravity(Gravity.BottOM); mHeadArrowImage = (ImageView) findViewById(R.id.slistview_header_arrow); mHeadLastRefreashTimeTxt = (TextView) findViewById(R.id.slistview_header_time); mHeadHintTxt = (TextView) findViewById(R.id.slistview_header_hint_text); mHeadLastRefreashTxt = (TextView) findViewById(R.id.slistview_header_last_refreash_txt); mRefreshingProgress = (ProgressBar) findViewById(R.id.slistview_header_progressbar); mRotateUpAnim = new RotateAnimation(0.0f,-180.0f,Animation.RELATIVE_TO_SELF,0.5f,0.5f); mRotateUpAnim.setDuration(ROTATE_ANIM_DURATION); mRotateUpAnim.setFillAfter(true); mRotateDownAnim = new RotateAnimation(-180.0f,0.0f,0.5f); mRotateDownAnim.setDuration(ROTATE_ANIM_DURATION); mRotateDownAnim.setFillAfter(true); } public void setState(int state) { if (state == mState) return; if (state == STATE_REFRESHING) { // 显示进度 mHeadArrowImage.clearanimation(); mHeadArrowImage.setVisibility(View.INVISIBLE); mRefreshingProgress.setVisibility(View.VISIBLE); } else { // 显示箭头图片 mHeadArrowImage.setVisibility(View.VISIBLE); mRefreshingProgress.setVisibility(View.INVISIBLE); } switch (state) { case STATE_norMAL: if (mState == STATE_READY) { mHeadArrowImage.startAnimation(mRotateDownAnim); } if (mState == STATE_REFRESHING) { mHeadArrowImage.clearanimation(); } mHeadHintTxt.setText("下拉刷新"); break; case STATE_READY: if (mState != STATE_READY) { mHeadArrowImage.clearanimation(); mHeadArrowImage.startAnimation(mRotateUpAnim); mHeadHintTxt.setText("松开刷新"); } break; case STATE_REFRESHING: mHeadHintTxt.setText("正在刷新"); break; default: } mState = state; } public void setVisiableHeight(int height) { if (height < 0) height = 0; LayoutParams lp = (LayoutParams) mContainer .getLayoutParams(); lp.height = height; mContainer.setLayoutParams(lp); } public int getStatus() { return mState; } public int getVisiableHeight() { return mContainer.getHeight(); } }
最后是布局文件
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="bottom"> <RelativeLayout android:id="@+id/slistview_header_content" android:layout_width="match_parent" android:layout_height="60dp"> <LinearLayout android:id="@+id/slistview_header_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:gravity="center" android:orientation="vertical"> <TextView android:id="@+id/slistview_header_hint_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="下拉刷新" /> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="3dp"> <TextView android:id="@+id/slistview_header_last_refreash_txt" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="上次刷新时间" android:textSize="12sp" /> <TextView android:id="@+id/slistview_header_time" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="12sp" /> </LinearLayout> </LinearLayout> <ProgressBar android:id="@+id/slistview_header_progressbar" android:layout_width="30dp" android:layout_height="30dp" android:layout_centerVertical="true" android:layout_toLeftOf="@id/slistview_header_text" android:visibility="invisible" /> <ImageView android:id="@+id/slistview_header_arrow" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignLeft="@id/slistview_header_progressbar" android:layout_centerVertical="true" android:layout_toLeftOf="@id/slistview_header_text" android:src="@drawable/mmtlistview_arrow" /> </RelativeLayout> </LinearLayout>
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持编程小技巧。
Android SwipeRefreshLayout
SwipeRefreshLayout字面意思就是下拉刷新的布局,继承自ViewGroup,在support v4兼容包下,但必须把你的support library的版本升级到19.1。 提到下拉刷新大家一定对ActionBarPullToRefresh比较熟悉,而如今google推出了更官方的下拉刷新组件,这无疑是对开发者来说比较好的消息。利用这个组件可以很方便的实现Google Now的刷新效果,见下图:
主要方法
setOnRefreshListener(OnRefreshListener): 为布局添加一个Listener
setRefreshing(boolean): 显示或隐藏刷新进度条
isRefreshing(): 检查是否处于刷新状态
setColorScheme(): 设置进度条的颜色主题,最多能设置四种
xml布局文件
布局文件很简单,只需要在最外层加上SwipeRefreshLayout,然后他的child是可滚动的view即可,如ScrollView或者ListView。如:
<android.support.v4.widget.SwipeRefreshLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/swipe_container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:text="@string/hello_world"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:gravity="center"/>
</ScrollView>
</android.support.v4.widget.SwipeRefreshLayout>
Activity代码
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
swipeLayout = (SwipeRefreshLayout) findViewById(R.id.swipe_container);
swipeLayout.setOnRefreshListener(this);
swipeLayout.setColorScheme(android.R.color.holo_blue_bright,
android.R.color.holo_green_light,
android.R.color.holo_orange_light,
android.R.color.holo_red_light);
}
public void onRefresh() {
new Handler().postDelayed(new Runnable() {
@Override public void run() {
swipeLayout.setRefreshing(false);
}
}, 5000);
}
上面的代码很简单,只需要给SwipeRefreshLayout添加一个listener,值得说明的是setColorScheme方法是设置刷新进度条的颜色,最多只能设置4种循环显示,默认第一个是随用户手势加载的颜色进度条。
源码
写了的小demo在github上,地址在:SwipeRefreshLayoutDemo
总结
google在不断完善自己的sdk,推出越来越多的组件,其目的是让开发更简单,设计上更统一,这可能是google未来的方向,不管怎样,这对开发者来说无疑是非常好的消息。
android SwipeRefreshLayout 与 ListView 结合实现下拉刷新,加载功能
package com.example.listviewdemo;
import java.util.ArrayList;
import java.util.List;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.app.Activity;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v4.widget.SwipeRefreshLayout.OnRefreshListener;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;
public class MainActivity extends Activity implements OnRefreshListener,OnScrollListener{
private SwipeRefreshLayout swipeRefreshLayout;
private ListView listView;
private List<String> list = new ArrayList<String>();
private ArrayAdapter<String> adapter ;
private View footer;
private boolean isfinsh = true;//是否加载完成
private Handler handler = new Handler(){
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case 1:
list.add("bbbbbbbbb1");
adapter.notifyDataSetChanged();
if(listView.getFooterViewsCount()>0){
listView.removeFooterView(footer);
}
isfinsh = true;
break;
default:
break;
}
};
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
swipeRefreshLayout = (SwipeRefreshLayout)findViewById(R.id.rf);
swipeRefreshLayout.setColorScheme(android.R.color.holo_blue_bright, android.R.color.holo_green_light,
android.R.color.holo_orange_light, android.R.color.holo_red_light);
swipeRefreshLayout.setOnRefreshListener(this);
listView = (ListView)findViewById(R.id.ls);
footer = LayoutInflater.from(this).inflate(R.layout.footer, null, false);
for(int i = 0 ;i<20;i++){
list.add("aaaaaaaaa"+i);
}
adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, list);
listView.addFooterView(footer);
listView.setAdapter(adapter);
listView.removeFooterView(footer);
listView.setOnScrollListener(this);
}
@Override
public void onRefresh() {
if(isfinsh){
isfinsh = false;
new ReTask().execute();
}else{
swipeRefreshLayout.setRefreshing(false);
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
int countIndex = view.getLastVisiblePosition();
Log.d("countIndex", ""+countIndex);
Log.d("totalItemCount", ""+totalItemCount);
if((countIndex+1) == totalItemCount){
if(totalItemCount > 0){
if(isfinsh){
isfinsh = false;
listView.addFooterView(footer);
new GetTask().execute();
}else{
}
}
}
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
private class ReTask extends AsyncTask<Void , Integer, Boolean>{
@Override
protected Boolean doInBackground(Void... arg0) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return true;
}
@Override
protected void onPostExecute(Boolean result) {
super.onPostExecute(result);
if(result){
list.clear();
for(int i = 0 ;i<20;i++){
list.add("aaaaaaaaa"+i);
}
adapter.notifyDataSetChanged();
swipeRefreshLayout.setRefreshing(false);
isfinsh = true;
}
}
}
private class GetTask extends AsyncTask<Void , Integer, Boolean>{
@Override
protected Boolean doInBackground(Void... arg0) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return true;
}
@Override
protected void onPostExecute(Boolean result) {
super.onPostExecute(result);
if(result)
handler.sendEmptyMessage(1);
}
}
}
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" >
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/rf"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<ListView
android:id="@+id/ls"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="none"
></ListView>
</android.support.v4.widget.SwipeRefreshLayout>
</RelativeLayout>
footer.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="horizontal"
android:gravity="center_horizontal"
android:padding="8dp"
>
<ProgressBar android:id="@+id/c81_forthBar"
android:layout_width="24dp"
android:layout_height="24dp"
/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="数据正在加载..."
/>
</LinearLayout>
注意在进行 setAdapter 时需 addFooterView,添加完再 removeFooterView,否则会报错
关于android组件SwipeRefreshLayout下拉小球式刷新效果和android下拉刷新框架的介绍现已完结,谢谢您的耐心阅读,如果想了解更多关于Android PullToRefreshLayout下拉刷新控件的终结者、Android RefreshLayout实现下拉刷新布局、Android SwipeRefreshLayout、android SwipeRefreshLayout 与 ListView 结合实现下拉刷新,加载功能的相关知识,请在本站寻找。
本文标签: