編輯:關於Android編程
關於實現ListView下拉刷新和加載更多的實現,我想網上一搜就一堆。不過我就沒發現比較實用的,要不就是實現起來太復雜,要不就是不健全的。因為小巫近期要開發新浪微博客戶端,需要實現ListView的下拉刷新,所以就想把這個UI整合到項目當中去,這裡只是一個demo,可以根據項目的需要進行修改。
自定義ListView:
package com.markupartist.android.widget;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import com.markupartist.android.example.pulltorefresh.R;
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.LinearInterpolator;
import android.view.animation.RotateAnimation;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.ImageView;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.TextView;
/**
* 2013/8/13
* 自定義ListView,實現OnScrollListener接口
* 此ListView是為實現"下拉刷新"和"上拉加載更多"而定制的,具體效果可參考新浪微博、騰訊微博
* @author wwj
*
*/
public class PullToRefreshListView extends ListView implements OnScrollListener {
private static final int TAP_TO_REFRESH = 1; //(未刷新)
private static final int PULL_TO_REFRESH = 2; // 下拉刷新
private static final int RELEASE_TO_REFRESH = 3; // 釋放刷新
private static final int REFRESHING = 4; // 正在刷新
private static final int TAP_TO_LOADMORE = 5; // 未加載更多
private static final int LOADING = 6; // 正在加載
private static final String TAG = "PullToRefreshListView";
private OnRefreshListener mOnRefreshListener; // 刷新監聽器
/**
* Listener that will receive notifications every time the list scrolls.
*/
private OnScrollListener mOnScrollListener; // 列表滾動監聽器
private LayoutInflater mInflater; // 用於加載布局文件
private RelativeLayout mRefreshHeaderView; // 刷新視圖(也就是頭部那部分)
private TextView mRefreshViewText; // 刷新提示文本
private ImageView mRefreshViewImage; // 刷新向上向下的那個圖片
private ProgressBar mRefreshViewProgress; // 這裡是圓形進度條
private TextView mRefreshViewLastUpdated; // 最近更新的文本
private RelativeLayout mLoadMoreFooterView; // 加載更多
private TextView mLoadMoreText; // 提示文本
private ProgressBar mLoadMoreProgress; // 加載更多進度條
private int mCurrentScrollState; // 當前滾動位置
private int mRefreshState; // 刷新狀態
private int mLoadState; // 加載狀態
private RotateAnimation mFlipAnimation; // 下拉動畫
private RotateAnimation mReverseFlipAnimation; // 恢復動畫
private int mRefreshViewHeight; // 刷新視圖高度
private int mRefreshOriginalTopPadding; // 原始上部間隙
private int mLastMotionY; // 記錄點擊位置
public PullToRefreshListView(Context context) {
super(context);
init(context);
}
public PullToRefreshListView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public PullToRefreshListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
private void init(Context context) {
// Load all of the animations we need in code rather than through XML
/** 定義旋轉動畫**/
// 參數:1.旋轉開始的角度 2.旋轉結束的角度 3. X軸伸縮模式 4.X坐標的伸縮值 5.Y軸的伸縮模式 6.Y坐標的伸縮值
mFlipAnimation = new RotateAnimation(0, -180,
RotateAnimation.RELATIVE_TO_SELF, 0.5f,
RotateAnimation.RELATIVE_TO_SELF, 0.5f);
mFlipAnimation.setInterpolator(new LinearInterpolator());
mFlipAnimation.setDuration(250); // 設置持續時間
mFlipAnimation.setFillAfter(true); // 動畫執行完是否停留在執行完的狀態
mReverseFlipAnimation = new RotateAnimation(-180, 0,
RotateAnimation.RELATIVE_TO_SELF, 0.5f,
RotateAnimation.RELATIVE_TO_SELF, 0.5f);
mReverseFlipAnimation.setInterpolator(new LinearInterpolator());
mReverseFlipAnimation.setDuration(250);
mReverseFlipAnimation.setFillAfter(true);
// 獲取LayoutInflater實例對象
mInflater = (LayoutInflater) context.getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
// 加載下拉刷新的頭部視圖
mRefreshHeaderView = (RelativeLayout) mInflater.inflate(
R.layout.pull_to_refresh_header, this, false);
mRefreshViewText =
(TextView) mRefreshHeaderView.findViewById(R.id.pull_to_refresh_text);
mRefreshViewImage =
(ImageView) mRefreshHeaderView.findViewById(R.id.pull_to_refresh_image);
mRefreshViewProgress =
(ProgressBar) mRefreshHeaderView.findViewById(R.id.pull_to_refresh_progress);
mRefreshViewLastUpdated =
(TextView) mRefreshHeaderView.findViewById(R.id.pull_to_refresh_updated_at);
mLoadMoreFooterView = (RelativeLayout) mInflater.inflate(
R.layout.loadmore_footer, this, false);
mLoadMoreText = (TextView) mLoadMoreFooterView.findViewById(R.id.loadmore_text);
mLoadMoreProgress = (ProgressBar) mLoadMoreFooterView.findViewById(R.id.loadmore_progress);
mRefreshViewImage.setMinimumHeight(50); // 設置圖片最小高度
mRefreshHeaderView.setOnClickListener(new OnClickRefreshListener());
mRefreshOriginalTopPadding = mRefreshHeaderView.getPaddingTop();
mLoadMoreFooterView.setOnClickListener(new OnClickLoadMoreListener());
mRefreshState = TAP_TO_REFRESH; // 初始刷新狀態
mLoadState = TAP_TO_LOADMORE;
addHeaderView(mRefreshHeaderView); // 增加頭部視圖
addFooterView(mLoadMoreFooterView); // 增加尾部視圖
super.setOnScrollListener(this);
measureView(mRefreshHeaderView); // 測量視圖
mRefreshViewHeight = mRefreshHeaderView.getMeasuredHeight(); // 得到視圖的高度
}
@Override
protected void onAttachedToWindow() {
setSelection(1); // 設置當前選中的項
}
@Override
public void setAdapter(ListAdapter adapter) {
super.setAdapter(adapter);
setSelection(1);
}
/**
* Set the listener that will receive notifications every time the list
* scrolls.
*
* @param l The scroll listener.
*/
@Override
public void setOnScrollListener(AbsListView.OnScrollListener l) {
mOnScrollListener = l;
}
/**
* Register a callback to be invoked when this list should be refreshed.
* 注冊監聽器
* @param onRefreshListener The callback to run.
*/
public void setOnRefreshListener(OnRefreshListener onRefreshListener) {
mOnRefreshListener = onRefreshListener;
}
/**
* Set a text to represent when the list was last updated.
* 設置一個文本來表示最近更新的列表,顯示的是最近更新列表的時間
* @param lastUpdated Last updated at.
*/
public void setLastUpdated(CharSequence lastUpdated) {
if (lastUpdated != null) {
mRefreshViewLastUpdated.setVisibility(View.VISIBLE);
mRefreshViewLastUpdated.setText("更新於: " + lastUpdated);
} else {
mRefreshViewLastUpdated.setVisibility(View.GONE);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
final int y = (int) event.getY(); // 獲取點擊位置的Y坐標
switch (event.getAction()) {
case MotionEvent.ACTION_UP: // 手指抬起
if (!isVerticalScrollBarEnabled()) {
setVerticalScrollBarEnabled(true);
}
if (getFirstVisiblePosition() == 0 && mRefreshState != REFRESHING) {
if ((mRefreshHeaderView.getBottom() > mRefreshViewHeight
|| mRefreshHeaderView.getTop() >= 0)
&& mRefreshState == RELEASE_TO_REFRESH) {
// Initiate the refresh
mRefreshState = REFRESHING; // 刷新狀態
prepareForRefresh();
onRefresh();
} else if (mRefreshHeaderView.getBottom() < mRefreshViewHeight
|| mRefreshHeaderView.getTop() < 0) {
// Abort refresh and scroll down below the refresh view
resetHeader();
setSelection(1);
}
}
break;
case MotionEvent.ACTION_DOWN:
mLastMotionY = y;
break;
case MotionEvent.ACTION_MOVE:
applyHeaderPadding(event);
break;
}
return super.onTouchEvent(event);
}
private void applyHeaderPadding(MotionEvent ev) {
final int historySize = ev.getHistorySize();
// Workaround for getPointerCount() which is unavailable in 1.5
// (it's always 1 in 1.5)
int pointerCount = 1;
try {
Method method = MotionEvent.class.getMethod("getPointerCount");
pointerCount = (Integer)method.invoke(ev);
} catch (NoSuchMethodException e) {
pointerCount = 1;
} catch (IllegalArgumentException e) {
throw e;
} catch (IllegalAccessException e) {
System.err.println("unexpected " + e);
} catch (InvocationTargetException e) {
System.err.println("unexpected " + e);
}
for (int h = 0; h < historySize; h++) {
for (int p = 0; p < pointerCount; p++) {
if (mRefreshState == RELEASE_TO_REFRESH) {
if (isVerticalFadingEdgeEnabled()) {
setVerticalScrollBarEnabled(false);
}
int historicalY = 0;
try {
// For Android > 2.0
Method method = MotionEvent.class.getMethod(
"getHistoricalY", Integer.TYPE, Integer.TYPE);
historicalY = ((Float) method.invoke(ev, p, h)).intValue();
} catch (NoSuchMethodException e) {
// For Android < 2.0
historicalY = (int) (ev.getHistoricalY(h));
} catch (IllegalArgumentException e) {
throw e;
} catch (IllegalAccessException e) {
System.err.println("unexpected " + e);
} catch (InvocationTargetException e) {
System.err.println("unexpected " + e);
}
// Calculate the padding to apply, we divide by 1.7 to
// simulate a more resistant effect during pull.
int topPadding = (int) (((historicalY - mLastMotionY)
- mRefreshViewHeight) / 1.7);
// 設置上、下、左、右四個位置的間隙間隙
mRefreshHeaderView.setPadding(
mRefreshHeaderView.getPaddingLeft(),
topPadding,
mRefreshHeaderView.getPaddingRight(),
mRefreshHeaderView.getPaddingBottom());
}
}
}
}
/**
* Sets the header padding back to original size.
* 設置頭部填充會原始大小
*/
private void resetHeaderPadding() {
mRefreshHeaderView.setPadding(
mRefreshHeaderView.getPaddingLeft(),
mRefreshOriginalTopPadding,
mRefreshHeaderView.getPaddingRight(),
mRefreshHeaderView.getPaddingBottom());
}
/**
* Resets the header to the original state.
* 重新設置頭部為原始狀態
*/
private void resetHeader() {
if (mRefreshState != TAP_TO_REFRESH) {
mRefreshState = TAP_TO_REFRESH;
resetHeaderPadding();
// Set refresh view text to the pull label
mRefreshViewText.setText(R.string.pull_to_refresh_tap_label);
// Replace refresh drawable with arrow drawable
mRefreshViewImage.setImageResource(R.drawable.ic_pulltorefresh_arrow);
// Clear the full rotation animation
mRefreshViewImage.clearAnimation();
// Hide progress bar and arrow.
mRefreshViewImage.setVisibility(View.GONE);
mRefreshViewProgress.setVisibility(View.GONE);
}
}
/**
* 重設ListView尾部視圖為初始狀態
*/
private void resetFooter() {
if(mLoadState != TAP_TO_LOADMORE) {
mLoadState = TAP_TO_LOADMORE;
// 進度條設置為不可見
mLoadMoreProgress.setVisibility(View.GONE);
// 按鈕的文本替換為“加載更多”
mLoadMoreText.setText(R.string.loadmore_label);
}
}
/**
* 測量視圖的大小
* @param child
*/
private void measureView(View child) {
ViewGroup.LayoutParams p = child.getLayoutParams();
if (p == null) {
p = new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
}
int childWidthSpec = ViewGroup.getChildMeasureSpec(0,
0 + 0, p.width);
int lpHeight = p.height;
int childHeightSpec;
if (lpHeight > 0) {
childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
} else {
childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
}
child.measure(childWidthSpec, childHeightSpec);
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
// When the refresh view is completely visible, change the text to say
// "Release to refresh..." and flip the arrow drawable.
if (mCurrentScrollState == SCROLL_STATE_TOUCH_SCROLL
&& mRefreshState != REFRESHING) {
if (firstVisibleItem == 0) { // 如果第一個可見條目為0
mRefreshViewImage.setVisibility(View.VISIBLE); // 讓指示箭頭變得可見
/**如果頭部視圖相對與父容器的位置大於其自身高度+20或者頭部視圖的頂部位置>0,並且要在刷新狀態不等於"釋放以刷新"**/
if ((mRefreshHeaderView.getBottom() > mRefreshViewHeight + 20
|| mRefreshHeaderView.getTop() >= 0)
&& mRefreshState != RELEASE_TO_REFRESH) {
mRefreshViewText.setText(R.string.pull_to_refresh_release_label);// 設置刷新文本為"Release to refresh..."
mRefreshViewImage.clearAnimation(); // 清除動畫
mRefreshViewImage.startAnimation(mFlipAnimation); // 啟動動畫
mRefreshState = RELEASE_TO_REFRESH; // 更改刷新狀態為“釋放以刷新"
} else if (mRefreshHeaderView.getBottom() < mRefreshViewHeight + 20
&& mRefreshState != PULL_TO_REFRESH) {
mRefreshViewText.setText(R.string.pull_to_refresh_pull_label);// 設置刷新文本為"Pull to refresh..."
if (mRefreshState != TAP_TO_REFRESH) {
mRefreshViewImage.clearAnimation();
mRefreshViewImage.startAnimation(mReverseFlipAnimation);
}
mRefreshState = PULL_TO_REFRESH;
}
} else {
mRefreshViewImage.setVisibility(View.GONE); // 讓刷新箭頭不可見
resetHeader(); // 重新設置頭部為原始狀態
}
} else if (mCurrentScrollState == SCROLL_STATE_FLING
&& firstVisibleItem == 0
&& mRefreshState != REFRESHING) {
setSelection(1);
}
if (mOnScrollListener != null) {
mOnScrollListener.onScroll(view, firstVisibleItem,
visibleItemCount, totalItemCount);
}
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
mCurrentScrollState = scrollState;
if (mOnScrollListener != null) {
mOnScrollListener.onScrollStateChanged(view, scrollState);
}
}
/**為刷新做准備**/
public void prepareForRefresh() {
resetHeaderPadding();
mRefreshViewImage.setVisibility(View.GONE); // 去掉刷新的箭頭
// We need this hack, otherwise it will keep the previous drawable.
mRefreshViewImage.setImageDrawable(null);
mRefreshViewProgress.setVisibility(View.VISIBLE); // 圓形進度條變為可見
// Set refresh view text to the refreshing label
mRefreshViewText.setText(R.string.pull_to_refresh_refreshing_label);
mRefreshState = REFRESHING;
}
/**為加載更多做准備**/
public void prepareForLoadMore() {
mLoadMoreProgress.setVisibility(View.VISIBLE);
mLoadMoreText.setText(R.string.loading_label);
mLoadState = LOADING;
}
public void onRefresh() {
Log.d(TAG, "onRefresh");
if (mOnRefreshListener != null) {
mOnRefreshListener.onRefresh();
}
}
public void OnLoadMore() {
Log.d(TAG, "onLoadMore");
if(mOnRefreshListener != null) {
mOnRefreshListener.onLoadMore();
}
}
/**
* Resets the list to a normal state after a refresh.
* @param lastUpdated Last updated at.
*/
public void onRefreshComplete(CharSequence lastUpdated) {
setLastUpdated(lastUpdated); // 顯示更新時間
onRefreshComplete();
}
/**
* Resets the list to a normal state after a refresh.
*/
public void onRefreshComplete() {
Log.d(TAG, "onRefreshComplete");
resetHeader();
// If refresh view is visible when loading completes, scroll down to
// the next item.
if (mRefreshHeaderView.getBottom() > 0) {
invalidateViews();
setSelection(1);
}
}
public void onLoadMoreComplete() {
Log.d(TAG, "onLoadMoreComplete");
resetFooter();
}
/**
* Invoked when the refresh view is clicked on. This is mainly used when
* there's only a few items in the list and it's not possible to drag the
* list.
* 點擊刷新
*/
private class OnClickRefreshListener implements OnClickListener {
@Override
public void onClick(View v) {
if (mRefreshState != REFRESHING) {
prepareForRefresh();
onRefresh();
}
}
}
/**
*
* @author wwj
* 加載更多
*/
private class OnClickLoadMoreListener implements OnClickListener {
@Override
public void onClick(View v) {
if(mLoadState != LOADING) {
prepareForLoadMore();
OnLoadMore();
}
}
}
/**
* Interface definition for a callback to be invoked when list should be
* refreshed.
* 接口定義一個回調方法當列表應當被刷新
*/
public interface OnRefreshListener {
/**
* Called when the list should be refreshed.
* 當列表應當被刷新是調用這個方法
*
* A call to {@link PullToRefreshListView #onRefreshComplete()} is * expected to indicate that the refresh has completed. */ public void onRefresh(); public void onLoadMore(); } }
package com.markupartist.android.example.pulltorefresh;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.LinkedList;
import android.app.Activity;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Bundle;
import android.widget.ArrayAdapter;
import com.markupartist.android.widget.PullToRefreshListView;
import com.markupartist.android.widget.PullToRefreshListView.OnRefreshListener;
public class PullToRefreshActivity extends Activity {
private LinkedList mListItems;
public static PullToRefreshListView weiboListView;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.pull_to_refresh);
weiboListView = (PullToRefreshListView) findViewById(R.id.weibolist);
// Set a listener to be invoked when the list should be refreshed.
weiboListView.setOnRefreshListener(new OnRefreshListener() {
@Override
public void onRefresh() {
// Do work to refresh the list here.
new GetDataTask(PullToRefreshActivity.this, 0).execute();
}
@Override
public void onLoadMore() {
new GetDataTask(PullToRefreshActivity.this, 1).execute();
}
});
mListItems = new LinkedList();
mListItems.addAll(Arrays.asList(mStrings));
ArrayAdapter adapter = new ArrayAdapter(this,
android.R.layout.simple_list_item_1, mListItems);
weiboListView.setAdapter(adapter);
}
private class GetDataTask extends AsyncTask {
private Context context;
private int index;
public GetDataTask(Context context, int index) {
this.context = context;
this.index = index;
}
@Override
protected String[] doInBackground(Void... params) {
// Simulates a background job.
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
;
}
return mStrings;
}
@Override
protected void onPostExecute(String[] result) {
if (index == 0) {
// 將字符串“Added after refresh”添加到頂部
mListItems.addFirst("Added after refresh...");
SimpleDateFormat format = new SimpleDateFormat(
"yyyy年MM月dd日 HH:mm");
String date = format.format(new Date());
// Call onRefreshComplete when the list has been refreshed.
weiboListView.onRefreshComplete(date);
} else if (index == 1) {
mListItems.addLast("Added after loadmore...");
weiboListView.onLoadMoreComplete();
}
super.onPostExecute(result);
}
}
public static String[] mStrings = { "一條微博", "兩條微博", "三條微博", "四條微博", "五條微博",
"六條微博", "七條微博", "八條微博", "九條微博", "十條微博", "十一條微博", "十二條微博" };
}
/2013.08.22_PullToRefresh_ListView_Demo/res/layout/pull_to_refresh_header.xml
/2013.08.22_PullToRefresh_ListView_Demo/res/layout/loadmore_footer.xml
Android UI之FrameLayout(幀布局)
Android UI之FrameLayout(幀布局)說明:幀布局會為每個包含其中的組件開辟一個空白區域(稱為幀),這些幀是一層層疊加在一起的,有點類似於一層層覆蓋貼上去
Android增量升級簡單實現(附源碼)
隨著現在手機硬件不斷的提升,分辨率提高手機的安裝包也是越來越大了。當年NOKIA,MOTO時代,一個手機APP如果有1MB那都是算大的,2MB已經不得了了。雖然網
Android網絡編程TCP、UDP(三)——UDP實例:搜索局域網所有的設備
接上面的UDP,本篇主要討論如何在局域網中搜索所有的設備,這個需求在物聯網應用的比較多,也比較綜合,特把名字加在標題中了。最後一塊是網絡編程的常見問題。3.6 實例:在局
Android仿UC浏覽器左右上下滾動功能
本文要解決在側滑菜單右邊加個文本框,並能實現文本的上下滑動和菜單的左右滾動。這裡推薦可以好好看看android的觸摸事件的分發機制,這裡我就不詳細講了,我只講講這個應用。