再也不用擔心下拉重新整理,上拉載入啦!-自定義ListView對上拉重新整理,上拉載入的詳解
前言:
看過許多下拉重新整理的例子,好多大牛們的程式碼寫的很完美,讓人羨慕嫉妒恨~~~,可是,對於下拉重新整理時的手勢操作卻沒有給出詳細的解釋,當一堆堆邏輯程式碼出來的時候,對於我們這些菜鳥來說,理解起來真是讓人腦子都大了。為了解放大腦(懶得自己進行全面分析),一步一步詳解下拉操作,媽媽再也不用擔心ListView 下拉重新整理是什麼鬼啦!~~
先上效果圖:~~
上拉載入資料: 下拉重新整理:下拉距離短不重新整理資料 下拉重新整理資料:
思路詳解:
自定義的帶有下拉重新整理和上拉載入的ListView開始時,跟系統的ListView一樣。不過多了個header和footer只不過這兩個佈局以不同的方式隱藏起來了而已。(header是在手機螢幕外的上面,footer是直接隱藏起來了。因為下拉和上拉載入不同,下拉載入有手勢判斷,要出現動畫效果,根據下拉的各種手勢,來設定具體的操作。如果直接跟footer一樣首先預設header的View.setVisibility(GONE),當有下拉手勢時再設定View.setVisibility(View.VISIBLE)就會沒有良好的動畫效果。
PS:破電腦只有自帶的Window畫圖工具。湊合看吧
先將程式碼拆分~ 上拉載入跟下拉重新整理分開來講,後面會有完整的程式碼。~~~
上拉載入資料:
由於這個比下拉重新整理簡單。先理解這個。上拉載入適用於需要載入的資料量很大時,如果一下子載入完。會使ListView出現卡頓。這時候,如果利用上拉載入。先載入一部分資料。當上拉時,再載入其他的一部分資料。這樣就會有很好的使用者體驗。
footer佈局檔案
<?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:layout_centerInParent="true" android:orientation="vertical"> <LinearLayout android:id="@+id/ll_footer" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <ProgressBar android:id="@+id/footer_pb" android:layout_width="wrap_content" android:layout_height="wrap_content" style="?android:attr/progressBarStyleSmall" android:layout_gravity="center" /> <TextView android:id="@+id/footer_tv" android:text="footer正在載入。。" android:textSize="16sp" android:gravity="center" android:layout_width="match_parent" android:layout_height="wrap_content" /> </LinearLayout> </LinearLayout>
接下來看上拉載入邏輯:上拉載入我們利用的是AbsListView.OnScrollListener這個介面。 它有兩個方法需要重寫:
1.publicvoid onScroll(AbsListView view,int firstVisibleItem,int visibleItemCount,int totalItemCount) {};
2.publicvoid onScrollStateChanged(AbsListView view,int scrollState) {};
借鑑別的地方的對這兩個方法的詳細解釋。他講解的很詳細啦:
new OnScrollListener() { boolean isLastRow = false; @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { //滾動時一直回撥,直到停止滾動時才停止回撥。單擊時回撥一次。 //firstVisibleItem:當前能看見的第一個列表項ID(從0開始) //visibleItemCount:當前能看見的列表項個數(小半個也算) //totalItemCount:列表項共數 //判斷是否滾到最後一行 if (firstVisibleItem + visibleItemCount == totalItemCount && totalItemCount > 0) { isLastRow = true; } } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { //正在滾動時回撥,回撥2-3次,手指沒拋則回撥2次。scrollState = 2的這次不回撥 //回撥順序如下 //第1次:scrollState = SCROLL_STATE_TOUCH_SCROLL(1) 正在滾動 //第2次:scrollState = SCROLL_STATE_FLING(2) 手指做了拋的動作(手指離開螢幕前,用力滑了一下) //第3次:scrollState = SCROLL_STATE_IDLE(0) 停止滾動 //當螢幕停止滾動時為0;當螢幕滾動且使用者使用的觸碰或手指還在螢幕上時為1; //由於使用者的操作,螢幕產生慣性滑動時為2 //當滾到最後一行且停止滾動時,執行載入 if (isLastRow && scrollState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE) { //載入元素 ...... isLastRow = false; } } }
好了,瞭解了OnScrollListener的這兩個方法具體是幹什麼的,接下來看我們怎麼實現~~~:
上拉載入的關鍵就在這個介面中實現:
首先是LoadListView中的定義的變數:
private int lastVisibleItem; //最後一個可見項
private int totalItems; //總的item
private View footer; //底部View+頭部View;
private boolean isLoading = false;//是否正在載入
private ILoadListener iListener;//自定義的一個載入介面。暴露給MainActivity讓它實現具體載入操作。可以根據需求不同而改寫。
載入佈局檔案以及設定監聽:
private void initViews(Context context) {
//獲得footer+header佈局檔案
LayoutInflater inflater =LayoutInflater.from(context);
footer = inflater.inflate(R.layout.footer,null);
footer.findViewById(R.id.ll_footer).setVisibility(GONE);//初始化時設定footer不可見
this.addFooterView(footer);
this.setOnScrollListener(this);//設定滾動監聽
}
重寫OnScrollListener的這兩個方法:
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if(lastVisibleItem==totalItems&&scrollState ==SCROLL_STATE_IDLE){
//如果不是在載入
if(!isLoading){
footer.findViewById(R.id.ll_footer).setVisibility(View.VISIBLE);
iListener.onLoad();
isLoading =true;
}
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
this.lastVisibleItem=firstVisibleItem+visibleItemCount;
this.totalItems = totalItemCount;
}
這樣就差不多了。接下來再完成介面的設定,具體操作再MainActivity中實現。
/**
* 載入更多資料的回撥介面
*/
public interface ILoadListener {
public void onLoad();
}
//上拉載入完畢
public void loadCompleted(){
isLoading =false;
footer.findViewById(R.id.ll_footer).setVisibility(GONE);
}
public void setInterface(ILoadListener iListener){
this.iListener=iListener;
}
上拉載入就完成了80%了,具體操作時,在MainActivity中:
private LoadListView mListView;
private List<String> datas;
private ArrayAdapter<String> arrayAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setupViews();
initDatas();
}
private void initDatas() {
for (int i = 0; i < 16; i++) {
datas.add("ListView的資料"+i+"");
}
}
private void initNewDatas(){
for (int i = 0; i < 3; i++) {
datas.add("footer加載出的資料"+i+"");
}
}
private void setupViews() {
mListView = (LoadListView) findViewById(R.id.lv_main);
//上拉載入介面
mListView.setInterface( this);
datas = new ArrayList<String>();
arrayAdapter = new ArrayAdapter<String>(MainActivity.this,android.R.layout.simple_expandable_list_item_1,datas);
mListView.setAdapter(arrayAdapter);
}
實現LoadList暴露的介面中的onLoad()方法:
//實現onLoad()方法。
@Override
public void onLoad() {
//新增延時效果模擬資料載入
Handler handler= new Handler() ;
handler.postDelayed(new Runnable() {
@Override
public void run() {
initNewDatas();//得到新資料
arrayAdapter.notifyDataSetChanged();//重新整理ListView;
mListView.loadCompleted();
}
}, 2000);
}
OK~上拉載入資料就大功告成了~~下拉重新整理:
首先是header佈局檔案:
<?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"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="10dip"
android:paddingBottom="10dip"
>
<LinearLayout
android:id="@+id/ll_header"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:gravity="center"
android:orientation="vertical">
<TextView
android:id="@+id/tip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="下拉可以重新整理"/>
<TextView
android:id="@+id/tv_lastupdate_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
</LinearLayout>
<ImageView
android:id="@+id/arrow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toLeftOf="@id/ll_header"
android:layout_marginRight="20dip"
android:src="@drawable/pull_down_refresh_arrow"
/>
<ProgressBar
android:id="@+id/header_pb"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="?android:attr/progressBarStyleSmall"
android:layout_toLeftOf="@id/ll_header"
android:layout_marginRight="20dip"
android:visibility="gone"/>
</RelativeLayout>
</LinearLayout>
LoadList中自定義的一些變數:
private boolean isRemark = false;//判斷是否在當前頁的最頂端並下滑
private int startY; //Y座標 記錄手指開始按下的座標
private RLoadListener rLoadListener;//自定義的一個載入介面。暴露給MainActivity讓它實現具體載入操作。可以根據需求不同而改寫。
private int scrollState;//當前滾動的 狀態
private int headerHeight;//頂部佈局檔案的高度
final int NONE= 0;//正常狀態
final int PULL =1;//下拉
final int RELESE =2;//釋放
final int REFLASHING =3; //重新整理
private int state=0;//判斷當前狀態,預設為正常狀態
private int firstVisibleItem;//第一個可見項
private View header; //頭部View;
新增頭部提示到ListView中:
LayoutInflater inflater =LayoutInflater.from(context);
header = inflater.inflate(R.layout.header,null);
//測量header的寬和高
measureView(header);
//記錄下header的高
headerHeight = header.getMeasuredHeight();
topPadding(-headerHeight);
this.addHeaderView(header);
this.setOnScrollListener(this);//設定滾動監聽
這裡新增頭佈局的時候需要計算header到底要移出螢幕多少的距離(移出的距離即為header的高),並且要告知父佈局:
/**
* 通知父佈局,佔用的寬和高
* @param view
*/
private void measureView(View view) {
//得到view的佈局寬高
ViewGroup.LayoutParams vlp = view.getLayoutParams();
//如果沒有就new一個
if (vlp==null){
vlp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
}
//ViewGroup.getChildMeasureSpec(int spec,int padding,int childDimension) 1.父view的詳細尺寸 2.view當前尺寸的下的邊距 3.child在當前尺寸下的寬(高)
int width = ViewGroup.getChildMeasureSpec(0,0,vlp.width);
int height;
int tempHeight = vlp.height;
if (tempHeight>0){
height = MeasureSpec.makeMeasureSpec(tempHeight,MeasureSpec.EXACTLY);
}else {
height =MeasureSpec.makeMeasureSpec(0,MeasureSpec.UNSPECIFIED);
}
view.measure(width, height);
}
/**
* 設定header佈局的上邊距
* @param topPadding
*/
private void topPadding(int topPadding) {
header.setPadding(header.getPaddingLeft(), topPadding, header.getPaddingRight(), header.getPaddingBottom());
//重繪
header.invalidate();
}
在OnScrollListener的兩個方法中記錄一些狀態量便於後面對OnTouch事件的操作:
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
this.scrollState =scrollState;
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
this.firstVisibleItem = firstVisibleItem;
}
利用OnTouchEvent對下拉手勢操作進行監聽:
先來說明下拉重新整理會出現的情況:1.下拉距離過短,不進行資料重新整理。2.下拉到一定距離重新整理資料。
當進行下拉重新整理的時候,手勢有3種狀態:1.剛按下的時候;2.手指下滑移動的時候;3.手指擡起釋放的時候。這3種手指狀態又對應了header不同的View狀態,然後根據view的狀態,再來改變header的顯示。
就是靠下面這三個方法來實現下拉的核心操作分析在程式碼後面:
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN://如果按下
if (firstVisibleItem==0){
isRemark=true;
startY = (int) ev.getY();
}
break;
case MotionEvent.ACTION_MOVE:
onMove(ev);
break;
case MotionEvent.ACTION_UP:
if (state==RELESE){
state = REFLASHING;
//載入最新資料
reflashViewByState();
rLoadListener.onRefresh();
}else if (state==PULL){
state = NONE;
isRemark = false;
reflashViewByState();
}
break;
}
return super.onTouchEvent(ev);
}
/**
* 判斷移動的過程
*
* @param ev
*/
private void onMove(MotionEvent ev) {
if (!isRemark){
return;
}
int tempY = (int) ev.getY();
//記錄滑動距離
int space = tempY-startY;
//比較滑動距離和header的高
int topPadding = space-headerHeight;
switch (state){
case NONE:
if (space>0){
state = PULL;
reflashViewByState();
}
break;
case PULL:
//重繪header
topPadding(topPadding);
if (space>headerHeight+30&&scrollState==SCROLL_STATE_TOUCH_SCROLL){
state =RELESE;
reflashViewByState();
}
break;
case RELESE:
topPadding(topPadding);
if (space<headerHeight+30){
state =PULL;
reflashViewByState();
}else if(space<=0){//如果space<0說明向上滑。所以firstVisibleItem就不是0了 所以將isRemark設定為false;即listView現在不是最頂端的位置
state = NONE;
isRemark = false;
reflashViewByState();
}
break;
}
}
/**
* 根據當前狀態,改變介面顯示
*/
private void reflashViewByState() {
TextView tip = (TextView) header.findViewById(R.id.tip);
ImageView arrow = (ImageView) header.findViewById(R.id.arrow);
ProgressBar header_pb = (ProgressBar) header.findViewById(R.id.header_pb);
RotateAnimation animation = new RotateAnimation(0,180,RotateAnimation.RELATIVE_TO_SELF,0.5f,RotateAnimation.RELATIVE_TO_SELF,0.5f);
animation.setDuration(500);
animation.setFillAfter(true);
RotateAnimation animation2 = new RotateAnimation(180,0,RotateAnimation.RELATIVE_TO_SELF,0.5f,RotateAnimation.RELATIVE_TO_SELF,0.5f);
animation2.setDuration(500);
animation2.setFillAfter(true);
/**
* 四種狀態動畫的改變
*/
switch (state){
case NONE:
arrow.clearAnimation();
topPadding(-headerHeight);
break;
case PULL:
arrow.setVisibility(View.VISIBLE);
header_pb.setVisibility(View.GONE);
tip.setText("下拉可以重新整理!!");
arrow.clearAnimation();
arrow.setAnimation(animation2);
break;
case RELESE:
arrow.setVisibility(View.VISIBLE);
header_pb.setVisibility(View.GONE);
tip.setText("鬆開可以重新整理!!");
arrow.clearAnimation();
arrow.setAnimation(animation);
break;
case REFLASHING:
topPadding(50);
arrow.setVisibility(View.GONE);
header_pb.setVisibility(View.VISIBLE);
tip.setText("正在重新整理...");
arrow.clearAnimation();
break;
}
}
手指操作的圖示:
由於圖太大,放到裡面看不清。。。需要高清無碼大圖的點選這裡:點我!!點我!!點我!!
這樣,下拉重新整理的核心程式碼已經完成了,接下來就是設定介面,和重新整理完所做的動作的方法:
下拉重新整理介面,暴露給MainActivity來具體實現到底刷新出什麼資料:
/**
* 下拉重新整理介面
*/
public interface RLoadListener{
public void onRefresh();
}
public void setReflashInterface(RLoadListener rLoadListener){
this.rLoadListener =rLoadListener;
}
重新整理完要做的工作:
/**
* 獲取完整資料
*
*/
public void reflashComplete(){
state = NONE;
isRemark = false;
reflashViewByState();
TextView lasetupdate_time = (TextView) header.findViewById(R.id.tv_lastupdate_time);
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy年MM月dd日 hh:mm:ss");
Date date = new Date(System.currentTimeMillis());
String time = simpleDateFormat.format(date);
lasetupdate_time.setText(time);
}
duang~duang~duang ~ 接下來就看怎麼用這些東西在MainActivity中。
貼上完整的程式碼~~。
1. 佈局檔案:
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:layout_centerInParent="true"
android:orientation="vertical">
<LinearLayout
android:id="@+id/ll_footer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<ProgressBar
android:id="@+id/footer_pb"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="?android:attr/progressBarStyleSmall"
android:layout_gravity="center"
/>
<TextView
android:id="@+id/footer_tv"
android:text="footer正在載入。。"
android:textSize="16sp"
android:gravity="center"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>
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="wrap_content"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="10dip"
android:paddingBottom="10dip"
>
<LinearLayout
android:id="@+id/ll_header"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:gravity="center"
android:orientation="vertical">
<TextView
android:id="@+id/tip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="下拉可以重新整理"/>
<TextView
android:id="@+id/tv_lastupdate_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
</LinearLayout>
<ImageView
android:id="@+id/arrow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toLeftOf="@id/ll_header"
android:layout_marginRight="20dip"
android:src="@drawable/pull_down_refresh_arrow"
/>
<ProgressBar
android:id="@+id/header_pb"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="?android:attr/progressBarStyleSmall"
android:layout_toLeftOf="@id/ll_header"
android:layout_marginRight="20dip"
android:visibility="gone"/>
</RelativeLayout>
</LinearLayout>
<?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"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="10dip"
android:paddingBottom="10dip"
>
<LinearLayout
android:id="@+id/ll_header"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:gravity="center"
android:orientation="vertical">
<TextView
android:id="@+id/tip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="下拉可以重新整理"/>
<TextView
android:id="@+id/tv_lastupdate_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
</LinearLayout>
<ImageView
android:id="@+id/arrow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toLeftOf="@id/ll_header"
android:layout_marginRight="20dip"
android:src="@drawable/pull_down_refresh_arrow"
/>
<ProgressBar
android:id="@+id/header_pb"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="?android:attr/progressBarStyleSmall"
android:layout_toLeftOf="@id/ll_header"
android:layout_marginRight="20dip"
android:visibility="gone"/>
</RelativeLayout>
</LinearLayout>
activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">
<com.example.administrator.listviewtest.LoadListView
android:id="@+id/lv_main"
android:layout_width="match_parent"
android:layout_height="match_parent"></com.example.administrator.listviewtest.LoadListView>
</RelativeLayout>
LoadListView.java
package com.example.administrator.listviewtest;
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
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;
/**
* Created by Administrator on 2015-10-12.
*/
public class LoadListView extends ListView implements AbsListView.OnScrollListener {
private int lastVisibleItem; //最後一個可見項
private int totalItems; //總的item
private View footer,header; //底部View+頭部View;
private boolean isLoading = false;//是否正在載入
private ILoadListener iListener;//自定義的一個載入介面。暴露給MainActivity讓它實現具體載入操作。可以根據需求不同而改寫。
private boolean isRemark = false;//判斷是否在當前頁的最頂端並下滑
private int startY; //Y座標 記錄手指開始按下的座標
private RLoadListener rLoadListener;//自定義的一個載入介面。暴露給MainActivity讓它實現具體載入操作。可以根據需求不同而改寫。
private int scrollState;//當前滾動的 狀態
private int headerHeight;//頂部佈局檔案的高度
final int NONE= 0;//正常狀態
final int PULL =1;//下拉
final int RELESE =2;//釋放
final int REFLASHING =3; //重新整理
private int state=0;//判斷當前狀態,預設為正常狀態
private int firstVisibleItem;//第一個可見項
public LoadListView(Context context) {
this(context,null);
}
public LoadListView(Context context, AttributeSet attrs) {
this(context, attrs, android.R.attr.listViewStyle);
}
public LoadListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initViews(context);
}
/**
* 新增底部+頭部提示到ListVIew
* @param context
*/
private void initViews(Context context) {
//獲得footer+header佈局檔案
LayoutInflater inflater =LayoutInflater.from(context);
footer = inflater.inflate(R.layout.footer,null);
footer.findViewById(R.id.ll_footer).setVisibility(GONE);//初始化時設定footer不可見
header = inflater.inflate(R.layout.header,null);
//測量header的寬和高
measureView(header);
//記錄下header的高
headerHeight = header.getMeasuredHeight();
topPadding(-headerHeight);
this.addHeaderView(header);
this.addFooterView(footer);
this.setOnScrollListener(this);//設定滾動監聽
}
/**
* 通知父佈局,佔用的寬和高
* @param view
*/
private void measureView(View view) {
//得到view的佈局寬高
ViewGroup.LayoutParams vlp = view.getLayoutParams();
//如果沒有就new一個
if (vlp==null){
vlp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
}
//ViewGroup.getChildMeasureSpec(int spec,int padding,int childDimension) 1.父view的詳細尺寸 2.view當前尺寸的下的邊距 3.child在當前尺寸下的寬(高)
int width = ViewGroup.getChildMeasureSpec(0,0,vlp.width);
int height;
int tempHeight = vlp.height;
if (tempHeight>0){
height = MeasureSpec.makeMeasureSpec(tempHeight,MeasureSpec.EXACTLY);
}else {
height =MeasureSpec.makeMeasureSpec(0,MeasureSpec.UNSPECIFIED);
}
view.measure(width, height);
}
/**
* 設定header佈局的上邊距
* @param topPadding
*/
private void topPadding(int topPadding) {
header.setPadding(header.getPaddingLeft(), topPadding, header.getPaddingRight(), header.getPaddingBottom());
//重繪
header.invalidate();
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
this.scrollState =scrollState;
if(lastVisibleItem==totalItems&&scrollState ==SCROLL_STATE_IDLE){
//如果不是在載入
if(!isLoading){
footer.findViewById(R.id.ll_footer).setVisibility(View.VISIBLE);
iListener.onLoad();
isLoading =true;
}
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
this.lastVisibleItem=firstVisibleItem+visibleItemCount;
this.totalItems = totalItemCount;
this.firstVisibleItem = firstVisibleItem;
}
/**
* 載入更多資料的回撥介面
*/
public interface ILoadListener {
public void onLoad();
}
//上拉載入完畢
public void loadCompleted(){
isLoading =false;
footer.findViewById(R.id.ll_footer).setVisibility(GONE);
}
public void setInterface(ILoadListener iListener){
this.iListener=iListener;
}
/**
* 下拉重新整理介面
*/
public interface RLoadListener{
public void onRefresh();
}
public void setReflashInterface(RLoadListener rLoadListener){
this.rLoadListener =rLoadListener;
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN://如果按下
if (firstVisibleItem==0){
isRemark=true;
startY = (int) ev.getY();
}
break;
case MotionEvent.ACTION_MOVE:
onMove(ev);
break;
case MotionEvent.ACTION_UP:
if (state==RELESE){
state = REFLASHING;
//載入最新資料
reflashViewByState();
rLoadListener.onRefresh();
}else if (state==PULL){
state = NONE;
isRemark = false;
reflashViewByState();
}
break;
}
return super.onTouchEvent(ev);
}
/**
* 判斷移動的過程
*
* @param ev
*/
private void onMove(MotionEvent ev) {
if (!isRemark){
return;
}
int tempY = (int) ev.getY();
//記錄滑動距離
int space = tempY-startY;
//比較滑動距離和header的高
int topPadding = space-headerHeight;
switch (state){
case NONE:
if (space>0){
state = PULL;
reflashViewByState();
}
break;
case PULL:
//重繪header
topPadding(topPadding);
if (space>headerHeight+30&&scrollState==SCROLL_STATE_TOUCH_SCROLL){
state =RELESE;
reflashViewByState();
}
break;
case RELESE:
topPadding(topPadding);
if (space<headerHeight+30){
state =PULL;
reflashViewByState();
}else if(space<=0){//如果space<0說明向上滑。所以firstVisibleItem就不是0了 所以將isRemark設定為false;即listView現在不是最頂端的位置
state = NONE;
isRemark = false;
reflashViewByState();
}
break;
}
}
/**
* 根據當前狀態,改變介面顯示
*/
private void reflashViewByState() {
TextView tip = (TextView) header.findViewById(R.id.tip);
ImageView arrow = (ImageView) header.findViewById(R.id.arrow);
ProgressBar header_pb = (ProgressBar) header.findViewById(R.id.header_pb);
//設定的下拉箭頭的動畫效果
RotateAnimation animation = new RotateAnimation(0,180,RotateAnimation.RELATIVE_TO_SELF,0.5f,RotateAnimation.RELATIVE_TO_SELF,0.5f);
animation.setDuration(500);
animation.setFillAfter(true);
//設定的下拉箭頭的動畫效果
RotateAnimation animation2 = new RotateAnimation(180,0,RotateAnimation.RELATIVE_TO_SELF,0.5f,RotateAnimation.RELATIVE_TO_SELF,0.5f);
animation2.setDuration(500);
animation2.setFillAfter(true);
/**
* 四種狀態動畫的改變
*/
switch (state){
case NONE:
arrow.clearAnimation();
topPadding(-headerHeight);
break;
case PULL:
arrow.setVisibility(View.VISIBLE);
header_pb.setVisibility(View.GONE);
tip.setText("下拉可以重新整理!!");
arrow.clearAnimation();
arrow.setAnimation(animation2);
break;
case RELESE:
arrow.setVisibility(View.VISIBLE);
header_pb.setVisibility(View.GONE);
tip.setText("鬆開可以重新整理!!");
arrow.clearAnimation();
arrow.setAnimation(animation);
break;
case REFLASHING:
topPadding(50);
arrow.setVisibility(View.GONE);
header_pb.setVisibility(View.VISIBLE);
tip.setText("正在重新整理...");
arrow.clearAnimation();
break;
}
}
/**
* 獲取完整資料
*
*/
public void reflashComplete(){
state = NONE;
isRemark = false;
reflashViewByState();
//設定重新整理完成的時間
TextView lasetupdate_time = (TextView) header.findViewById(R.id.tv_lastupdate_time);
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy年MM月dd日 hh:mm:ss");
Date date = new Date(System.currentTimeMillis());
String time = simpleDateFormat.format(date);
lasetupdate_time.setText(time);
}
}
MainActivity.java
package com.example.administrator.listviewtest;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.ArrayAdapter;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.LogRecord;
public class MainActivity extends AppCompatActivity implements LoadListView.ILoadListener,LoadListView.RLoadListener{
private LoadListView mListView;
private List<String> datas;
private ArrayAdapter<String> arrayAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setupViews();
initDatas();
}
private void initDatas() {
for (int i = 0; i < 16; i++) {
datas.add("ListView的資料"+i+"");
}
}
private void initNewDatas(){
for (int i = 0; i < 3; i++) {
datas.add("footer加載出的資料"+i+"");
}
}
private void initREflashDatas() {
datas.add(0,"下拉重新整理載入的資料");
}
private void setupViews() {
mListView = (LoadListView) findViewById(R.id.lv_main);
//上拉載入介面
mListView.setInterface( this);
mListView.setReflashInterface(this);
datas = new ArrayList<String>();
arrayAdapter = new ArrayAdapter<String>(MainActivity.this,android.R.layout.simple_expandable_list_item_1,datas);
mListView.setAdapter(arrayAdapter);
}
//實現onLoad()方法。
@Override
public void onLoad() {
//新增延時效果模擬資料載入
Handler handler= new Handler() ;
handler.postDelayed(new Runnable() {
@Override
public void run() {
initNewDatas();//得到新資料
arrayAdapter.notifyDataSetChanged();//重新整理ListView;
mListView.loadCompleted();
}
}, 2000);
}
// 實現的重新整理方法
@Override
public void onRefresh() {
Handler handler= new Handler() ;
handler.postDelayed(new Runnable() {
@Override
public void run() {
initREflashDatas();//得到新資料
arrayAdapter.notifyDataSetChanged();//重新整理ListView;
mListView.reflashComplete();
}
}, 2000);
}
}
大功告成~~。
附上原始碼記得給好評~:原始碼地址。
Ps:Google其實早已經更新了sdk新增加的一個widget,SwipeRefreshLayout字面意思就是下拉重新整理的佈局,繼承自ViewGroup,可以實現下拉重新整理的操作~~