編輯:關於Android編程
接著上次來講,這次來動手寫一下listview的下拉刷新功能和上拉加載更多功能。
當然google在android4.0以上的API裡面的提供了一個可以下拉加載更多的控件,這個小圓圈加載控件在豆瓣,知乎日報裡面都有運用到,而我在下一篇博客也會提到。
先來了解一下最基本的listview的的加載功能吧。
首先是下拉刷新功能,我先說一下基本的思路。listveiw的面提供了一個addheader()方法,我們可以重寫listview,然後用addheader方法加載我們自定義的加載布局。然後就是隱藏這個header,然後復寫監聽方法OnScrollListener()和OnTouch()方法,最後再提供一個接口方法來讓用戶實現加載數據。具體的我在代碼裡面都注釋好了。
再來說一下上拉加載,這個相比於下拉加載就簡單多了,我們可以addfooter()方法添加布局,然後監聽OnScrollListener就可以了,當最後一個可見的item等於總數量的item時,就可以加載數據了。具體在代碼裡面斗注釋好了。
效果圖:


先發布局文件:
footer.xml
listitem.xml
package com.example.listview_pulltorefresh;
import java.sql.Date;
import java.text.SimpleDateFormat;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
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;
public class RefreshListview extends ListView {
private View header;// 頂部布局文件
private View footer;// 底部布局
private int headerHeight;// 頂部布局文件的高度
private int firstVisibleItem;// 當前第一個可見item的位置
private boolean isRemark;// 標記當前listviews是否最頂端摁下
private int startY;// 開始的Y值
private int mscrollState;// 當前listview的滾動狀態
private int state;// 當前狀態
private static final int NONE = 0;// 正常狀態
private static final int PULL = 1;// 下拉狀態
private static final int RELEASE = 2;// 松開狀態
private static final int REFRESHING = 3;// 刷新狀態
private int mtotalItemCount;//全部item的數量
private int lastVisableItem;//最後一個可見的item
private boolean isLoading=false;//是否正在加載
public RefreshListview(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// TODO Auto-generated constructor stub
initView(context);
}
public RefreshListview(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
initView(context);
}
public RefreshListview(Context context) {
super(context);
// TODO Auto-generated constructor stub
initView(context);
}
/**
* 添加頂部布局文件
*
* @param context
*/
private void initView(Context context) {
LayoutInflater inflater = LayoutInflater.from(context);
footer = inflater.inflate(R.layout.footer_loading, null, false);
header = inflater.inflate(R.layout.header_layout, null, false);
measureView(header);
headerHeight = header.getMeasuredHeight();
Log.i("test", "headHeight:" + headerHeight);
topPadding(-headerHeight);
this.addHeaderView(header);
//先設置底部隱藏
footer.setVisibility(View.GONE);
this.addFooterView(footer);
this.setOnScrollListener(new OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
// TODO Auto-generated method stub
mscrollState = scrollState;
//最後一個可見的item是總數量,並且當前滾動狀態停止,就加載數據
if (mtotalItemCount==lastVisableItem&&scrollState==OnScrollListener.SCROLL_STATE_IDLE) {
if (!isLoading) {
//加載數據
isLoading=true;
footer.setVisibility(VISIBLE);
mListener2.onReflashMore();
}
}
}
/**
* firstvisebleitem第一個可見的位置
*/
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
// TODO Auto-generated method stub
lastVisableItem=firstVisibleItem+visibleItemCount;
mtotalItemCount=totalItemCount;
}
});
this.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
// TODO Auto-generated method stub
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (firstVisibleItem == 0) {
isRemark = true;
startY = (int) event.getY();
}
case MotionEvent.ACTION_MOVE:
onMove(event);
break;
case MotionEvent.ACTION_UP:
if (state == RELEASE) {
state = REFRESHING;
reflashViewByState();
mListener.onrReflash(); // 加載數據
// 在外部調用reflashcomplete
} else if (state == PULL) {
state = NONE;
isRemark = false;
reflashViewByState();
}
break;
default:
break;
}
return false;
}
});
}
/**
* 通過view獲取layoutparams,然後初始化lp, 要調用measure()方法來設置子view的寬高
* measure的方法參數有變化的是用MeasureSpec.makeMeasureSpec設置
* 沒有變化的用getChildMeasureSpec()方法設置
*
* @param view
*/
public void measureView(View view) {
ViewGroup.LayoutParams lParams = view.getLayoutParams();
if (lParams == null) {
lParams = new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
// spec左右邊距,padding內邊距
int width = ViewGroup.getChildMeasureSpec(0, 0, lParams.width);
int height;
int tempHeight = lParams.height;
if (tempHeight > 0) {
// 填充用exactly
height = MeasureSpec.makeMeasureSpec(tempHeight,
MeasureSpec.EXACTLY);
} else {
// 意思就是<=0時,則告訴父布局子view高度填充0
height = MeasureSpec
.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
}
view.measure(width, height);
}
}
/**
* 設置header布局的上邊距
*
* @param topPadding
*/
public void topPadding(int topPadding) {
header.setPadding(header.getPaddingLeft(), topPadding,
header.getPaddingRight(), header.getPaddingBottom());
header.invalidate();
}
/**
* 判斷移動過程中的操作
*
* @param event
*/
private void onMove(MotionEvent event) {
// TODO Auto-generated method stub
if (!isRemark) {
return;
}
int tempY = (int) event.getY();// 獲取當前y
int space = tempY - startY;// 顯示的高度
int topPadding = space - headerHeight;// 因為是要用負值,所以減去高度
switch (state) {
case NONE:
if (space > 0) {
state = PULL;
reflashViewByState();
}
break;
case PULL:
topPadding(topPadding);
// 大於heigh+30且在滑動時,則是可以刷新
if (space > headerHeight + 30
&& mscrollState == OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
state = RELEASE;
reflashViewByState();
}
break;
case RELEASE:
topPadding(topPadding);
// 為釋放狀態時,則可以回到下拉狀態
if (space < headerHeight + 30) {
state = PULL;
reflashViewByState();
} else if (space <= 0) {
state = NONE;
isRemark = false;
reflashViewByState();
}
break;
case REFRESHING:
break;
default:
break;
}
}
/**
* 改變下拉過程中的header布局中的控件的內容
*/
public void reflashViewByState() {
TextView tip = (TextView) header.findViewById(R.id.tip);
ImageView arrow = (ImageView) header.findViewById(R.id.arrow);
ProgressBar progressBar = (ProgressBar) header
.findViewById(R.id.progress);
// RotateAnimation旋轉動畫,旋轉的角度,相對與自己,中心位置
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);
progressBar.setVisibility(GONE);
arrow.clearAnimation();
arrow.setAnimation(animation2);
tip.setText("下拉可以刷新");
break;
case RELEASE:
arrow.setVisibility(View.VISIBLE);
progressBar.setVisibility(GONE);
arrow.clearAnimation();
arrow.setAnimation(animation);
tip.setText("松開可以刷新");
break;
case REFRESHING:
topPadding(50);
arrow.clearAnimation();
arrow.setVisibility(View.GONE);
progressBar.setVisibility(VISIBLE);
tip.setText("正在刷新");
break;
default:
break;
}
}
/**
* 獲取完數據
*/
public void reflashComplete() {
state = NONE;
isRemark = false;
reflashViewByState();
TextView lastTimeReflash = (TextView) header
.findViewById(R.id.lastrefresh_time);
SimpleDateFormat format = new SimpleDateFormat("yyyy年MM月dd日 hh:mm:ss");
Date date = new Date(System.currentTimeMillis());
String timeString = format.format(date);
lastTimeReflash.setText(timeString);
}
/**
* 底部加載完畢
*/
public void reflashFooterComplete() {
isLoading=false;
footer.setVisibility(GONE);
}
/**
* 刷新數據接口
*
* @author nickming
*
*/
public interface OnReflashListener {
public void onrReflash();
}
public OnReflashListener mListener;// 刷新數據的接口
public void setOnReflashListener(OnReflashListener listener) {
mListener = listener;
}
/**
* 加載更多接口
* @author nickming
*
*/
public interface OnReflashMoreListener{
public void onReflashMore();
}
public OnReflashMoreListener mListener2;
public void setOnReflashMoreListener(OnReflashMoreListener listener) {
mListener2=listener;
}
}
MainActivity.class
package com.example.listview_pulltorefresh;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import com.example.listview_pulltorefresh.RefreshListview.OnReflashListener;
import com.example.listview_pulltorefresh.RefreshListview.OnReflashMoreListener;
public class MainActivity extends Activity {
private RefreshListview mlistview;
MyAdapter adapter;
List mdata;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mlistview = (RefreshListview) findViewById(R.id.listview);
mdata = new ArrayList();
for (int i = 0; i < 20; i++) {
DataBean dataBean = new DataBean();
dataBean.setTextString("數據列:" + i);
mdata.add(dataBean);
}
adapter = new MyAdapter(this, mdata);
mlistview.setAdapter(adapter);
mlistview.setOnReflashListener(new OnReflashListener() {
@Override
public void onrReflash() {
// TODO Auto-generated method stub
// 模擬網絡延時
Handler mHandler = new Handler();
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
// 獲取最新數據
addHeaderData();
// 通知布局顯示
adapter.notifyDataSetChanged();
// listview刷新
mlistview.reflashComplete();
}
}, 3000);
}
});
mlistview.setOnReflashMoreListener(new OnReflashMoreListener() {
@Override
public void onReflashMore() {
// TODO Auto-generated method stub
Handler mHandler=new Handler();
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
addFooterData();
adapter.notifyDataSetChanged();
mlistview.reflashFooterComplete();
}
}, 2000);
}
});
}
public void addHeaderData() {
for (int i = 0; i < 10; i++) {
DataBean dataBean = new DataBean();
String nameString = UUID.randomUUID().toString();
dataBean.setTextString("最新數據:" + nameString);
mdata.add(0, dataBean);// 放在最前面
}
}
public void addFooterData() {
for (int i = 0; i < 10; i++) {
DataBean dataBean = new DataBean();
String nameString = UUID.randomUUID().toString();
dataBean.setTextString("最新數據:" + nameString);
mdata.add( dataBean);// 放在最後面
}
}
}
package com.example.listview_pulltorefresh;
import java.util.List;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
public class MyAdapter extends BaseAdapter {
private Context mcontext;
private List mData;
LayoutInflater inflater;
public MyAdapter(Context context, List mData) {
super();
this.mcontext = context;
this.mData = mData;
inflater=LayoutInflater.from(context);
}
@Override
public int getCount() {
// TODO Auto-generated method stub
return mData.size();
}
@Override
public Object getItem(int position) {
// TODO Auto-generated method stub
return mData.get(position);
}
@Override
public long getItemId(int position) {
// TODO Auto-generated method stub
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
viewHolder holder=null;
if (convertView == null) {
convertView = inflater.inflate(R.layout.listitem, parent,false);
holder = new viewHolder();
holder.textView = (TextView) convertView
.findViewById(R.id.textView1);
convertView.setTag(holder);
}else {
holder=(viewHolder) convertView.getTag();
}
holder.textView.setText(mData.get(position).getTextString());
return convertView;
}
class viewHolder {
TextView textView;
}
}
package com.example.listview_pulltorefresh;
import android.widget.TextView;
public class DataBean {
String textString;
public DataBean() {
// TODO Auto-generated constructor stub
}
public DataBean(String textString) {
super();
this.textString = textString;
}
public String getTextString() {
return textString;
}
public void setTextString(String textString) {
this.textString = textString;
}
}
Android中替換WebView加載網頁失敗時的頁面
我們用webView去請求一個網頁鏈接的時候,如果請求網頁失敗或無網絡的情況下,它會返回給我們這樣一個頁面,如下圖所示: 上面這個頁面就是系統自帶的頁面,你覺得
Android Studio官方文檔之添加URL和App索引支持
Android Studio可以幫你在App中添加對URLs,app索引,搜索功能的支持。這些功能可以幫你推動更多的流量到你的App、發現App中最被常用的內容,使用戶更
Android 面試題總結之Android 基礎(四)
Service常見面試題Service 是否在 main thread 中執行, service 裡面是否 能執行耗時的操作?默認情況,如果沒有顯示的指 servic 所
Android4.4 Telephony流程分析——SIM卡開機時的數據加載
本文代碼以MTK平台Android 4.4為分析對象,與Google原生AOSP有些許差異,請讀者知悉。 本文主要介紹sim卡數據的讀取過程,當射頻狀態處於