android 學習中使用最多的大量數量集控件 莫過於listview ,雖然就目前來說 google 新推出一個叫recycleView的新空間,並且他本身集合了許多特性,使用起來非常方便。最主要的特性有以下幾點:
**1、控制其顯示的方式,請通過佈局管理器LayoutManager
2、控制Item間的間隔(可繪製),請通過ItemDecoration
3、控制Item增刪的動畫,請通過ItemAnimator
作者: 小灬航航
鏈接:http://www.imooc.com/article/9335
來源:慕課網** 詳細可以點擊鏈接
但是就目前來說大多數初級開發者還是更加喜歡和傾向於使用listview,如何實現listview 中的一些完美特效呢。其實,listview本身是提供了頭部和尾部的控件接口的。我們可以通過addHeaderView(View v,object data, boolean isSelectable);
addFooterView(View v,object data, boolean isSelectable);
這兩個頭部和根部的2個接口,通過自定義的view加進去。可以達到我們的效果了。當然在自定義的時候一些刷新動畫,都是通過在重寫
onTouchEvent(MotionEvent ev)這個觸摸事件來完成的。詳細內容可以看代碼。其中有較爲詳細的註釋。
import java.util.Date;
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.Animation;
import android.view.animation.AnimationUtils;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
/**
* 自定義拉下刷新ListView,常規箭頭旋轉,系統進度條,無回彈動畫
* @author
* @Description
*/
public class MyCusListView extends ListView implements OnScrollListener {
private static final String TAG = "MyCusListView===>";
/** 操作狀態:下拉剛開始、回退到頂、一次刷新結束 */
private static final int DONE = 0x1;
/** 操作狀態:鬆開即可刷新 */
private final static int RELEASE_TO_REFRESH = 0x2;
/** 操作狀態:下拉可以刷新 */
private final static int PULL_TO_REFRESH = 0x3;
/** 操作狀態:正在刷新 */
private final static int REFRESHING = 0x4;
/** 自定義ListView頭佈局 */
private LinearLayout headView;
/** 刷新提示文本 */
private TextView txtHeadTip;
/** 最近刷新時間文本 */
private TextView txtLastRefresh;
/** 下拉箭頭圖標 */
private ImageView imgRefreshArrow;
/** 刷新進度條圖標 */
private ProgressBar pbRefreshRound;
/** headView寬 */
private int headContentWidth;
/** headView高 */
private int headContentHeight;
/** 下拉時箭頭旋轉動畫 */
private Animation pullAnim;
/** 取消時箭頭旋轉動畫 */
private Animation reserveAnim;
/** 標識各種刷新狀態 */
private int refreshState;
/** 首次觸摸屏幕設爲true,鬆手時設爲false,控制一次觸摸事件的記錄狀態 */
private boolean isRecored = false;
/** 手指首次觸摸屏幕時Y位置 */
private int startY;
/** 手指移動的距離和headView的padding距離的比例,防止移動時headView下拉過長 */
private final static int RATIO = 3;
/** 表示已經下拉到可以刷新狀態,可以拉回 */
private boolean isBack = false;
/** 刷新監聽回調接口 */
private OnRefreshListener refreshListener;
/** 列表在屏幕頂端第一個完整可見項的position */
private int firstItemIndex;
public MyCusListView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
/** 初始化 */
private void init(Context context) {
Log.i(TAG, "init()...");
// 獲取自定義頭view
headView = (LinearLayout) LayoutInflater.from(context).inflate(
R.layout.head_cus_listview, null);
// 獲取headView中控件
imgRefreshArrow = (ImageView) headView
.findViewById(R.id.imgRefreshArrow);
pbRefreshRound = (ProgressBar) headView
.findViewById(R.id.pbRefreshRound);
txtHeadTip = (TextView) headView.findViewById(R.id.txtHeadTip);
txtLastRefresh = (TextView) headView.findViewById(R.id.txtLastRefresh);
// 預估headView寬高
measureView(headView);
// 獲取headView寬高
headContentWidth = headView.getMeasuredWidth();
headContentHeight = headView.getMeasuredHeight();
Log.i(TAG, "headView寬:[" + headContentWidth + "],高:["
+ headContentHeight + "]");
// 設置headView的padding值,topPadding爲其本身的負值,達到在屏幕中隱藏的效果
headView.setPadding(0, -headContentHeight, 0, 0);
// 重繪headView
headView.invalidate();
// 將headView添加到自定義的ListView頭部
this.addHeaderView(headView, null, false);
// 設置ListView的滑動監聽
this.setOnScrollListener(this);
// 獲取箭頭旋轉動畫
pullAnim = AnimationUtils.loadAnimation(context, R.anim.arrow_rotate);
reserveAnim = AnimationUtils.loadAnimation(context,
R.anim.arrow_rotate_reverse);
// 初始刷新狀態
refreshState = DONE;
}
/**
* 預估headView的寬高
*
* @param child
*/
private void measureView(View child) {
Log.i(TAG, "measureView()...");
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
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
// 記錄滾動時列表第一個完整可見項的position
firstItemIndex = firstVisibleItem;
}
/** 監聽觸摸事件 */
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:// 第一次觸摸時
if (firstItemIndex == 0) {
// 開始記錄
isRecored = true;
// 獲取首次Y位置
startY = (int) ev.getY();
Log.i(TAG, "從首次觸摸點就開始記錄...");
} else {
Log.i(TAG, "首次觸摸時firstItemIndex不爲0,不執行下拉刷新");
}
Log.i(TAG, "記錄狀態isRecored:" + isRecored);
break;
case MotionEvent.ACTION_UP:// 鬆開屏幕時
// 移除記錄
isRecored = false;
Log.i(TAG, "停止記錄..." + ",isRecored:" + isRecored);
if (refreshState == PULL_TO_REFRESH) {
refreshState = DONE;
changeHeadView();
Log.i(TAG, "PULL_TO_REFRESH狀態鬆手,回到原始狀態");
} else if (refreshState == RELEASE_TO_REFRESH) {
refreshState = REFRESHING;
changeHeadView();
onRefreshing();
Log.i(TAG, "RELEASE_TO_REFRESH狀態鬆手,進入REFRESHING狀態");
} else if (refreshState == REFRESHING) {
if (firstItemIndex == 0) {
// 保持刷新狀態
headView.setPadding(0, -headContentHeight, 0, 0);
Log.i(TAG, "REFRESHING狀態鬆手,保持該狀態,headView仍在頂部");
} else {
Log.i(TAG, "REFRESHING狀態鬆手,保持該狀態,headView被推出頂部");
}
}
break;
case MotionEvent.ACTION_MOVE:// 手勢移動時
// 記錄實時的手指移動時在屏幕的Y位置,用於和startY比較
int curY = (int) ev.getY();
if (!isRecored && firstItemIndex == 0) {
isRecored = true;
Log.i(TAG, "從移動狀態執行下拉刷新,開始記錄..." + ",isRecored:" + isRecored);
startY = curY;
}
if (isRecored) {
// 開始或結束狀態
if (refreshState == DONE) {
if (curY - startY > 0) {// 表示向下拉了
// 狀態改爲下拉刷新
refreshState = PULL_TO_REFRESH;
changeHeadView();
}
}
// 下拉刷新狀態
if (refreshState == PULL_TO_REFRESH) {
setSelection(0);
// 不斷改變headView的高度
headView.setPadding(0, (curY - startY) / RATIO
- headContentHeight, 0, 0);
// 下拉到RELEASE_TO_REFRESH狀態
if ((curY - startY) / RATIO >= headContentHeight * 1.5) {
refreshState = RELEASE_TO_REFRESH;
isBack = true;
changeHeadView();
} else if ((curY - startY) <= 0) {
// 上推到頂
refreshState = DONE;
changeHeadView();
}
}
// 鬆手可以刷新狀態
if (refreshState == RELEASE_TO_REFRESH) {
setSelection(0);
// 不斷改變headView的高度
headView.setPadding(0, (curY - startY) / RATIO
- headContentHeight, 0, 0);
// 又往上推
if ((curY - startY) / RATIO < headContentHeight * 1.5) {
refreshState = PULL_TO_REFRESH;
changeHeadView();
}
}
// 正在刷新狀態
if (refreshState == REFRESHING) {
if (curY - startY > 0) {
// 只改變padding值,不做其餘處理
headView.setPadding(0, (curY - startY) / RATIO, 0, 0);
}
}
}
break;
}
return super.onTouchEvent(ev);
}
/** 進入刷新的方法 */
private void onRefreshing() {
// 調用回調接口中的刷新方法
if (refreshListener != null) {
refreshListener.toRefresh();
}
}
/** 使用界面傳遞給此ListView的回調接口,用於兩者間通信 */
public interface OnRefreshListener {
public void toRefresh();
}
/**
* 註冊一個用於刷新的回調接口
*
* @param refreshListener
*/
public void setOnRefreshListener(OnRefreshListener refreshListener) {
// 獲取傳遞過來的回調接口
this.refreshListener = refreshListener;
}
/** 使用界面執行完刷新操作時調用此方法 */
public void onRefreshFinished() {
refreshState = DONE;
changeHeadView();
// 顯示最近更新
txtLastRefresh.setText("最近更新:" + new Date().toLocaleString());
}
/** 根據下拉的狀態改變headView */
private void changeHeadView() {
switch (refreshState) {
case DONE:// 開始或結束狀態
Log.i(TAG, "當前狀態:DONE");
// 回退狀態清除
isBack = false;
// 回覆原始高度
headView.setPadding(0, -headContentHeight, 0, 0);
// 進度條隱藏
pbRefreshRound.setVisibility(View.GONE);
// 設置原始箭頭圖片
imgRefreshArrow.setImageResource(R.drawable.indicator_arrow);
imgRefreshArrow.setVisibility(View.VISIBLE);
txtHeadTip.setText("下拉可以刷新...");
break;
case PULL_TO_REFRESH:// 下拉刷新狀態
Log.i(TAG, "當前狀態:PULL_TO_REFRESH");
// 從RELEASE_TO_REFRESH回到PULL_TO_REFRESH狀態
Log.i(TAG, "是否從鬆開刷新回到下拉刷新...isBack:" + isBack);
if (isBack) {
// 設置箭頭回轉動畫
imgRefreshArrow.startAnimation(reserveAnim);
}
txtHeadTip.setText("下拉可以刷新...");
break;
case RELEASE_TO_REFRESH:
Log.i(TAG, "當前狀態:RELEASE_TO_REFRESH");
// 設置箭頭旋轉動畫
imgRefreshArrow.startAnimation(pullAnim);
txtHeadTip.setText("鬆開即可刷新...");
break;
case REFRESHING:
startY = 0;
// 保持headView在屏幕頂端顯示
// headView.setPadding(0, 0, 0, 0);
headView.setPadding(0, -headContentHeight, 0, 0);
headView.setVisibility(View.GONE);
refreshState = DONE;
// 顯示出進度條
pbRefreshRound.setVisibility(View.GONE);
// 隱藏箭頭圖標
imgRefreshArrow.clearAnimation();
imgRefreshArrow.setVisibility(View.GONE);
txtHeadTip.setText("正在刷新...");
Log.i(TAG, "當前狀態:REFRESHING");
break;
}
}
@Override
public void setAdapter(ListAdapter adapter) {
super.setAdapter(adapter);
txtLastRefresh.setText("最近更新:" + new Date().toLocaleString());
}
}
這個自定義控件實現了下拉箭頭,和回彈的箭頭。以及刷新時的進度更新動作,並且附帶刷新時間。
自定義headView佈局頁面代碼如下:
<?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" >
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<LinearLayout
android:id="@+id/layout_Text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:gravity="center"
android:orientation="vertical" >
<TextView
android:id="@+id/txtHeadTip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="下拉可以刷新..."
android:textColor="@color/gray"
android:textSize="15sp"
android:textStyle="bold" />
<TextView
android:id="@+id/txtLastRefresh"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="無更新記錄"
android:textColor="@color/lightgray"
android:textSize="13sp" />
</LinearLayout>
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginRight="20dp"
android:layout_toLeftOf="@id/layout_Text" >
<ImageView
android:id="@+id/imgRefreshArrow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@drawable/indicator_arrow" />
<ProgressBar
android:id="@+id/pbRefreshRound"
style="?android:attr/progressBarStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="gone" />
</FrameLayout>
</RelativeLayout>
</LinearLayout>
下面是效果圖: