Android 下拉/上拉刷新/左滑删除控件

记录一个最近整理的listView列表包含下拉/上拉刷新/左滑删除为一体的控件

项目中经常使用listView的下拉刷新,上拉加载更多,以及左滑删除更多
但是因为时间关系从来没有把这个控件好好整理下,异常边界处理不够好
这次根据自己项目的实际需要整理了一下

下拉刷新采用的是SwipeRefreshLayout
上拉加载更多逻辑是自己写的
滑动删除采用的是别人开源的控件左滑控件这个控件不会改变你任何Adapter中的代码

代码说明

  • SListView当数据加载完成后需要调用listView.loadDataOver(true, true);方法(不论失败成功),第一个参数表示当前从服务器获取数据是否成功,第二个参数是当前是否获取到数据
  • SListView 上拉加载footerView显示出来的前提是listView的数据超过手机一屏幕,否则永远不会显示出来
  • SListView上拉加载会有几个状态,第一个加载中,第二个加载完成没有更多数据(这种表示正常加载数据后服务器没有更多的新数据,此时footerView会展示暂无更多数据字样,同时会限制5分钟内再次执行上拉操作无效),第三个加载异常(这种状态一般是服务器获取数据失败或者其他异常造成的失败,此时footerView会展示点击加载更多字样,此时再次执行上拉操作是没有反应的,必须用户手动去点击footerView才会加载)
  • SListView的item使用了左滑控件在SListView onTouch中有部分代码是针对体验做的限制,比如item左滑打开时此时执行任何如下拉刷新、上拉刷新、点击其他item、点击界面空白都会让左滑打开的item关闭且事件不生效(具体见qq中item左滑打开时)
  • 左滑控件item使用这个滑动控件时adapter代码是不需要改变,但是唯一需要注意的是不要使用convertView设置整个item的点击事件,控件要求的,如果要设置整个item的点击事件请在item布局中命个id另外设置

代码–copy都可以使用

@Override
protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
//上拉加载更多
listView.setUpPullRefreshListener(new SListView.IUpPullRefreshListener() {
            @Override
            public void upPullRefresh() {
                pageNo++;
                loadListData();
            }
        });
//下拉刷新
swipeLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
                swipeLayout.setRefreshing(true);
                pageNo=1;
                loadListData();

            }
        });
 //传递SwipeRefreshLayout实例而已
 listView.setSlidingRestriction(SwipeLayout);
 }       
 public void loadListData(){
     ........................
      //当数据加载完成后调用此方法具体见下面代码
     listView.loadDataOver(true, true);
     swipeLayout.setRefreshing(false);
     adapter.notifyDataSetChanged();
 }
//界面布局activity_main

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:imageText="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#ffffff"
    android:orientation="vertical">

    <android.support.v4.widget.SwipeRefreshLayout
        android:id="@+id/swipeLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <com.cx.wdiget.listView.SListView
            android:id="@+id/listView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:divider="@null" />

    android.support.v4.widget.SwipeRefreshLayout>
RelativeLayout>
//item布局代码
//your_content 这里面是自定义的布局,宽度必须使用match_parent,外面就是滑动展示的布局

<com.mcxtzhang.swipemenulib.SwipeMenuLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="50dp"
    android:clickable="true">

    <LinearLayout
        android:id="@+id/your_content"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <TextView
            android:id="@+id/tv_name"
            android:layout_width="match_parent"
            android:layout_height="49dp"
            android:background="@color/white"
            android:gravity="center"
            android:text="11111111111111111111111111"
            android:textColor="@color/color_333333"
            android:textSize="@dimen/dimen_18sp" />
        <View
            android:layout_width="match_parent"
            android:layout_height="@dimen/dimen_0.5dp"
            android:background="#00c0c7" />
    LinearLayout>
    <TextView
        android:id="@+id/tv_delete"
        android:layout_width="100dp"
        android:layout_height="match_parent"
        android:background="#ff0000"
        android:gravity="center"
        android:text="删 除"
        android:textColor="@android:color/white"
        android:textSize="25sp" />
com.mcxtzhang.swipemenulib.SwipeMenuLayout>
//自定义listView类
package com.cx.wdiget.listView;

import android.content.Context;
import android.support.v4.widget.SwipeRefreshLayout;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.widget.AbsListView;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;

