自定义ListView实现下拉刷新,下拉加载的功能
package com.loaderman.myrefreshlistviewdemo; import android.content.Context; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.animation.Animation; import android.view.animation.RotateAnimation; import android.widget.AbsListView; import android.widget.ImageView; import android.widget.ListView; import android.widget.ProgressBar; import android.widget.TextView; import java.text.SimpleDateFormat; import java.util.Date; /** * * 实现步骤: * 1、给ListView添加头布局 * 2、默认让ListView的头布局隐藏起来 * 负的paddingTop的值 * 如何获取头布局的高度 * 3、慢慢的将头布局拖出来 * 获取在ListView中的滑动偏移量--onTouchEvent * 关于起点坐标的获取dispatchTouchEvent * 4、给RefreshListView定义了三种状态 * refreshUi:根据当前的状态刷新控件的显示 * 在状态发生改变的时候来调用此方法即可 * 5、增加了动画效果 * clearAnimation的使用 * 6、处理up的事件 * STATE_PULL_TO_REFRESH的时候up * 隐藏头布局 * STATE_RELEASE_TO_REFRESH的时候up * 显示头布局 * 更新状态--STATE_REFRESHING * 通知观察者去加载数据 * 7、观察者设计模式的使用 * 找出被观察者 * 定义观察者接口,接口中的方法就是观察者感兴趣的事件 * 在被观察中存储观察者的引用 * 在事件发生的时候,通知观察者 * 为什么要用接口而不使用抽象类--单继承,多实现 * 8、由TabDetailPager来通知RefreshListView数据加载完成 * setOnRefreshComplete * 更新状态,隐藏头布局 * 9、设置时间的显示 * 存在sp中 * 10、自定义ProgressBar的效果 * 上拉加载: * 1、添加脚布局,默认隐藏 * 2、增加了滚动监听, * idle,显示最后一个条目的时候,显示脚布局 * 3、通知观察者加载下一页的数据 * 4、加载下一页数据的逻辑 * 将下一页数据的集合添加到上一页数据的集合红,不能new Adapter * 5、TabDetailPager通知ListView下一页数据加载完成 * 重置isLoadingMore * 隐藏脚布局 */ public class RefreshListView extends ListView { public static final int STATE_PULL_TO_REFRESH = 0; public static final int STATE_RELEASE_TO_REFRESH = 1; public static final int STATE_REFRESHING = 2; private int mCurrentState = STATE_PULL_TO_REFRESH;//定义ListView当前的状态 private float startY; private int headerViewHeight; private View headerView; private ImageView ivArrow; private ProgressBar pb; private TextView tvTips; private TextView tvDate; private RotateAnimation downAnimation; private RotateAnimation upAnimation; private View footerView; private int footerViewHeight; public RefreshListView(Context context) { this(context, null); } public RefreshListView(Context context, AttributeSet attrs) { this(context, attrs, -1); } public RefreshListView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initHeaderView(); initAnimation(); initFooterView(); } private void initAnimation() { upAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); upAnimation.setFillAfter(true); upAnimation.setDuration(200); downAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); downAnimation.setFillAfter(true); downAnimation.setDuration(200); } private void initHeaderView() { //头布局越早添加,位于越上边 headerView = View.inflate(getContext(), R.layout.layout_refresh_header, null); ivArrow = (ImageView) headerView.findViewById(R.id.ivArrow); pb = (ProgressBar) headerView.findViewById(R.id.pb); tvTips = (TextView) headerView.findViewById(R.id.tvTips); tvDate = (TextView) headerView.findViewById(R.id.tvDate); String lastUpdateTime = PrefUtils.getString(getContext(), "lastUpdateTime", ""); tvDate.setText(lastUpdateTime); //设置一个控件的高度或者宽度的信息得找LayoutParams //如果设置一个负的padding的值,只能在代码中设置才会其效果 //measure-layout-draw //千万不要在Activity的onCreate方法中获取一个控件的宽度或者高度或者位置信息 //监听视图树 /*headerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { } });*/ //手动测量 headerView.measure(0, 0);//将测量的工作交给系统来完成,我们不参与任何的限制的意见 //获取测量之后的宽度或者高度信息 headerViewHeight = headerView.getMeasuredHeight(); headerView.setPadding(0, -headerViewHeight, 0, 0); this.addHeaderView(headerView); } private boolean isLoadingMore = false; private void initFooterView() { footerView = View.inflate(getContext(), R.layout.layout_refresh_footer, null); footerView.measure(0, 0); footerViewHeight = footerView.getMeasuredHeight(); footerView.setPadding(0, -footerViewHeight, 0, 0); this.addFooterView(footerView); //给ListView增加一个监听 this.setOnScrollListener(new OnScrollListener() { @Override public void onScrollStateChanged(AbsListView view, int scrollState) { int lastVisiblePosition = getLastVisiblePosition(); if (scrollState == SCROLL_STATE_IDLE && lastVisiblePosition == getCount() - 1 && !isLoadingMore) { //System.out.println("到底了..."); Log.i("RefreshListView", "到底了..."); isLoadingMore = true; //将脚布局显示出来 footerView.setPadding(0, 0, 0, 0); //自动滑到脚布局的位置,让脚布局可以一下子就能够看得见 setSelection(getCount() - 1); notifyLoadMore();//通知观察者去加载下一页的数据 } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { } }); } //一旦事件到达了一个控件上,一定,最先,会调用dispatchTouchEvent @Override public boolean dispatchTouchEvent(MotionEvent ev) { int action = ev.getAction(); if (action == MotionEvent.ACTION_DOWN) { startY = ev.getY();//在这个父控件得到事件的时候,就把起点坐标初始化,这样就不会受制于子控件是否消费了事件,起点坐标就会很精确了 } return super.dispatchTouchEvent(ev); } //onTouchEvent的来源: //1、自身拦截 2、子控件回传 @Override public boolean onTouchEvent(MotionEvent ev) { int action = ev.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: startY = ev.getY(); break; case MotionEvent.ACTION_MOVE: if (mCurrentState == STATE_REFRESHING) { break; } float moveY = ev.getY(); float dy = moveY - startY; //什么情况下需要把头布局拖出来 int firstVisiblePosition = getFirstVisiblePosition(); //1、下拉 2、显示的第0个条目是下拉刷新头布局 if (dy > 0 && firstVisiblePosition == 0) { int paddingTop = (int) (dy - headerViewHeight); headerView.setPadding(0, paddingTop, 0, 0); int oldState = mCurrentState; if (paddingTop < 0) { //头布局有一部分没有显示出来 mCurrentState = STATE_PULL_TO_REFRESH; } else { mCurrentState = STATE_RELEASE_TO_REFRESH; } //在状态发生改变的时候才需要刷新UI if (oldState != mCurrentState) { refreshUi(); } return true;//代表消费了事件 } break; case MotionEvent.ACTION_UP: if (mCurrentState == STATE_PULL_TO_REFRESH) { //将头布局隐藏起来 headerView.setPadding(0, -headerViewHeight, 0, 0); } else if (mCurrentState == STATE_RELEASE_TO_REFRESH) { //改变当前的状态,刷新控件 mCurrentState = STATE_REFRESHING; refreshUi(); //将头布局完全显示出来 headerView.setPadding(0, 0, 0, 0); //去重写加载网络上的数据 //tabDetailPager.getDataFromServer(); notifyRefresh(); } break; } return super.onTouchEvent(ev); } public void setOnRefreshComplete(boolean success) { //1、更新当前的状态 mCurrentState = STATE_PULL_TO_REFRESH; pb.setVisibility(View.INVISIBLE); ivArrow.setVisibility(View.VISIBLE); tvTips.setText("下拉刷新"); //2、隐藏头布局 headerView.setPadding(0, -headerViewHeight, 0, 0); if (success) { //更新tvDate的显示 setCurrentDate(); } } private void setCurrentDate() { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); String currentDate = sdf.format(new Date()); tvDate.setText(currentDate); PrefUtils.setString(getContext(), "lastUpdateTime", currentDate); } public void setOnLoadMoreComplete() { isLoadingMore = false; //隐藏脚布局 footerView.setPadding(0, -footerViewHeight, 0, 0); } //定义观察者接口 public interface OnRefreshListener { public void onRefresh(); public void onLoadMore(); } //保存观察者的实例对象 private OnRefreshListener listener; public void setOnRefreshListener(OnRefreshListener listener) { this.listener = listener; } //通知观察者 private void notifyRefresh() { if (listener != null) { listener.onRefresh(); } } private void notifyLoadMore() { if (listener != null) { listener.onLoadMore(); } } /*private TabDetailPager tabDetailPager; public void setTabDetailPager(TabDetailPager tabDetailPager) { this.tabDetailPager = tabDetailPager; }*/ private void refreshUi() { switch (mCurrentState) { case STATE_PULL_TO_REFRESH: pb.setVisibility(View.INVISIBLE);//INVISIBLE会占位,GONE不会占位 ivArrow.setVisibility(View.VISIBLE); tvTips.setText("下拉刷新"); ivArrow.startAnimation(downAnimation); break; case STATE_RELEASE_TO_REFRESH: pb.setVisibility(View.INVISIBLE); ivArrow.setVisibility(View.VISIBLE); ivArrow.startAnimation(upAnimation); tvTips.setText("松开刷新"); break; case STATE_REFRESHING: pb.setVisibility(View.VISIBLE); ivArrow.clearAnimation();//要控制一个控件的可见度的时候,需要先移除之前设置过的动画 ivArrow.setVisibility(View.INVISIBLE); tvTips.setText("正在刷新"); break; } } }
layout_refresh_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="match_parent" android:gravity="center" android:orientation="horizontal" > <ProgressBar android:id="@+id/pb" android:layout_width="wrap_content" android:layout_height="wrap_content" android:indeterminateDrawable="@drawable/shape_progress"/> <TextView android:id="@+id/tvTips" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="正在加载" android:textColor="#F00" android:textSize="16sp"/> </LinearLayout>
layout_refresh_header.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="match_parent" > <FrameLayout android:layout_margin="5dp" android:layout_width="wrap_content" android:layout_height="wrap_content"> <ImageView android:id="@+id/ivArrow" android:layout_gravity="center" android:layout_width="wrap_content" android:src="@drawable/common_listview_headview_red_arrow" android:layout_height="wrap_content"/> <ProgressBar android:id="@+id/pb" android:visibility="invisible" android:indeterminateDrawable="@drawable/shape_progress" android:layout_width="wrap_content" android:layout_height="wrap_content"/> </FrameLayout> <LinearLayout android:layout_margin="5dp" android:gravity="center" android:layout_width="match_parent" android:orientation="vertical" android:layout_height="wrap_content"> <TextView android:layout_width="wrap_content" android:text="下拉刷新" android:id="@+id/tvTips" android:textColor="#F00" android:textSize="16sp" android:layout_height="wrap_content"/> <TextView android:layout_width="wrap_content" android:text="2016-12-17" android:id="@+id/tvDate" android:textColor="#ccc" android:textSize="12sp" android:layout_height="wrap_content"/> </LinearLayout> </LinearLayout>
shape_progress.xml
<?xml version="1.0" encoding="utf-8"?> <rotate xmlns:android="http://schemas.android.com/apk/res/android" android:fromDegrees="0" android:toDegrees="720" android:pivotY="50%" android:pivotX="50%" > <shape android:innerRadius="15dp" android:shape="ring" android:thickness="3dp" android:useLevel="false" > <!--<solid android:color="@android:"--> <gradient android:startColor="#f00" android:centerColor="#af00" android:endColor="#fff" /> </shape> </rotate>
package com.loaderman.myrefreshlistviewdemo; import android.content.Context; import android.content.SharedPreferences; /** * 关于SharedPreference的工具类 */ public class PrefUtils { public static String getString(Context context,String key,String defValue) { SharedPreferences sp = context.getSharedPreferences("config", Context.MODE_PRIVATE); String retString = sp.getString(key, defValue); return retString; } public static void setString(Context context,String key ,String value) { SharedPreferences sp = context.getSharedPreferences("config", Context.MODE_PRIVATE); SharedPreferences.Editor edit = sp.edit(); edit.putString(key, value); edit.commit(); } }
代码使用:
package com.loaderman.myrefreshlistviewdemo; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.BaseAdapter; import android.widget.TextView; import android.widget.Toast; import java.util.ArrayList; import java.util.Random; public class MainActivity extends AppCompatActivity implements RefreshListView.OnRefreshListener { private RefreshListView lvListNews; private ArrayList mList; private MyListAdapter myListAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mList = new ArrayList<>(); for (int i = 0; i < 30; i++) { mList.add("我是天才" + i + "号"); } lvListNews = (RefreshListView) findViewById(R.id.lvListNews); lvListNews.setOnRefreshListener(this); lvListNews.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { } }); myListAdapter = new MyListAdapter(); lvListNews.setAdapter(myListAdapter); } //下拉刷新 @Override public void onRefresh() { final Random random = new Random(); mList.add(0, "我是天才" + random.nextInt(100) + "号"); Toast.makeText(MainActivity.this, "刷新了一条数据", Toast.LENGTH_SHORT).show(); //刷新完成 lvListNews.setOnRefreshComplete(true); myListAdapter.notifyDataSetChanged(); } //上拉加载 @Override public void onLoadMore() { // 添加数据 for (int i = 30; i < 35; i++) { mList.add("我是天才" + i+ "号"); // 这里要放在里面刷新,放在外面会导致刷新的进度条卡住 myListAdapter.notifyDataSetChanged(); } //加载完成 lvListNews.setOnLoadMoreComplete(); Toast.makeText(MainActivity.this, "加载了" + 5 + "条数据", Toast.LENGTH_SHORT).show(); } class MyListAdapter extends BaseAdapter { @Override public int getCount() { return mList.size(); } @Override public Object getItem(int position) { return mList.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder = null; if(convertView == null){ convertView = View.inflate(MainActivity.this, R.layout.item_news_tab_detail, null); holder = new ViewHolder(); holder.tvContent = (TextView) convertView.findViewById(R.id.tvContent); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } holder.tvContent.setText(mList.get(position)+""); return convertView; } } static class ViewHolder { TextView tvContent; } }
activity_main.xml
<?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:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.loaderman.myrefreshlistviewdemo.MainActivity"> <com.loaderman.myrefreshlistviewdemo.RefreshListView android:id="@+id/lvListNews" android:layout_width="match_parent" android:layout_height="match_parent" /> </RelativeLayout>
效果图:
最后,关注【码上加油站】微信公众号后,有疑惑有问题想加油的小伙伴可以码上加入社群,让我们一起码上加油吧!!!