Android 类ListView下拉刷新控件实现

Android下拉刷新,在目前好多应用被使用到。

比如微博,下拉刷新更多数据。

一般我们在运用的ListView,本身就实现了下拉获取更多数据。只是这个下拉刷新的操作时在listView拉到底端的监听。


对于ListView刷新,我们可以分为两种情况:

1.获取更多的数据,按服务器数据库时间顺序存储入情况,此刻我们是获取是显示在我们应用中的数据更早前的数据,这也是最常见的情况。

比如(微博获取更多信息,就是获取更多更早前的信息,然后动态的添加到已有的数据的下方);

2.获取更多的最新的数据,其实还是一种获取更多的操作方式。但是这里主要考虑到用户的操作习惯了。一般,用户的操作习惯分这么两种

第一种,获取下一页,第二种,类似于网页的F5刷新,停留在当前页面的刷新。

ListView刷新其实类似于网页。如果没用下拉刷新,那么用户得将ListView拖拉到最后(当然也可以是在界面顶端添加一个刷新按钮控件,但是,对于手机这样界面不是很大,这样的设计其实是不应太多的。),如果数据太多,那么用户要下拉到很下面才能执行刷新。而对于大多数用户习惯,获取更多的最新资讯后,希望他添加的时候是在界面最上面的显眼处的。也就是,用户还是喜欢的是懒操作,在同一个可显示界面完成所有操作。那么,下拉刷新是一个不错的设计。

效果图:正常状态


下拉刷新:

基本效果就是这样。

自定义控件代码

