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; /** * * 實現步驟: * 一、給ListView添加頭佈局 * 二、默認讓ListView的頭佈局隱藏起來 * 負的paddingTop的值 * 如何獲取頭佈局的高度 * 三、慢慢的將頭佈局拖出來 * 獲取在ListView中的滑動偏移量--onTouchEvent * 關於起點座標的獲取dispatchTouchEvent * 四、給RefreshListView定義了三種狀態 * refreshUi:根據當前的狀態刷新控件的顯示 * 在狀態發生改變的時候來調用此方法便可 * 五、增長了動畫效果 * clearAnimation的使用 * 六、處理up的事件 * STATE_PULL_TO_REFRESH的時候up * 隱藏頭佈局 * STATE_RELEASE_TO_REFRESH的時候up * 顯示頭佈局 * 更新狀態--STATE_REFRESHING * 通知觀察者去加載數據 * 七、觀察者設計模式的使用 * 找出被觀察者 * 定義觀察者接口,接口中的方法就是觀察者感興趣的事件 * 在被觀察中存儲觀察者的引用 * 在事件發生的時候,通知觀察者 * 爲何要用接口而不使用抽象類--單繼承,多實現 * 八、由TabDetailPager來通知RefreshListView數據加載完成 * setOnRefreshComplete * 更新狀態,隱藏頭佈局 * 九、設置時間的顯示 * 存在sp中 * 十、自定義ProgressBar的效果 * 上拉加載: * 一、添加腳佈局,默認隱藏 * 二、增長了滾動監聽, * idle,顯示最後一個條目的時候,顯示腳佈局 * 三、通知觀察者加載下一頁的數據 * 四、加載下一頁數據的邏輯 * 將下一頁數據的集合添加到上一頁數據的集合紅,不能new Adapter * 五、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的來源: //一、自身攔截 二、子控件回傳 @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(); //一、下拉 二、顯示的第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) { //一、更新當前的狀態 mCurrentState = STATE_PULL_TO_REFRESH; pb.setVisibility(View.INVISIBLE); ivArrow.setVisibility(View.VISIBLE); tvTips.setText("下拉刷新"); //二、隱藏頭佈局 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.xmljava
<?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.xmlandroid
<?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.xmlapp
<?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>
效果圖:dom