【Android - 自定义View】之自定义可下拉刷新或上拉加载的ListView
首先来介绍一下这个自定义View:
- (1)这个自定义View的名称叫做 RefreshableListView ,继承自ListView类;
- (2)在这个自定义View中,用户可以设置是否支持下拉刷新或上拉加载,当然也可以设置为都支持或都不支持;
- (3)在这个自定义View中设置了下拉刷新和上拉加载的回调方法,用户可以自己编写下拉刷新和上拉加载的业务代码。
接下来简单介绍一下这个自定义View中用到的技术点:
- (1)为ListView添加头部和底部布局,分别调用addHeaderView() 和addFooterView() 方法;
- (2)通过ListView的setPadding() 方法设置隐藏ListView的头部和底部布局;
- (3)通过ListView的 post() 方法将头部布局和底部布局的测量操作post到ListView加载完成后进行;
- (4)实现 OnScrollListener 接口,在 onScroll() 和 onScrollStateChanged() 方法中判断当前位置是否可以进行上拉或下拉操作;
- (5)重写 onTouchEvent() 方法,通过手势操作头部布局和底部布局进行下拉刷新和上拉加载操作;
- (6)使用 ObjectAnimator 属性动画进行头部布局中箭头的反转操作;
- (7)定义了回调接口,让用户自己编写下拉刷新和上拉加载的业务代码;
- (8)设置了两个标志位 isRefreshEnabled 和 isLoadEnabled ,让用户自己控制是否可以下拉刷新或上拉加载。
下面是这个自定义View—— RefreshableListView 中的代码:
import android.animation.ObjectAnimator; import android.content.Context; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.widget.AbsListView; import android.widget.ImageView; import android.widget.ListView; import android.widget.ProgressBar; import android.widget.TextView; /** * 自定义的可下拉刷新或上拉加载的ListView */ public class RefreshableListView extends ListView implements AbsListView.OnScrollListener { private static final int STATE_NORMAL = 0x000; // 正常状态(没有显示头部布局) private static final int STATE_PULLING = 0x001; // 正在下拉或上拉,但没有达到刷新或加载的要求的状态 private static final int STATE_PREPARED = 0x002; // 达到刷新或加载的要求,松开手指就可以刷新或加载的状态 private static final int STATE_REFRESHING = 0x003; // 正在刷新或加载的状态 private View headerView; // 顶部布局 private ImageView arrow; // 顶部布局中的箭头 private ProgressBar headerProgress; // 顶部布局中的进度条 private TextView headerTip; // 顶部布局中的提示信息 private int headerHeight; // 头部布局的高度 private boolean isRefreshEnabled; // 是否允许下拉刷新 private boolean isRefreshable; // 是否可以下拉刷新 private ProgressBar footerProgress; // 底部布局中的进度条 private TextView footerTip; // 底部布局中的提示信息 private int footerHeight; // 底部布局的高度 private boolean isLoadEnabled; // 是否允许上拉加载 private boolean isLoadable; // 是否可以上拉加载 private int firstItemIndex; // 第一个可见Item的下标 private int visibleItemCount; // 页面中可见的Item的个数 private int totalItemCount; // ListView中加载的Item的总个数 private int firstItemTopPadding; // 第一个Item的top值 private int startY; // 记录手指按下时的Y坐标位置 private int offsetY; // 记录手指拖动过程中Y坐标的偏移量 private int rotateTime; // 旋转次数,用于控制箭头只旋转一次 private boolean isScrollIdle; // 滑动动作是否是停止的 private OnRefreshListener onRefreshListener; public RefreshableListView(Context context) { super(context); initView(context); } public RefreshableListView(Context context, AttributeSet attrs) { super(context, attrs); initView(context); } public RefreshableListView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initView(context); } /** * 初始化界面,添加顶部布局文件到ListView中 */ private void initView(Context context) { // 初始化头部布局及布局中的控件 headerView = LayoutInflater.from(context).inflate(R.layout.sideworks_rlv_header, this, false); arrow = (ImageView) headerView.findViewById(R.id.rlv_header_iv_arrow); headerProgress = (ProgressBar) headerView.findViewById(R.id.rlv_header_progress_progressbar); headerTip = (TextView) headerView.findViewById(R.id.rlv_header_tv_tip); // 初始化底部布局及布局中的控件 View footerView = LayoutInflater.from(context).inflate(R.layout.sideworks_rlv_footer, this, false); footerProgress = (ProgressBar) footerView.findViewById(R.id.rlv_footer_progress_progressbar); footerTip = (TextView) footerView.findViewById(R.id.rlv_footer_tv_tip); // 此时视图刚刚开始初始化,如果直接获取测量值会返回0,因此需要将这个操作post到初始化之后进行 this.post(new Runnable() { @Override public void run() { headerHeight = headerView.getMeasuredHeight(); // 布局文件中,头部布局100dp,底部布局60dp,因此偷个懒,用*0.6的方法得到底部布局的高度 footerHeight = (int) (headerHeight * 0.6); setViewPadding(-headerHeight, -footerHeight); } }); this.addHeaderView(headerView); this.addFooterView(footerView); this.setOnScrollListener(this); } /** * 设置RefreshableListView的上下边距(用于隐藏头部和底部布局) */ private void setViewPadding(int topPadding, int bottomPadding) { this.setPadding(headerView.getPaddingLeft(), topPadding, headerView.getPaddingRight(), bottomPadding); } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { this.firstItemIndex = firstVisibleItem; this.visibleItemCount = visibleItemCount; this.totalItemCount = totalItemCount; View firstView = this.getChildAt(firstVisibleItem); if (firstView != null) { this.firstItemTopPadding = firstView.getTop(); } } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { isScrollIdle = scrollState == OnScrollListener.SCROLL_STATE_IDLE; } /** * 监听手指操作的事件(按下、滑动、抬起) */ @Override public boolean onTouchEvent(MotionEvent ev) { switch (ev.getAction()) { // 手指按下时,判断是否可以下拉刷新或上拉加载 case MotionEvent.ACTION_DOWN: isRefreshable = false; if (isRefreshEnabled && firstItemIndex == 0 && firstItemTopPadding == -headerHeight) { isRefreshable = true; } else if (isLoadEnabled && isScrollIdle && firstItemIndex + visibleItemCount == totalItemCount) { isLoadable = true; } startY = (int) ev.getY(); break; // 手指移动时,判断是否在下拉刷新或上拉加载,如果是,则动态改变头部布局或底部布局的状态 case MotionEvent.ACTION_MOVE: offsetY = (int) ev.getY() - startY; if (isRefreshEnabled && isRefreshable && offsetY > 0) { setViewPadding(-headerHeight + offsetY, -footerHeight); if (offsetY >= headerHeight) { setCurrentState(STATE_PREPARED); } else { setCurrentState(STATE_PULLING); } } else if (isLoadEnabled && isLoadable && offsetY < 0) { setViewPadding(-headerHeight, -footerHeight - offsetY); if (offsetY <= -footerHeight) { setCurrentState(STATE_PREPARED); } else { setCurrentState(STATE_PULLING); } } break; // 手指抬起时,判断是否下拉或上拉到可以刷新或加载的程度,如果达到程度,则进行刷新或加载 case MotionEvent.ACTION_UP: if (isRefreshEnabled && isRefreshable && offsetY > 0) { if (offsetY <= headerHeight) { setViewPadding(-headerHeight, -footerHeight); setCurrentState(STATE_NORMAL); } else { setViewPadding(0, -footerHeight); setCurrentState(STATE_REFRESHING); onRefreshListener.onRefreshing(); // 调用接口的回调方法 } } else if (isLoadEnabled && isLoadable && offsetY < 0) { if (offsetY >= -footerHeight) { setViewPadding(-headerHeight, -footerHeight); setCurrentState(STATE_NORMAL); } else { setViewPadding(-headerHeight, 0); setCurrentState(STATE_REFRESHING); onRefreshListener.onLoading(); // 调用接口的回调方法 } } isRefreshable = false; isLoadable = false; break; } return super.onTouchEvent(ev); } /** * 根据当前的状态进行相应的处理 */ private void setCurrentState(int state) { switch (state) { // 普通状态:头部布局和尾部布局都隐藏,头部布局中显示箭头不显示进度条,底部布局中不显示进度条 case STATE_NORMAL: headerProgress.setVisibility(View.GONE); arrow.setVisibility(View.VISIBLE); footerProgress.setVisibility(View.GONE); break; // 正在下拉后上拉,但没有达到刷新或加载的要求的状态: // 如果是下拉,则将头部布局中的箭头指向调整为指下,同时改变文本; // 如果是上拉,则改变文本 case STATE_PULLING: if (isRefreshEnabled && isRefreshable) { if (rotateTime == 1) { ObjectAnimator toUpAnim = ObjectAnimator.ofFloat(arrow, "rotation", 180f, 0f); toUpAnim.setDuration(200); toUpAnim.start(); rotateTime--; } headerTip.setText("下拉可以刷新"); } else if (isLoadEnabled && isLoadable) { footerTip.setText("上拉加载更多"); } break; // 下拉或上拉达到刷新或加载的条件,但还没有松手的状态: // 如果是下拉,则将头部布局中的箭头指向调整为指上,同时改变文本; // 如果是上拉,则改变文本 case STATE_PREPARED: if (isRefreshEnabled && isRefreshable) { if (rotateTime == 0) { ObjectAnimator toUpAnim = ObjectAnimator.ofFloat(arrow, "rotation", 0f, 180f); toUpAnim.setDuration(200); toUpAnim.start(); rotateTime++; } headerTip.setText("松开手指刷新"); } else if (isLoadEnabled && isLoadable) { footerTip.setText("松开手指加载"); } break; // 正在刷新或加载的状态: // 如果是下拉,则隐藏头部布局中的箭头,显示头部布局中的进度条,改变文本; // 如果是上拉,则显示底部布局中的进度条,改变文本 case STATE_REFRESHING: if (isRefreshEnabled && isRefreshable) { arrow.setVisibility(View.GONE); headerProgress.setVisibility(View.VISIBLE); if (rotateTime == 1) { ObjectAnimator toUpAnim = ObjectAnimator.ofFloat(arrow, "rotation", 180f, 0f); toUpAnim.setDuration(200); toUpAnim.start(); rotateTime--; } headerTip.setText("正在刷新......"); } else if (isLoadEnabled && isLoadable) { footerProgress.setVisibility(View.VISIBLE); footerTip.setText("正在加载......"); } break; } } /** * 刷新结束后必须调用这个方法来重置一些参数 */ public void onRefreshComplete() { setViewPadding(-headerHeight, -footerHeight); setCurrentState(STATE_NORMAL); } /** * 设置是否允许下拉刷新和上拉加载 */ public void setEnables(boolean isRefreshEnabled, boolean isLoadEnabled) { this.isRefreshEnabled = isRefreshEnabled; this.isLoadEnabled = isLoadEnabled; } /** * 监听下拉刷新的接口 */ interface OnRefreshListener { void onRefreshing(); // 在下拉刷新的时候回调的方法 void onLoading(); // 在上拉加载的时候回调的方法 } public void setOnRefreshListener(OnRefreshListener onRefreshListener) { this.onRefreshListener = onRefreshListener; } }
头部布局文件 sideworks_rlv_header.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="100.0dip" android:background="#DEDEDE" android:gravity="center" android:orientation="horizontal"> <RelativeLayout android:layout_width="wrap_content" android:layout_height="wrap_content"> <ImageView android:id="@+id/rlv_header_iv_arrow" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:contentDescription="@string/app_name" android:src="@mipmap/img_rlv_arrow" /> <ProgressBar android:id="@+id/rlv_header_progress_progressbar" android:layout_width="35.0dip" android:layout_height="35.0dip" android:layout_centerInParent="true" android:visibility="gone" /> </RelativeLayout> <TextView android:id="@+id/rlv_header_tv_tip" android:layout_width="wrap_content" android:layout_height="100.0dip" android:layout_marginLeft="10.0dip" android:gravity="center" android:text="下拉可以刷新" android:textColor="#444444" android:textSize="16.0sp" android:textStyle="bold" /> </LinearLayout>
底部布局文件 sideworks_rlv_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"> <LinearLayout android:id="@+id/rlv_footer_ly_content" android:layout_width="match_parent" android:layout_height="60.0dip" android:background="#DEDEDE" android:gravity="center" android:orientation="horizontal"> <ProgressBar android:id="@+id/rlv_footer_progress_progressbar" android:layout_width="30.0dip" android:layout_height="30.0dip" android:visibility="gone" /> <TextView android:id="@+id/rlv_footer_tv_tip" android:layout_width="wrap_content" android:layout_height="60.0dip" android:layout_marginLeft="10.0dip" android:gravity="center" android:text="上拉加载更多" android:textColor="#444444" android:textSize="16.0sp" android:textStyle="bold" /> </LinearLayout> </LinearLayout>
控件中每一条数据的布局文件 listitem_rlv_listview.xml 中的代码:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="90.0dip" android:background="@android:color/white"> <ImageView android:id="@+id/rlv_listitem_iv_image" android:layout_width="65.0dip" android:layout_height="65.0dip" android:layout_centerVertical="true" android:layout_marginLeft="10.0dip" android:contentDescription="@string/app_name" android:src="@mipmap/img_rlv_listitem_image" /> <TextView android:id="@+id/rlv_listitem_tv_name" android:layout_width="wrap_content" android:layout_height="90.0dip" android:layout_centerVertical="true" android:layout_marginLeft="10.0dip" android:layout_toRightOf="@id/rlv_listitem_iv_image" android:gravity="center" android:textColor="@android:color/black" android:textSize="16.0sp" /> <Button android:id="@+id/rlv_listitem_btn_download" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_centerVertical="true" android:layout_marginRight="10.0dip" android:text="Download" /> </RelativeLayout>
适配器类文件 ListViewAdapter.java 中的代码:
import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.Button; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; import java.util.List; /** * ListView的数据适配器 */ public class ListViewAdapter extends BaseAdapter { private Context context; private List<String> nameList; public ListViewAdapter(Context context, List<String> nameList) { this.context = context; this.nameList = nameList; } @Override public int getCount() { return nameList.size(); } @Override public Object getItem(int position) { return nameList.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(final int position, View convertView, ViewGroup parent) { ViewHolder holder = null; if (convertView == null) { convertView = LayoutInflater.from(context).inflate(R.layout.listitem_rlv_listview, parent, false); holder = new ViewHolder(); holder.image = (ImageView) convertView.findViewById(R.id.rlv_listitem_iv_image); holder.name = (TextView) convertView.findViewById(R.id.rlv_listitem_tv_name); holder.download = (Button) convertView.findViewById(R.id.rlv_listitem_btn_download); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } holder.image.setImageResource(R.mipmap.ic_launcher); holder.name.setText(nameList.get(position)); holder.download.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(context, nameList.get(position) + " Clicked!", Toast.LENGTH_SHORT).show(); } }); return convertView; } private static class ViewHolder { ImageView image; TextView name; Button download; } }
主界面的JAVA文件 MainActivity.java 中的代码:
import android.os.Handler; import android.os.Message; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import java.util.ArrayList; import java.util.List; public class MainActivity extends AppCompatActivity { private RefreshableListView listview; // 自定义ListView控件 private List<String> nameList; // RefreshableListView控件中显示的数据的数据集 private ListViewAdapter adapter; // RefreshableListView控件的适配器 // Handler更新UI界面 private Handler refreshHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { // 下拉刷新的回调,在列表的最前面插入一条数据 case 0: nameList.add(0, "新加入的名称!!"); break; // 上拉加载的回调,在列表的最后添加一条数据 case 1: nameList.add("新加入的名称!!"); break; } // ListView的数据适配器更新数据集 adapter.notifyDataSetChanged(); // 必须调用这个方法,重置头部布局或底部布局的视图 listview.onRefreshComplete(); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } @Override protected void onResume() { super.onResume(); // 通过ID找到我们的自定义控件RefreshableListView listview = (RefreshableListView) this.findViewById(R.id.rlv_lv_listview); // 初始化RefreshableListView控件中显示的数据集 initNameList(); // 创建RefreshableListView的数据适配器 adapter = new ListViewAdapter(MainActivity.this, nameList); // 为RefreshableListView适配数据 listview.setAdapter(adapter); // 设置是否可以下拉刷新或上拉加载,这里设置的是可以下拉刷新,不可以上拉加载 listview.setEnables(true, false); // 设置RefreshableListView的回调 listview.setOnRefreshListener(new RefreshableListView.OnRefreshListener() { // 下拉刷新的回调方法,在这个方法中停留2秒后发送一条消息给Handler @Override public void onRefreshing() { new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } refreshHandler.sendEmptyMessage(0); } }).start(); } // 上拉加载的回调方法,在这个方法中停留2秒后发送一条消息给Handler @Override public void onLoading() { new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } refreshHandler.sendEmptyMessage(1); } }).start(); } }); } /** * 初始化RefreshableListView控件中显示的数据集 */ private void initNameList() { nameList = new ArrayList<>(); for (int i = 0; i < 15; i++) { nameList.add("APP名称" + (i + 1)); } } }
最后上一张效果图(这张效果图是同时支持下拉刷新和上拉加载功能的),如下:
posted on 2017-04-19 20:01 ITGungnir 阅读(3597) 评论(0) 编辑 收藏 举报