/** * 刷新控制view * * @author Nono * */ public class RefreshableView extends LinearLayout { private static final String TAG = "LILITH"; private Scroller scroller; private View refreshView; private ImageView refreshIndicatorView; private int refreshTargetTop = -60; private ProgressBar bar; private TextView downTextView; private TextView timeTextView; private RefreshListener refreshListener; private String downTextString; private String releaseTextString; private Long refreshTime = null; private int lastX; private int lastY; // 拉动标记 private boolean isDragging = false; // 是否可刷新标记 private boolean isRefreshEnabled = true; // 在刷新中标记 private boolean isRefreshing = false; private Context mContext; public RefreshableView(Context context) { super(context); mContext = context; } public RefreshableView(Context context, AttributeSet attrs) { super(context, attrs); mContext = context; init(); } private void init() { // TODO Auto-generated method stub //滑动对象, scroller = new Scroller(mContext); //刷新视图顶端的的view refreshView = LayoutInflater.from(mContext).inflate(R.layout.refresh_top_item, null); //指示器view refreshIndicatorView = (ImageView) refreshView.findViewById(R.id.indicator); //刷新bar bar = (ProgressBar) refreshView.findViewById(R.id.progress); //下拉显示text downTextView = (TextView) refreshView.findViewById(R.id.refresh_hint); //下来显示时间 timeTextView = (TextView) refreshView.findViewById(R.id.refresh_time); LayoutParams lp = new LinearLayout.LayoutParams(LayoutParams.FILL_PARENT, -refreshTargetTop); lp.topMargin = refreshTargetTop; lp.gravity = Gravity.CENTER; addView(refreshView, lp); downTextString = mContext.getResources().getString(R.string.refresh_down_text); releaseTextString = mContext.getResources().getString(R.string.refresh_release_text); } /** * 刷新 * @param time */ private void setRefreshText(String time) { // TODO Auto-generated method stub //timeTextView.setText(time); } @Override public boolean onTouchEvent(MotionEvent event) { int y= (int) event.getRawY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: //记录下y坐标 lastY = y; break; case MotionEvent.ACTION_MOVE: Log.i(TAG, "ACTION_MOVE"); //y移动坐标 int m = y - lastY; if(((m < 6) && (m > -1)) || (!isDragging )){ doMovement(m); } //记录下此刻y坐标 this.lastY = y; break; case MotionEvent.ACTION_UP: Log.i(TAG, "ACTION_UP"); fling(); break; } return true; } /** * up事件处理 */ private void fling() { // TODO Auto-generated method stub LinearLayout.LayoutParams lp = (LayoutParams) refreshView.getLayoutParams(); Log.i(TAG, "fling()" + lp.topMargin); if(lp.topMargin > 0){//拉到了触发可刷新事件 refresh(); }else{ returnInitState(); } } private void returnInitState() { // TODO Auto-generated method stub LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)this.refreshView.getLayoutParams(); int i = lp.topMargin; scroller.startScroll(0, i, 0, refreshTargetTop); invalidate(); } private void refresh() { // TODO Auto-generated method stub LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)this.refreshView.getLayoutParams(); int i = lp.topMargin; refreshIndicatorView.setVisibility(View.GONE); bar.setVisibility(View.VISIBLE); timeTextView.setVisibility(View.GONE); downTextView.setVisibility(View.GONE); scroller.startScroll(0, i, 0, 0-i); invalidate(); if(refreshListener !=null){ refreshListener.onRefresh(this); isRefreshing = true; } } /** * */ @Override public void computeScroll() { // TODO Auto-generated method stub if(scroller.computeScrollOffset()){ int i = this.scroller.getCurrY(); LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)this.refreshView.getLayoutParams(); int k = Math.max(i, refreshTargetTop); lp.topMargin = k; this.refreshView.setLayoutParams(lp); this.refreshView.invalidate(); invalidate(); } } /** * 下拉move事件处理 * @param moveY */ private void doMovement(int moveY) { // TODO Auto-generated method stub LinearLayout.LayoutParams lp = (LayoutParams) refreshView.getLayoutParams(); if(moveY > 0){ //获取view的上边距 float f1 =lp.topMargin; float f2 = moveY * 0.3F; int i = (int)(f1+f2); //修改上边距 lp.topMargin = i; //修改后刷新 refreshView.setLayoutParams(lp); refreshView.invalidate(); invalidate(); } timeTextView.setVisibility(View.VISIBLE); if(refreshTime!= null){ setRefreshTime(refreshTime); } downTextView.setVisibility(View.VISIBLE); refreshIndicatorView.setVisibility(View.VISIBLE); bar.setVisibility(View.GONE); if(lp.topMargin > 0){ downTextView.setText(R.string.refresh_release_text); refreshIndicatorView.setImageResource(R.drawable.refresh_arrow_up); }else{ downTextView.setText(R.string.refresh_down_text); refreshIndicatorView.setImageResource(R.drawable.refresh_arrow_down); } } public void setRefreshEnabled(boolean b) { this.isRefreshEnabled = b; } public void setRefreshListener(RefreshListener listener) { this.refreshListener = listener; } /** * 刷新时间 * @param refreshTime2 */ private void setRefreshTime(Long time) { // TODO Auto-generated method stub } /** * 结束刷新事件 */ public void finishRefresh(){ Log.i(TAG, "执行了=====finishRefresh"); LinearLayout.LayoutParams lp= (LinearLayout.LayoutParams)this.refreshView.getLayoutParams(); int i = lp.topMargin; refreshIndicatorView.setVisibility(View.VISIBLE); timeTextView.setVisibility(View.VISIBLE); scroller.startScroll(0, i, 0, refreshTargetTop); invalidate(); isRefreshing = false; } /*该方法一般和ontouchEvent 一起用 * (non-Javadoc) * @see android.view.ViewGroup#onInterceptTouchEvent(android.view.MotionEvent) */ @Override public boolean onInterceptTouchEvent(MotionEvent e) { // TODO Auto-generated method stub int action = e.getAction(); int y= (int) e.getRawY(); switch (action) { case MotionEvent.ACTION_DOWN: lastY = y; break; case MotionEvent.ACTION_MOVE: //y移动坐标 int m = y - lastY; //记录下此刻y坐标 this.lastY = y; if(m > 6 && canScroll()){ return true; } break; case MotionEvent.ACTION_UP: break; case MotionEvent.ACTION_CANCEL: break; } return false; } private boolean canScroll() { // TODO Auto-generated method stub View childView; if(getChildCount()>1){ childView = this.getChildAt(1); if(childView instanceof ListView){ int top =((ListView)childView).getChildAt(0).getTop(); int pad =((ListView)childView).getListPaddingTop(); if((Math.abs(top-pad)) < 3&& ((ListView) childView).getFirstVisiblePosition() == 0){ return true; }else{ return false; } }else if(childView instanceof ScrollView){ if(((ScrollView)childView).getScrollY() == 0){ return true; }else{ return false; } } } return false; } /** * 刷新监听接口 * @author Nono * */ public interface RefreshListener{ public void onRefresh(RefreshableView view); } }

此控件自定义实现一个线性布局,内部包含一个第一个子控件,刷新显示的View。

因为对于ListView下拉刷新的例子网上挺多的,上次我朋友也做,说特么弄了一个礼拜发现一个问题,listview里面条目太少时,刷新的view就会显示出来。

我没具体看过那个代码,也不知道到底什么情况。

自个定义的稍微用了点小技巧。即,我将刷新view的topMargin设置为了该view的高度的负数,那么,他就刚好隐藏起来了。

对于上面那个对于条目太少而造成刷新的view显示的bug有所解决。

@Override public boolean onTouchEvent(MotionEvent event) { int y= (int) event.getRawY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: //记录下y坐标 lastY = y; break; case MotionEvent.ACTION_MOVE: Log.i(TAG, "ACTION_MOVE"); //y移动坐标 int m = y - lastY; if(((m < 6) && (m > -1)) || (!isDragging )){ doMovement(m); } //记录下此刻y坐标 this.lastY = y; break; case MotionEvent.ACTION_UP: Log.i(TAG, "ACTION_UP"); fling(); break; } return true; } /*该方法一般和ontouchEvent 一起用 * (non-Javadoc) * @see android.view.ViewGroup#onInterceptTouchEvent(android.view.MotionEvent) */ @Override public boolean onInterceptTouchEvent(MotionEvent e) { // TODO Auto-generated method stub int action = e.getAction(); int y= (int) e.getRawY(); switch (action) { case MotionEvent.ACTION_DOWN: lastY = y; break; case MotionEvent.ACTION_MOVE: //y移动坐标 int m = y - lastY; //记录下此刻y坐标 this.lastY = y; if(m > 6 && canScroll()){ return true; } break; case MotionEvent.ACTION_UP: break; case MotionEvent.ACTION_CANCEL: break; } return false; }
控件使用:

<com.xxx.xxxx.view.RefreshableView android:orientation="vertical" android:id="@+id/refresh_root"
		 android:layout_width="fill_parent" android:layout_height="wrap_content"
 		 xmlns:android="http://schemas.android.com/apk/res/android" >

		<ListView android:id="@+id/def_list" android:layout_width="fill_parent"
		android:scrollbars="none"  android:fadingEdge="none"
		android:layout_height="wrap_content" android:scrollingCache="false">
		</ListView>
		</com.xxxx.xxxxx.view.RefreshableView>


 

即,我的listview是以该控件的第二个子view加入进去。此刻又会遇到一个问题:我在界面执行下拉时,应用如何分辨我是执行的RefreshableView下拉刷新操作,还是listview下拉操作。这个问题一开始挺悲剧的,就是当我的listview中数据挺多,那我手势一网上滑,此刻操作不会出现冲突,因为刷新View监听的下滑事件监听;然后此刻listview滑到了底部,我要回到listview顶部去,就得执行向下滑操作,那么这时的操作就和刷新view中下滑,下拉刷新监听冲突。后来发现了view中两个函数:子控件截取事件函数。具体来说是就是用的点击屏幕,touch事件会先被onInterceptTouchEvent捕捉,子view先做处理,然后根据true和false,才判断是否截取事件交给父View处理。那么我们就可以在onInterceptTouchEvent做了预处理,更具判断listview是佛在最底部,可下滑,来判断是佛让子view来截取处理该touch事件。具体判断看canScroll();同样的考虑到ScrollView类似有listview的情况,也做了处理,因此该控件支持包含listview和scrollview。实现代码基本如上,内部可能还有挺多的bug,但是目前我应用上实现暂没发现。最后感谢网上的可刷新代码demo,以及sina微博可刷新控件代码以及网易应用可刷新控件代码。

 

                
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值