import com.cx.wdiget.R;
import com.mcxtzhang.swipemenulib.SwipeMenuLayout;

/**
 * 自定义listView下拉和上拉刷新
 * 

* 下拉刷新采用SwipeRefreshLayout *

* 上拉刷新自定义实现 *

* listView中item左滑删除采用这个第三方{@link SwipeMenuLayout}控件 */ public class SListView extends ListView implements AbsListView.OnScrollListener { private LayoutInflater inflate; private View footerRootView; private ProgressBar pb_footerProgressBar; private TextView tv_footerHint; private IUpPullRefreshListener iUpPullRefreshListener; private IDownPullRefreshListener iDownPullRefreshListener; /** * 上拉加载更多状态码 *

* UP_LOADING 加载中 *

* UP_LOADING_DONE 加载完成 *

* UP_LOADING_NO_MORE 加载完成没有更多新数据,数据加载成功没有下一页数据时显示 *

* UP_LOADING_ERROR 加载完成异常(无网络、获取数据失败等) */ private int upLoadingState = 0; private final int UP_LOADING = 1; private final int UP_LOADING_DONE = 2; private final int UP_LOADING_NO_MORE = 3; private final int UP_LOADING_ERROR = 4; /**当前item是否超过手机一屏幕,超过才把footerView预加载出来**/ private boolean moreThanOneScreen = false; /**当前item是否是滑动到屏幕最后一条**/ private boolean scrollLastItem = false; /**使用了{@link SwipeMenuLayout}控件后,是否有item滑动开,false关闭 true 打开**/ private boolean swipeMenuLayoutOpen = false; /**当上拉加载,没有更多数据时,记录加载时间,做一个几分钟内再次执行上拉不加载数据的限制**/ private long upNoMoreDataStartTime = 0; /**swipeRefreshLayout不为null表示使用他来做下拉刷新**/ private SwipeRefreshLayout swipeRefreshLayout; public SListView(Context context) { this(context, null); } public SListView(Context context, AttributeSet attrs) { super(context, attrs); initView(context); } /** * 初始化操作 * * @param context context */ private void initView(Context context) { inflate = LayoutInflater.from(context); this.setOnScrollListener(this); } /** * listView 初始化FooterView */ private void initFooterView() { footerRootView = inflate.inflate(R.layout.item_down_pull_refresh_footer, null); pb_footerProgressBar = (ProgressBar) footerRootView.findViewById(R.id.pb_footerProgressBar); tv_footerHint = (TextView) footerRootView.findViewById(R.id.tv_footerHint); footerRootView.setVisibility(View.GONE); footerRootView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { upPullLoadingData(true); } }); addFooterView(footerRootView); } /** * * on touch event * * @param event event * @return */ @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: swipeMenuLayoutOpen = (SwipeMenuLayout.getViewCache() != null); //当左滑打开时不能执行任何非当前item的事件 if (swipeMenuLayoutOpen) { if(swipeRefreshLayout!=null){ swipeRefreshLayout.setEnabled(false); } this.requestDisallowInterceptTouchEvent(swipeMenuLayoutOpen); SwipeMenuLayout.getViewCache().smoothClose(); } break; case MotionEvent.ACTION_MOVE: if (swipeRefreshLayout != null) { swipeRefreshLayout.setEnabled(!swipeMenuLayoutOpen); } this.requestDisallowInterceptTouchEvent(swipeMenuLayoutOpen); break; case MotionEvent.ACTION_UP: swipeMenuLayoutOpen = false; break; case MotionEvent.ACTION_CANCEL: swipeMenuLayoutOpen = false; break; default: break; } return swipeMenuLayoutOpen || super.onTouchEvent(event); } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { if (scrollState == SCROLL_STATE_IDLE && scrollLastItem) { upPullLoadingData(false); } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { moreThanOneScreen = (totalItemCount > visibleItemCount); scrollLastItem = (firstVisibleItem + visibleItemCount == totalItemCount); //满足条件先把footerRootView显示出来,滑动到底部时不会造成footerRootView显示的延迟 //iUpPullRefreshListener表示用户允许执行上拉加载 //moreThanOneScreen当列表数据超过一屏幕时才展示 if (iUpPullRefreshListener != null && footerRootView != null) { footerRootView.setVisibility(moreThanOneScreen ? View.VISIBLE : View.GONE); } } /** * 上拉刷新状态的改变,改变样式 */ private void changeFooterViewByState() { if (iUpPullRefreshListener == null || footerRootView == null) { return; } switch (upLoadingState) { case UP_LOADING_NO_MORE: pb_footerProgressBar.setVisibility(View.GONE); tv_footerHint.setText("没有更多了"); if (upNoMoreDataStartTime <= 0) { upNoMoreDataStartTime = System.currentTimeMillis(); } break; case UP_LOADING_ERROR: pb_footerProgressBar.setVisibility(View.GONE); tv_footerHint.setText("点击加载更多"); upNoMoreDataStartTime = 0; break; case UP_LOADING: pb_footerProgressBar.setVisibility(View.VISIBLE); tv_footerHint.setText("加载中..."); upNoMoreDataStartTime = 0; break; default: break; } } /** * 执行上拉加载数据 */ private void upPullLoadingData(boolean clickFooterView) { if (iUpPullRefreshListener != null && upLoadingState != UP_LOADING && moreThanOneScreen) { if (!AndroidUtils.isNetworkAvailable()) { AndroidUtils.toast("网络不给力,请检查后重试"); upLoadingState = UP_LOADING_ERROR; changeFooterViewByState(); return; } if (upLoadingState == UP_LOADING_NO_MORE && !againLoadingNoMoreData()) { return; } if (upLoadingState == UP_LOADING_ERROR && !clickFooterView) { return; } upLoadingState = UP_LOADING; changeFooterViewByState(); iUpPullRefreshListener.upPullRefresh(); } } /** * 超过五分钟才能再次执行上拉加载 *

