android ListView的上部下拉重新整理下部點選載入更多具體實現及拓展
這次就不上圖了,例子太多太多了,想必大家都見過.這個功能的實現,簡直是開發者必備的.
我也不過多介紹了,網上詳細介紹的部落格太多太多了,若想深入瞭解,請參考網上其他博文.
在這裡,我只是按照自己的理解,模擬實現了一個,順便程式碼貢獻出來.
我對之詳細標明的註釋,想必如果不懂的同學們,看註釋也應該明白,前提是,你要耐心看,因為程式碼有點多,但是我整理過了,還算清晰.
詳細程式碼:
package com.jj.drag; 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.View.OnClickListener; import android.view.ViewGroup; import android.view.animation.Animation; 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.LinearLayout; import android.widget.ListView; import android.widget.ProgressBar; import android.widget.RelativeLayout; import android.widget.TextView; /*** * 自定義拖拉ListView * * @author zhangjia * */ public class DragListView extends ListView implements OnScrollListener, OnClickListener { // 拖拉ListView列舉所有狀態 private enum DListViewState { LV_NORMAL, // 普通狀態 LV_PULL_REFRESH, // 下拉狀態(為超過mHeadViewHeight) LV_RELEASE_REFRESH, // 鬆開可重新整理狀態(超過mHeadViewHeight) LV_LOADING;// 載入狀態 } // 點選載入更多列舉所有狀態 private enum DListViewLoadingMore { LV_NORMAL, // 普通狀態 LV_LOADING, // 載入狀態 LV_OVER; // 結束狀態 } private View mHeadView;// 頭部headView private TextView mRefreshTextview; // 重新整理msg(mHeadView) private TextView mLastUpdateTextView;// 更新事件(mHeadView) private ImageView mArrowImageView;// 下拉圖示(mHeadView) private ProgressBar mHeadProgressBar;// 重新整理進度體(mHeadView) private int mHeadViewWidth; // headView的寬(mHeadView) private int mHeadViewHeight;// headView的高(mHeadView) private View mFootView;// 尾部mFootView private View mLoadMoreView;// mFootView 的view(mFootView) private TextView mLoadMoreTextView;// 載入更多.(mFootView) private View mLoadingView;// 載入中...View(mFootView) private Animation animation, reverseAnimation;// 旋轉動畫,旋轉動畫之後旋轉動畫. private int mFirstItemIndex = -1;// 當前檢視能看到的第一個項的索引 // 用於保證startY的值在一個完整的touch事件中只被記錄一次 private boolean mIsRecord = false; private int mStartY, mMoveY;// 按下是的y座標,move時的y座標 private DListViewState mlistViewState = DListViewState.LV_NORMAL;// 拖拉狀態.(自定義列舉) private DListViewLoadingMore loadingMoreState = DListViewLoadingMore.LV_NORMAL;// 載入更多預設狀態. private final static int RATIO = 2;// 手勢下拉距離比. private boolean mBack = false;// headView是否返回. private OnRefreshLoadingMoreListener onRefreshLoadingMoreListener;// 下拉重新整理介面(自定義) private boolean isScroller = true;// 是否遮蔽ListView滑動。 public DragListView(Context context) { super(context, null); initDragListView(context); } public DragListView(Context context, AttributeSet attrs) { super(context, attrs); initDragListView(context); } // 注入下拉重新整理介面 public void setOnRefreshListener( OnRefreshLoadingMoreListener onRefreshLoadingMoreListener) { this.onRefreshLoadingMoreListener = onRefreshLoadingMoreListener; } /*** * 初始化ListView */ public void initDragListView(Context context) { String time = "1994.12.05";// 更新時間 initHeadView(context, time);// 初始化該head. initLoadMoreView(context);// 初始化footer setOnScrollListener(this);// ListView滾動監聽 } /*** * 初始話頭部HeadView * * @param context * 上下文 * @param time * 上次更新時間 */ public void initHeadView(Context context, String time) { mHeadView = LayoutInflater.from(context).inflate(R.layout.head, null); mArrowImageView = (ImageView) mHeadView .findViewById(R.id.head_arrowImageView); mArrowImageView.setMinimumWidth(60); mHeadProgressBar = (ProgressBar) mHeadView .findViewById(R.id.head_progressBar); mRefreshTextview = (TextView) mHeadView .findViewById(R.id.head_tipsTextView); mLastUpdateTextView = (TextView) mHeadView .findViewById(R.id.head_lastUpdatedTextView); // 顯示更新事件 mLastUpdateTextView.setText("最近更新:" + time); measureView(mHeadView); // 獲取寬和高 mHeadViewWidth = mHeadView.getMeasuredWidth(); mHeadViewHeight = mHeadView.getMeasuredHeight(); addHeaderView(mHeadView, null, false);// 將初始好的ListView add進拖拽ListView // 在這裡我們要將此headView設定到頂部不顯示位置. mHeadView.setPadding(0, -1 * mHeadViewHeight, 0, 0); initAnimation();// 初始化動畫 } /*** * 初始化底部載入更多控制元件 */ private void initLoadMoreView(Context context) { mFootView = LayoutInflater.from(context).inflate(R.layout.footer, null); mLoadMoreView = mFootView.findViewById(R.id.load_more_view); mLoadMoreTextView = (TextView) mFootView .findViewById(R.id.load_more_tv); mLoadingView = (LinearLayout) mFootView .findViewById(R.id.loading_layout); mLoadMoreView.setOnClickListener(this); addFooterView(mFootView); } /*** * 初始化動畫 */ private void initAnimation() { // 旋轉動畫 animation = new RotateAnimation(0, -180, RotateAnimation.RELATIVE_TO_SELF, 0.5f, RotateAnimation.RELATIVE_TO_SELF, 0.5f); animation.setInterpolator(new LinearInterpolator());// 勻速 animation.setDuration(250); animation.setFillAfter(true);// 停留在最後狀態. // 反向旋轉動畫 reverseAnimation = new RotateAnimation(-180, 0, RotateAnimation.RELATIVE_TO_SELF, 0.5f, RotateAnimation.RELATIVE_TO_SELF, 0.5f); reverseAnimation.setInterpolator(new LinearInterpolator()); reverseAnimation.setDuration(250); reverseAnimation.setFillAfter(true); } /*** * 作用:測量 headView的寬和高. * * @param child */ private void measureView(View child) { ViewGroup.LayoutParams p = child.getLayoutParams(); if (p == null) { p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_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); } /*** * touch 事件監聽 */ @Override public boolean onTouchEvent(MotionEvent ev) { switch (ev.getAction()) { // 按下 case MotionEvent.ACTION_DOWN: doActionDown(ev); break; // 移動 case MotionEvent.ACTION_MOVE: doActionMove(ev); break; // 擡起 case MotionEvent.ACTION_UP: doActionUp(ev); break; default: break; } /*** * 如果是ListView本身的拉動,那麼返回true,這樣ListView不可以拖動. * 如果不是ListView的拉動,那麼呼叫父類方法,這樣就可以上拉執行. */ if (isScroller) { return super.onTouchEvent(ev); } else { return true; } } /*** * 摁下操作 * * 作用:獲取摁下是的y座標 * * @param event */ void doActionDown(MotionEvent event) { if (mIsRecord == false && mFirstItemIndex == 0) { mStartY = (int) event.getY(); mIsRecord = true; } } /*** * 拖拽移動操作 * * @param event */ void doActionMove(MotionEvent event) { mMoveY = (int) event.getY();// 獲取實時滑動y座標 // 檢測是否是一次touch事件. if (mIsRecord == false && mFirstItemIndex == 0) { mStartY = (int) event.getY(); mIsRecord = true; } /*** * 如果touch關閉或者正處於Loading狀態的話 return. */ if (mIsRecord == false || mlistViewState == DListViewState.LV_LOADING) { return; } // 向下啦headview移動距離為y移動的一半.(比較友好) int offset = (mMoveY - mStartY) / RATIO; switch (mlistViewState) { // 普通狀態 case LV_NORMAL: { // 如果<0,則意味著上滑動. if (offset > 0) { // 設定headView的padding屬性. mHeadView.setPadding(0, offset - mHeadViewHeight, 0, 0); switchViewState(DListViewState.LV_PULL_REFRESH);// 下拉狀態 } } break; // 下拉狀態 case LV_PULL_REFRESH: { setSelection(0);// 時時保持在頂部. // 設定headView的padding屬性. mHeadView.setPadding(0, offset - mHeadViewHeight, 0, 0); if (offset < 0) { /*** * 要明白為什麼isScroller = false; */ isScroller = false; switchViewState(DListViewState.LV_NORMAL);// 普通狀態 Log.e("jj", "isScroller=" + isScroller); } else if (offset > mHeadViewHeight) {// 如果下拉的offset超過headView的高度則要執行重新整理. switchViewState(DListViewState.LV_RELEASE_REFRESH);// 更新為可重新整理的下拉狀態. } } break; // 可重新整理狀態 case LV_RELEASE_REFRESH: { setSelection(0);時時保持在頂部 // 設定headView的padding屬性. mHeadView.setPadding(0, offset - mHeadViewHeight, 0, 0); // 下拉offset>0,但是沒有超過headView的高度.那麼要goback 原裝. if (offset >= 0 && offset <= mHeadViewHeight) { mBack = true; switchViewState(DListViewState.LV_PULL_REFRESH); } else if (offset < 0) { switchViewState(DListViewState.LV_NORMAL); } else { } } break; default: return; } ; } /*** * 手勢擡起操作 * * @param event */ public void doActionUp(MotionEvent event) { mIsRecord = false;// 此時的touch事件完畢,要關閉。 isScroller = true;// ListView可以Scrooler滑動. mBack = false; // 如果下拉狀態處於loading狀態. if (mlistViewState == DListViewState.LV_LOADING) { return; } // 處理相應狀態. switch (mlistViewState) { // 普通狀態 case LV_NORMAL: break; // 下拉狀態 case LV_PULL_REFRESH: mHeadView.setPadding(0, -1 * mHeadViewHeight, 0, 0); switchViewState(mlistViewState.LV_NORMAL); break; // 重新整理狀態 case LV_RELEASE_REFRESH: mHeadView.setPadding(0, 0, 0, 0); switchViewState(mlistViewState.LV_LOADING); onRefresh();// 下拉重新整理 break; } } // 切換headview檢視 private void switchViewState(DListViewState state) { switch (state) { // 普通狀態 case LV_NORMAL: { mArrowImageView.clearAnimation();// 清除動畫 mArrowImageView.setImageResource(R.drawable.arrow); } break; // 下拉狀態 case LV_PULL_REFRESH: { mHeadProgressBar.setVisibility(View.GONE);// 隱藏進度條 mArrowImageView.setVisibility(View.VISIBLE);// 下拉圖示 mRefreshTextview.setText("下拉可以重新整理"); mArrowImageView.clearAnimation();// 清除動畫 // 是有可重新整理狀態(LV_RELEASE_REFRESH)轉為這個狀態才執行,其實就是你下拉後在上拉會執行. if (mBack) { mBack = false; mArrowImageView.clearAnimation();// 清除動畫 mArrowImageView.startAnimation(reverseAnimation);// 啟動反轉動畫 } } break; // 鬆開重新整理狀態 case LV_RELEASE_REFRESH: { mHeadProgressBar.setVisibility(View.GONE);// 隱藏進度條 mArrowImageView.setVisibility(View.VISIBLE);// 顯示下拉圖示 mRefreshTextview.setText("鬆開獲取更多"); mArrowImageView.clearAnimation();// 清除動畫 mArrowImageView.startAnimation(animation);// 啟動動畫 } break; // 載入狀態 case LV_LOADING: { Log.e("!!!!!!!!!!!", "convert to IListViewState.LVS_LOADING"); mHeadProgressBar.setVisibility(View.VISIBLE); mArrowImageView.clearAnimation(); mArrowImageView.setVisibility(View.GONE); mRefreshTextview.setText("載入中..."); } break; default: return; } // 切記不要忘記時時更新狀態。 mlistViewState = state; } /*** * 下拉重新整理 */ private void onRefresh() { if (onRefreshLoadingMoreListener != null) { onRefreshLoadingMoreListener.onRefresh(); } } /*** * 下拉重新整理完畢 */ public void onRefreshComplete() { mHeadView.setPadding(0, -1 * mHeadViewHeight, 0, 0);// 迴歸. switchViewState(mlistViewState.LV_NORMAL);// } /*** * 點選載入更多 * * @param flag * 資料是否已全部載入完畢 */ public void onLoadMoreComplete(boolean flag) { if (flag) { updateLoadMoreViewState(DListViewLoadingMore.LV_OVER); } else { updateLoadMoreViewState(DListViewLoadingMore.LV_NORMAL); } } // 更新Footview檢視 private void updateLoadMoreViewState(DListViewLoadingMore state) { switch (state) { // 普通狀態 case LV_NORMAL: mLoadingView.setVisibility(View.GONE); mLoadMoreTextView.setVisibility(View.VISIBLE); mLoadMoreTextView.setText("檢視更多"); break; // 載入中狀態 case LV_LOADING: mLoadingView.setVisibility(View.VISIBLE); mLoadMoreTextView.setVisibility(View.GONE); break; // 載入完畢狀態 case LV_OVER: mLoadingView.setVisibility(View.GONE); mLoadMoreTextView.setVisibility(View.VISIBLE); mLoadMoreTextView.setText("載入完畢"); break; default: break; } loadingMoreState = state; } /*** * ListView 滑動監聽 */ @Override public void onScrollStateChanged(AbsListView view, int scrollState) { } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { mFirstItemIndex = firstVisibleItem; } /*** * 底部點選事件 */ @Override public void onClick(View v) { // 防止重複點選 if (onRefreshLoadingMoreListener != null && loadingMoreState == DListViewLoadingMore.LV_NORMAL) { updateLoadMoreViewState(DListViewLoadingMore.LV_LOADING); onRefreshLoadingMoreListener.onLoadMore();// 對外提供方法載入更多. } } /*** * 自定義介面 */ public interface OnRefreshLoadingMoreListener { /*** * // 下拉重新整理執行 */ void onRefresh(); /*** * 點選載入更多 */ void onLoadMore(); } }
上面就是全部程式碼,其實重要的是明白理解,這樣我們還可以進行拓展.
具體應用:(只需要這樣引用即可.)
<com.jj.drag.DragListView
android:id="@+id/dlv_main"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:cacheColorHint="#00000000" />
在Activity中的呼叫,相比大家都清楚,開個非同步或執行緒進行載入資料,這裡我簡單說一下非同步使用,執行緒同理.
程式碼如下:
先宣告一點,這個只是個示例,所以這部分程式碼寫的不夠友好,也請見諒./*** * 執行類 非同步 * * @author zhangjia * */ class MyAsyncTask extends AsyncTask<Void, Void, Void> { private Context context; private int index;// 用於判斷是下拉重新整理還是點選載入更多 public MyAsyncTask(Context context, int index) { this.context = context; this.index = index; } @Override protected Void doInBackground(Void... params) { try { Thread.sleep(2000); } catch (InterruptedException e1) { e1.printStackTrace(); } return null; } @Override protected void onPreExecute() { super.onPreExecute(); } @Override protected void onPostExecute(Void result) { super.onPostExecute(result); if (index == DRAG_INDEX) dlv_main.onRefreshComplete(); else if (index == LOADMORE_INDEX) dlv_main.onLoadMoreComplete(false); } }
就說道這裡,最後展示一下效果:
至於如果顯示,如何adapter.notifyDataSetChanged();那就要大家開發時候自己調理了.
最後說明一點:網上有好多介紹下拉重新整理的例子,但是他們沒有對滑動進行處理,比如,我下拉的時候現在不想重新整理了,這時我又向上滑動,正常的處理,應該滑動到FirstItemIndex=1就是頂部,滑動就結束了.(意思就是要下拉和listview正常滑動要分開)可是網上一些案例都沒有對之處理,用起來不友好,大家可以看看成功案例,那些新浪,騰訊,百度等.
解決方法:(這是onTouch方法中的一部分.)
/***
* 如果是ListView本身的拉動,那麼返回true,這樣ListView不可以拖動.
* 如果不是ListView的拉動,那麼呼叫父類方法,這樣就可以上拉執行.
*/
if (isScroller) {
return super.onTouchEvent(ev);
} else {
return true;
}
要問Why的話,那麼你就要去詳細看Touch種種事件,記住,這裡用到的不是分發與攔截,分發攔截流程如下:
Activity 的dispatchTouchEvent開始分發給子的View,如果該View是ViewGroup的話,那麼執行其dispatchTouchEvent進行分發,在執行相應的onInterceptTouchEvent攔截.如果要想實現上訴說的那種效果,那麼在自定義ListView中對攔截分發方法是無效的,只有在ListView的上一層進行處理,比我我們在外層自定義一個佈局,等等,實現起來總之麻煩一個字,其實我們也可以考慮考慮onTouchEvent事件的實現,
ListView.java
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (mItemsCanFocus && ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) {
// Don't handle edge touches immediately -- they may actually belong to one of our
// descendants.
return false;
}
return super.onTouchEvent(ev);
}
繼續點選檢視父類,這裡就不顯示了,自己可以檢視原始碼,其實就是我們ListView滑動的具體實現,而此時我們只是想臨時遮蔽掉此滑動,那麼我們只需要不呼叫父類的onTouchEvent不就OK的,是的,確實如此,而何時進行遮蔽,大家就仔細看上面原始碼實現吧,解釋的也很清楚,這樣大家都明白了吧。注:有疑問請留言!之前這個例子
知識拓展:
首先我們還是看一些案例:
效果就是可以上下拖拽.而用在最多的地方就是ListView,而普通的佈局拖拽直接自定義佈局就OK了,詳情請參考上面連線那篇文章.
實現起來也不是很麻煩,就是對上面那個自定義類稍作修改,把底部也做成動態拖拽效果就OK了.
這裡不詳細講解,因為註釋相當明確,如有疑問,請指出.
程式碼如下:
package com.jj.drag;
import android.content.Context;
import android.os.AsyncTask;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.animation.Animation;
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.LinearLayout;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.TextView;
/***
* 自定義拖拉ListView
*
* @author zhangjia
*
*/
public class DragListView extends ListView implements OnScrollListener,
OnClickListener {
// 下拉ListView列舉所有狀態
private enum DListViewState {
LV_NORMAL, // 普通狀態
LV_PULL_REFRESH, // 下拉狀態(為超過mHeadViewHeight)
}
// 點選載入更多列舉所有狀態
private enum DListViewLoadingMore {
LV_NORMAL, // 普通狀態
LV_PULL_REFRESH, // 上拉狀態(為超過mHeadViewHeight)
}
private View mHeadView, mFootView;// 頭部headView
private int mHeadViewWidth; // headView的寬(mHeadView)
private int mHeadViewHeight;// headView的高(mHeadView)
private int mFirstItemIndex = -1;// 當前檢視能看到的第一個項的索引
private int mLastItemIndex = -1;// 當前檢視中是否是最後一項.
// 用於保證startY的值在一個完整的touch事件中只被記錄一次
private boolean mIsRecord = false;// 針對下拉
private boolean mIsRecord_B = false;// 針對上拉
private int mStartY, mMoveY;// 按下是的y座標,move時的y座標
private DListViewState mlistViewState = DListViewState.LV_NORMAL;// 拖拉狀態.(自定義列舉)
private DListViewLoadingMore loadingMoreState = DListViewLoadingMore.LV_NORMAL;// 載入更多預設狀態.
private final static int RATIO = 2;// 手勢下拉距離比.
private boolean isScroller = true;// 是否遮蔽ListView滑動。
private MyAsynTask myAsynTask;// 任務
private final static int DRAG_UP = 1, DRAG_DOWN = 2;
public DragListView(Context context) {
super(context, null);
initDragListView(context);
}
public DragListView(Context context, AttributeSet attrs) {
super(context, attrs);
initDragListView(context);
}
/***
* 初始化ListView
*/
public void initDragListView(Context context) {
initHeadView(context);// 初始化該head.
initFooterView(context);// 初始化footer
setOnScrollListener(this);// ListView滾動監聽
}
/***
* 初始話頭部HeadView
*
* @param context
* 上下文
* @param time
* 上次更新時間
*/
public void initHeadView(Context context) {
mHeadView = LayoutInflater.from(context).inflate(R.layout.head, null);
measureView(mHeadView);
// 獲取寬和高
mHeadViewWidth = mHeadView.getMeasuredWidth();
mHeadViewHeight = mHeadView.getMeasuredHeight();
addHeaderView(mHeadView, null, false);// 將初始好的ListView add進拖拽ListView
// 在這裡我們要將此headView設定到頂部不顯示位置.
mHeadView.setPadding(0, -1 * mHeadViewHeight, 0, 0);
}
/***
* 初始化底部載入更多控制元件
*/
private void initFooterView(Context context) {
mFootView = LayoutInflater.from(context).inflate(R.layout.head, null);
addFooterView(mFootView, null, false);// 將初始好的ListView add進拖拽ListView
// 在這裡我們要將此FooterView設定到底部不顯示位置.
mFootView.setPadding(0, -1 * mHeadViewHeight, 0, 0);
}
/***
* 作用:測量 headView的寬和高.
*
* @param child
*/
private void measureView(View child) {
ViewGroup.LayoutParams p = child.getLayoutParams();
if (p == null) {
p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_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);
}
/***
* touch 事件監聽
*/
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
// 按下
case MotionEvent.ACTION_DOWN:
doActionDown_B(ev);
doActionDown(ev);
break;
// 移動
case MotionEvent.ACTION_MOVE:
doActionMove_B(ev);
doActionMove(ev);
break;
// 擡起
case MotionEvent.ACTION_UP:
doActionUp_B(ev);
doActionUp(ev);
break;
default:
break;
}
/***
* 如果是ListView本身的拉動,那麼返回true,這樣ListView不可以拖動.
* 如果不是ListView的拉動,那麼呼叫父類方法,這樣就可以上拉執行.
*/
if (isScroller) {
return super.onTouchEvent(ev);
} else {
return true;
}
}
/***
* 摁下操作
*
* 作用:獲取摁下是的y座標
*
* @param event
*/
void doActionDown(MotionEvent event) {
// 如果是第一項且是一次touch
if (mIsRecord == false && mFirstItemIndex == 0) {
mStartY = (int) event.getY();
mIsRecord = true;
}
}
/***
* 摁下操作 底部
*
* 作用:獲取摁下是的y座標
*/
void doActionDown_B(MotionEvent event) {
// 如果是第一項且是一次touch
if (mIsRecord_B == false && mLastItemIndex == getCount()) {
mStartY = (int) event.getY();
mIsRecord_B = true;
}
}
/***
* 拖拽移動操作
*
* @param event
*/
void doActionMove(MotionEvent event) {
// 判斷是否是第一項,若不是直接返回
mMoveY = (int) event.getY();// 獲取實時滑動y座標
// 檢測是否是一次touch事件.
if (mIsRecord == false && mFirstItemIndex == 0) {
mStartY = (int) event.getY();
mIsRecord = true;
}
// 直接返回說明不是第一項
if (mIsRecord == false)
return;
// 向下啦headview移動距離為y移動的一半.(比較友好)
int offset = (mMoveY - mStartY) / RATIO;
switch (mlistViewState) {
// 普通狀態
case LV_NORMAL: {
// 說明下拉
if (offset > 0) {
// 設定headView的padding屬性.
mHeadView.setPadding(0, offset - mHeadViewHeight, 0, 0);
mlistViewState = DListViewState.LV_PULL_REFRESH;// 下拉狀態
}
}
break;
// 下拉狀態
case LV_PULL_REFRESH: {
setSelection(0);// 時時保持在頂部.
// 設定headView的padding屬性.
mHeadView.setPadding(0, offset - mHeadViewHeight, 0, 0);
if (offset < 0) {
/***
* 要明白為什麼isScroller = false;
*/
isScroller = false;
mlistViewState = mlistViewState.LV_NORMAL;
}
}
break;
default:
return;
}
}
void doActionMove_B(MotionEvent event) {
mMoveY = (int) event.getY();// 獲取實時滑動y座標
// 檢測是否是一次touch事件.(若mFirstItemIndex為0則要初始化mStartY)
if (mIsRecord_B == false && mLastItemIndex == getCount()) {
mStartY = (int) event.getY();
mIsRecord_B = true;
}
// 直接返回說明不是最後一項
if (mIsRecord_B == false)
return;
// 向下啦headview移動距離為y移動的一半.(比較友好)
int offset = (mMoveY - mStartY) / RATIO;
switch (loadingMoreState) {
// 普通狀態
case LV_NORMAL: {
// 說明上拉
if (offset < 0) {
int distance = Math.abs(offset);
// 設定headView的padding屬性.
mFootView.setPadding(0, distance - mHeadViewHeight, 0, 0);
loadingMoreState = loadingMoreState.LV_PULL_REFRESH;// 下拉狀態
}
}
break;
// 上拉狀態
case LV_PULL_REFRESH: {
setSelection(getCount() - 1);// 時時保持最底部
// 設定headView的padding屬性.
int distance = Math.abs(offset);
mFootView.setPadding(0, distance - mHeadViewHeight, 0, 0);
// 說明下滑
if (offset > 0) {
/***
* 要明白為什麼isScroller = false;
*/
isScroller = false;
loadingMoreState = loadingMoreState.LV_NORMAL;
}
}
break;
default:
return;
}
}
/***
* 手勢擡起操作
*
* @param event
*/
public void doActionUp(MotionEvent event) {
mIsRecord = false;// 此時的touch事件完畢,要關閉。
mIsRecord_B = false; // 此時的touch事件完畢,要關閉。
isScroller = true;// ListView可以Scrooler滑動.
mlistViewState = mlistViewState.LV_NORMAL;// 狀態也迴歸最初狀態
// 執行相應動畫.
myAsynTask = new MyAsynTask();
myAsynTask.execute(DRAG_UP);
}
private void doActionUp_B(MotionEvent event) {
mIsRecord = false;// 此時的touch事件完畢,要關閉。
isScroller = true;// ListView可以Scrooler滑動.
loadingMoreState = loadingMoreState.LV_NORMAL;// 狀態也迴歸最初狀態
// 執行相應動畫.
myAsynTask = new MyAsynTask();
myAsynTask.execute(DRAG_DOWN);
}
/***
* ListView 滑動監聽
*/
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
mFirstItemIndex = firstVisibleItem;
mLastItemIndex = firstVisibleItem + visibleItemCount;
}
@Override
public void onClick(View v) {
}
/***
* 用於產生動畫
*
* @author zhangjia
*
*/
private class MyAsynTask extends AsyncTask<Integer, Integer, Void> {
private final static int STEP = 30;// 步伐
private final static int TIME = 5;// 休眠時間
private int distance;// 距離(該距離指的是:mHeadView的PaddingTop+mHeadView的高度,及預設位置狀態.)
private int number;// 迴圈執行次數.
private int disPadding;// 時時padding距離.
private int DRAG;
@Override
protected Void doInBackground(Integer... params) {
try {
this.DRAG = params[0];
if (params[0] == DRAG_UP) {
// 獲取距離.
distance = mHeadView.getPaddingTop()
+ Math.abs(mHeadViewHeight);
} else {
// 獲取距離.
distance = mFootView.getPaddingTop()
+ Math.abs(mHeadViewHeight);
}
// 獲取迴圈次數.
if (distance % STEP == 0) {
number = distance / STEP;
} else {
number = distance / STEP + 1;
}
// 進行迴圈.
for (int i = 0; i < number; i++) {
Thread.sleep(TIME);
publishProgress(STEP);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
switch (DRAG) {
case DRAG_UP:
disPadding = Math.max(mHeadView.getPaddingTop() - STEP, -1
* mHeadViewHeight);
mHeadView.setPadding(0, disPadding, 0, 0);// 迴歸.
break;
case DRAG_DOWN:
disPadding = Math.max(mFootView.getPaddingTop() - STEP, -1
* mHeadViewHeight);
mFootView.setPadding(0, disPadding, 0, 0);// 迴歸.
break;
default:
break;
}
}
}
}
執行效果:
預設效果 下拉拖拽效果(會自動回縮) 上拉拖拽效果(會自動回縮)
前面那章實現起來有點小BUG,正在處理,不過這個實現起來沒有發現什麼BUG,要說BUG的話,那麼就是優化,因為我覺得上面效果是實現了,可是效能覺得有點差,比如說“我每次UP的時候要執行任務,那麼就要建立任務物件,你想想看,每次執行都要建立,那麼要建立多少物件,雖說JAVA虛擬機器會自動回收,但是總覺得不是很完善,嗯,臨時就如此了,自己在研究研究看.
至於微信,陌陌等大多數應用都是(資料少的話,就上下都可以拖拽,只是一個人性效果,而資料多的話,上部用於載入過時資料.下部只是個形式.),效果實現起來也不難,只是進行了些判斷,效果嘛,其實上面自定義ListView整理下就OK了.
上面我詳細給出了兩個自定義原始碼的實現,大家可以直接引用.
在這裡我將原始碼上傳,如果上面看的不明白的話,你可以下載,只要你耐心看,我相信大家都能弄明白,都會進行響應擴充套件的.其實我們要的就是創新,而不是簡單應用.
就說到這裡,如有疑問請留言。
另外,如果對您有幫助的話,記得贊一個哦.
在此:Thanks for you !
相關推薦
android ListView的上部下拉重新整理下部點選載入更多具體實現及拓展
這次就不上圖了,例子太多太多了,想必大家都見過.這個功能的實現,簡直是開發者必備的. 我也不過多介紹了,網上詳細介紹的部落格太多太多了,若想深入瞭解,請參考網上其他博文. 在這裡,我只是按照自己的理解,模擬實現了一個,順便程式碼貢獻出來. 我對之詳細標明的註釋,想必如果不懂
jq實現點選載入更多分頁功能
頁面上實現類似於下拉載入更多的功能,這種是點選載入更多 。 大致思路是: 首先Ajax獲取到下一頁內容,返回json格式資料,如果是跨域請求可以用jsonp返回,通過jq的append()到某個元素後面 此時分頁的page+1,可以在“載入更多”按鈕上把總頁數和當前
懶人載入,點選載入更多
//js //點選載入更多 var page = 1; $(".load_more").on('click',function (){ var thisID = $('.layui-this').attr("lay-id");
點選載入更多先載入5條,然後依次分批載入
html頁面 <center> <div id="more" data-status="1" onclick="app.hospital.dept.busy.loadMore();" style="margin: 20px;dis
點選載入更多外掛
<?PHP 要求( '../類/ connect.php'); 要求( '../類/ db_sql.php'); 要求( '../資料/ dbcache / class.php'); if($ _ POST [action] =='getmorenews'){ $表=用ht
ajax 點選載入更多,出現後面的內容,一次載入十條內容
1.在html部分增加兩句程式碼 <div id="more" data-status="1"> 載入更多 </div> <input type="hidden" id="page" value="2">
vux loadmore + axios 實現點選載入更多
在微信專案中有應用過幾個上拉載入更多的元件,但總會出現一些相容性方面的bug,需要各種補漏(注:元件都是基於iscroll實現的, iscroll原本就有些坑)。Vux也有提供Scroller元件實現上拉載入或下拉重新整理,但官方已經不再維護該元件(未實際使用過,不知是否有坑)。所以這次我們採用更為簡單的方式
PHP+Ajax點選載入更多列表資料例項
一款簡單實用的PHP+Ajax點選載入更多列表資料例項,實現原理:通過“更多”按鈕向服務端傳送Ajax請求,PHP根據分頁引數查詢
Android中ListView下拉重新整理上拉載入更多效果實現
在Android開發中,下拉重新整理和上拉載入更多在很多app中都會有用到,下面就是具體的實現的方法。 首先,我們自定義一個RefreshListView來繼承與ListView,下面是程式碼: package com.example.downrefresh; import
RecyclerView展示固定資料、上拉載入更多、下拉重新整理、點選事件、長按點選事件、刪除條目、重新整理條目、新增條目、多條目載入
1、依賴: implementation 'com.android.support:recyclerview-v7:27.0.2' 2、activity_main、 <?xml version="1.0" encoding="utf-8"?> <Li
Android listview子控制元件的的點選事件(轉)
1.先看圖,是否是你想要的 2.佈局檔案<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" androi
微信小程式 下拉重新整理/上拉載入更多 (上拉載入更多怎麼實現)
實現原理: 1、下拉重新整理:由於小程式資料是實時渲染的。我們把data{}內的資料清空重新載入即可實現下拉重新整理。 2、上拉載入更多(頁面上拉觸底事件):新獲取的資料追加到data{}內的原
Android listview的item中button的點選事件 item和button 可同時點選
此種應用情形在android開發中會遇到很遇到很多 在此闡述一下我的解決方案 第一步 給一個listview xml 可根據自己需要自己佈局 第二步 給listview 一個adapter 我們需要在 adapter 中 定義一個點選響應介面 OnCl
Android Demo之旅 ListView底部新增載入更多按鈕實現資料分頁
在我們的實際專案中,資料應該說是很多的,我們的ListView不可能一下子把資料全部載入進來,我們可以當滾動條滾動到ListView的底部的時候,給一個更多的提示,當我們點選它即載入下一頁的資料,相當與我們的分頁效果,參考網上的東西,寫了一個小小的demo,並總結了一些知識
支付寶賬單分組、重新整理、載入更多效果實現
專案中賬單需要做二期優化,支付軟體嘛當然向支付寶看齊了。。分析了下支付的實現,github了一圈最後回到程式碼中,merge了兩個哥們的開源專案【開源就是好】。。 我主要分析一下整個merge的程式碼: 1:定義我們下拉重新整理和上拉載入的類【兩個
多行文字省略,js點選顯示更多
html+css: <div id="textInfos" style=" overflow : hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2;
簡易資料分析 08 | Web Scraper 翻頁——點選「更多按鈕」翻頁
這是簡易資料分析系列的第 8 篇文章。 我們在Web Scraper 翻頁——控制連結批量抓取資料一文中,介紹了控制網頁連結批量抓取資料的辦法。 但是你在預覽一些網站時,會發現隨著網頁的下拉,你需要點選類似於「載入更多」的按鈕去獲取資料,而網頁連結一直沒有變化。 所以控制連結批量抓去資料的方案失效了,所以
Android ListView 下拉重新整理,上拉載入更多,帶動畫 自定義控制元件
之前每次 專案中用到ListView 的 下拉重新整理 以及上拉分頁載入 都是 用的 網上 下載 的 類庫, 使用起來 諸多不便 ,於是 趁著有空 ,自己封裝了ListView 讓其 實現 下拉重新整理,以及分頁載入功能。 以下是 效果圖: 當 滑動到 ListView 頂
Android分組列表懸停顯示,分組listView懸停效果,帶下拉重新整理和上拉載入更多
分組列表,帶下拉重新整理和上拉載入更多【專案地址在文章最後!!】 效果圖: 實現過程,借鑑PinnedHeadListView,但是該demo沒有下拉重新整理功能,故將該控制元件整合到PullToRefresh 庫中,【PullToRefresh 庫為第
Android scrollview中巢狀listview實現listview的下拉重新整理上拉載入更多
我們都知道在Android中scrollview和listview都能滑動,如果scrollview巢狀listview會出現一些問題,比如listview不能正常顯示item...但是在一些專案中,一些頁面內容比較多,需要在外面放一個scrollview,裡面還會巢狀li