* 当上拉加载状态为UP_LOADING_NO_MORE时,再次执行上拉加载时有时间限制 *

* 超过限制时间后才能再次执行上拉操作 * * @return true 可以加载 false不可以加载 */ private boolean againLoadingNoMoreData() { return (System.currentTimeMillis() - upNoMoreDataStartTime > (5 * 60 * 1000)); } /** * 数据加载完成后调用 * * @param loadSuccess 加载数据是否成功 * @param nextPage 是否还有下一页数据 */ public void loadDataOver(boolean loadSuccess, boolean nextPage) { Log.v("tag", "loadDataOver"); if (loadSuccess) { if (!nextPage) { upLoadingState = UP_LOADING_NO_MORE; changeFooterViewByState(); } else { upLoadingState = UP_LOADING_DONE; } } else { upLoadingState = UP_LOADING_ERROR; changeFooterViewByState(); } } /** * 左滑删除限制 *

* 使用{@link SwipeMenuLayout} *

* 当item滑动开时,不允许执行任何事件({@link #onTouchEvent(MotionEvent)}) */ public void setSlidingRestriction(SwipeRefreshLayout swipeRefreshLayout) { this.swipeRefreshLayout = swipeRefreshLayout; } public void setUpPullRefreshListener(IUpPullRefreshListener iUpPullRefreshListener) { if (iUpPullRefreshListener != null) { this.iUpPullRefreshListener = iUpPullRefreshListener; initFooterView(); } } public void setDownPullRefreshListener(IDownPullRefreshListener iDownPullRefreshListener) { this.iDownPullRefreshListener = iDownPullRefreshListener; } public interface IUpPullRefreshListener { void upPullRefresh(); } public interface IDownPullRefreshListener { void downPullRefresh(); } }

//item_down_pull_refresh_footer

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="50dp"
    android:background="@color/color_ffffff"
    android:gravity="center"
    android:orientation="horizontal"
    android:padding="@dimen/dimen_15dp">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:orientation="horizontal">

        <ProgressBar
            android:id="@+id/pb_footerProgressBar"
            style="?android:attr/progressBarStyleSmall"
            android:layout_width="@dimen/dimen_15dp"
            android:layout_height="@dimen/dimen_15dp"
            android:indeterminate="true"
            android:indeterminateDrawable="@drawable/up_pull_footer_progressbar"
            android:visibility="visible" />

        <TextView
            android:id="@+id/tv_footerHint"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="@dimen/dimen_10dp"
            android:gravity="center"
            android:text="@string/p2refresh_head_load_more"
            android:textColor="#00c0c7"
            android:textSize="@dimen/dimen_14sp" />
    LinearLayout>

LinearLayout>

你可能感兴趣的:(Android,上拉刷新,下拉刷新,滑动删除,Android)