編輯:關於android開發
上拉加載更多,下拉刷新,網上比較強大比較全的一個開源庫PullToRefresh,支持Listview、GridView、ScrollView等眾多控件。下載地址:
git clone https://github.com/chrisbanes/Android-PullToRefresh.git
噢,伙計,當然你也可以這樣
https://github.com/chrisbanes/Android-PullToRefresh
整個庫先從地基入手PullToRefreshBase,我們必須先了解這個類關聯的類別,先看State枚舉類
public static enum State {
/**
* When the UI is in a state which means that user is not interacting
* with the Pull-to-Refresh function.
* 重置初始化狀態
*/
RESET(0x0),
/**
* When the UI is being pulled by the user, but has not been pulled far
* enough so that it refreshes when released.
* 拉動距離不足指定阈值,進行釋放
*/
PULL_TO_REFRESH(0x1),
/**
* When the UI is being pulled by the user, and has
* been pulled far enough so that it will refresh when released.
* 拉動距離大於等於指定阈值,進行釋放
*/
RELEASE_TO_REFRESH(0x2),
/**
* When the UI is currently refreshing, caused by a pull gesture.
* 由於用戶手勢操作,引起當前UI刷新
*/
REFRESHING(0x8),
/**
* When the UI is currently refreshing, caused by a call to
* {@link PullToRefreshBase#setRefreshing() setRefreshing()}.
* 由於代碼調用setRefreshing引起刷新UI
*/
MANUAL_REFRESHING(0x9),
/**
* When the UI is currently overscrolling, caused by a fling on the
* Refreshable View.
* 由於結束滑動,可以刷新視圖
*/
OVERSCROLLING(0x10);
/**
* Maps an int to a specific state. This is needed when saving state.
* int 映射到狀態,需要保存這個狀態,直白的說:根據index 獲取枚舉類型
* @param stateInt - int to map a State to
* @return State that stateInt maps to
*/
static State mapIntToValue(final int stateInt) {
for (State value : State.values()) {
if (stateInt == value.getIntValue()) {
return value;
}
}
// If not, return default
return RESET;
}
private int mIntValue;
State(int intValue) {
mIntValue = intValue;
}
int getIntValue() {
return mIntValue;
}
}
再來看Mode的枚舉類
public static enum Mode {
/**
* Disable all Pull-to-Refresh gesture and Refreshing handling
* 禁用刷新加載
*/
DISABLED(0x0),
/**
* Only allow the user to Pull from the start of the Refreshable View to
* refresh. The start is either the Top or Left, depending on the
* scrolling direction.
* 僅僅支持下動刷新
*/
PULL_FROM_START(0x1),
/**
* Only allow the user to Pull from the end of the Refreshable View to
* refresh. The start is either the Bottom or Right, depending on the
* scrolling direction.
* 僅僅支持上啦加載更多
*/
PULL_FROM_END(0x2),
/**
* Allow the user to both Pull from the start, from the end to refresh.
* 上啦下拉都支持
*/
BOTH(0x3),
/**
* Disables Pull-to-Refresh gesture handling, but allows manually
* setting the Refresh state via
* {@link PullToRefreshBase#setRefreshing() setRefreshing()}.
* 只允許手動觸發
*/
MANUAL_REFRESH_ONLY(0x4);
/**
* @deprecated Use {@link #PULL_FROM_START} from now on.
* 不贊成使用,過時了
*/
public static Mode PULL_DOWN_TO_REFRESH = Mode.PULL_FROM_START;
/**
* @deprecated Use {@link #PULL_FROM_END} from now on.
*/
public static Mode PULL_UP_TO_REFRESH = Mode.PULL_FROM_END;
/**
* Maps an int to a specific mode. This is needed when saving state, or
* inflating the view from XML where the mode is given through a attr
* int.
*
* @param modeInt - int to map a Mode to
* @return Mode that modeInt maps to, or PULL_FROM_START by default.
*/
static Mode mapIntToValue(final int modeInt) {
for (Mode value : Mode.values()) {
if (modeInt == value.getIntValue()) {
return value;
}
}
// If not, return default
return getDefault();
}
//默認狀態只支持刷新
static Mode getDefault() {
return PULL_FROM_START;
}
private int mIntValue;
// The modeInt values need to match those from attrs.xml
//mode的值要與自定義屬性的值相匹配
Mode(int modeInt) {
mIntValue = modeInt;
}
/**
* @return true if the mode permits Pull-to-Refresh
* 如果當前模式允許刷新則返回true
*/
boolean permitsPullToRefresh() {
return !(this == DISABLED || this == MANUAL_REFRESH_ONLY);
}
/**
* @return true if this mode wants the Loading Layout Header to be shown
* 如果該模式下能加載顯示header部分,則返回true
*/
public boolean showHeaderLoadingLayout() {
return this == PULL_FROM_START || this == BOTH;
}
/**
* @return true if this mode wants the Loading Layout Footer to be shown
* 如果該模式下能加載顯示footer部分,則返回true
*/
public boolean showFooterLoadingLayout() {
return this == PULL_FROM_END || this == BOTH || this == MANUAL_REFRESH_ONLY;
}
int getIntValue() {
return mIntValue;
}
}
動畫相關枚舉類型AnimationStyle
public static enum AnimationStyle {
/**
* This is the default for Android-PullToRefresh. Allows you to use any
* drawable, which is automatically rotated and used as a Progress Bar.
* 默認使用旋轉的進度條 ProgressBar
*/
ROTATE,
/**
* This is the old default, and what is commonly used on iOS. Uses an
* arrow image which flips depending on where the user has scrolled.
* 箭頭圖像翻轉根據用戶手勢
*/
FLIP;
static AnimationStyle getDefault() {
return ROTATE;
}
/**
* Maps an int to a specific mode. This is needed when saving state, or
* inflating the view from XML where the mode is given through a attr
* int.
*
* @param modeInt - int to map a Mode to
* @return Mode that modeInt maps to, or ROTATE by default.
*/
static AnimationStyle mapIntToValue(int modeInt) {
switch (modeInt) {
case 0x0:
default:
return ROTATE;
case 0x1:
return FLIP;
}
}
LoadingLayout createLoadingLayout(Context context, Mode mode, Orientation scrollDirection, TypedArray attrs) {
switch (this) {
case ROTATE:
default:
return new RotateLoadingLayout(context, mode, scrollDirection, attrs);
case FLIP:
return new FlipLoadingLayout(context, mode, scrollDirection, attrs);
}
}
}
HeaderLayout 、FooterLayout對應的接口ILoadingLayout
public interface ILoadingLayout {
/**
* Set the Last Updated Text. This displayed under the main label when
* Pulling
* 最後更新時間
* @param label - Label to set
*/
public void setLastUpdatedLabel(CharSequence label);
/**
* Set the drawable used in the loading layout. This is the same as calling
* setLoadingDrawable(drawable, Mode.BOTH)
* 設置使用的可拉的加載布局的drawable
* @param drawable - Drawable to display
*/
public void setLoadingDrawable(Drawable drawable);
/**
* Set Text to show when the Widget is being Pulled
* setPullLabel(releaseLabel, Mode.BOTH)
* 設置上拉顯示文字
* @param pullLabel - CharSequence to display
*/
public void setPullLabel(CharSequence pullLabel);
/**
* Set Text to show when the Widget is refreshing
* setRefreshingLabel(releaseLabel, Mode.BOTH)
* 設置下拉刷新顯示文字
* @param refreshingLabel - CharSequence to display
*/
public void setRefreshingLabel(CharSequence refreshingLabel);
/**
* Set Text to show when the Widget is being pulled, and will refresh when
* released. This is the same as calling
* setReleaseLabel(releaseLabel, Mode.BOTH)
* 設置釋放顯示文字
* @param releaseLabel - CharSequence to display
*/
public void setReleaseLabel(CharSequence releaseLabel);
/**
* Set's the Sets the typeface and style in which the text should be
* displayed. Please see
* {@link android.widget.TextView#setTypeface(Typeface)
* TextView#setTypeface(Typeface)}.
* 設置字體
*/
public void setTextTypeface(Typeface tf);
}
進入LoadingLayout構造函數
public LoadingLayout(Context context, final Mode mode, final Orientation scrollDirection, TypedArray attrs) {
super(context);
//根據不同方向選擇加載不同布局
mMode = mode;
mScrollDirection = scrollDirection;
switch (scrollDirection) {
case HORIZONTAL:
//Inflater這種用法第一次見到,比較新穎,get..
LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header_horizontal, this);
break;
case VERTICAL:
default:
LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header_vertical, this);
break;
}
mInnerLayout = (FrameLayout) findViewById(R.id.fl_inner);
//自定義屬性的另一種取法
if (attrs.hasValue(R.styleable.PullToRefresh_ptrHeaderBackground)) {
Drawable background = attrs.getDrawable(R.styleable.PullToRefresh_ptrHeaderBackground);
if (null != background) {
//版本分支設置背景
ViewCompat.setBackground(this, background);
}
}
//**************************此處略*******************************
reset();
}
該類內部定義了一系列抽象方法,具體稍後再說,下面接著了解刷新和加載更多的監聽接口OnRefreshListener、OnRefreshListener2
/**
* Simple Listener to listen for any callbacks to Refresh.
* 這個接口只是用與僅僅支持刷新模式
* @author Chris Banes
*/
public static interface OnRefreshListener {
/**
* onRefresh will be called for both a Pull from start, and Pull from
* end
* 下拉結束後能夠刷新(滑動距離>=阈值)調用onRefresh回掉函數
*/
public void onRefresh(final PullToRefreshBase refreshView);
}
/**
* An advanced version of the Listener to listen for callbacks to Refresh.
* This listener is different as it allows you to differentiate between Pull
* Ups, and Pull Downs.
* 當前模式支持刷新和加載更多
* @author Chris Banes
*/
public static interface OnRefreshListener2 {
// TODO These methods need renaming to START/END rather than DOWN/UP
/**
* onPullDownToRefresh will be called only when the user has Pulled from
* the start, and released.
* 下拉刷新
*/
public void onPullDownToRefresh(final PullToRefreshBase refreshView);
/**
* onPullUpToRefresh will be called only when the user has Pulled from
* the end, and released.
* 上啦加載更多
*/
public void onPullUpToRefresh(final PullToRefreshBase refreshView);
}
上啦和下拉的Event事件回調接口類OnPullEventListener
/**
* Listener that allows you to be notified when the user has started or
* finished a touch event. Useful when you want to append extra UI events
* (such as sounds). See (
* {@link PullToRefreshAdapterViewBase#setOnPullEventListener}.
*
* @author Chris Banes
*/
public static interface OnPullEventListener {
/**
* Called when the internal state has been changed, usually by the user
* pulling.
* 通過用戶上下拉引起狀態改變,把觸摸事件回調
* @param refreshView - View which has had it's state change.
* @param state - The new state of View.
* @param direction - One of {@link Mode#PULL_FROM_START} or
* {@link Mode#PULL_FROM_END} depending on which direction
* the user is pulling. Only useful when state is
* {@link State#PULL_TO_REFRESH} or
* {@link State#RELEASE_TO_REFRESH}.
*/
public void onPullEvent(final PullToRefreshBase refreshView, State state, Mode direction);
}
一個滑動相關聯的Runnable 實現類SmoothScrollRunnable以及一個滑動結束的監聽接口OnSmoothScrollFinishedListener
final class SmoothScrollRunnable implements Runnable {
private final Interpolator mInterpolator;
private final int mScrollToY;
private final int mScrollFromY;
private final long mDuration;
private OnSmoothScrollFinishedListener mListener;
private boolean mContinueRunning = true;
private long mStartTime = -1;
private int mCurrentY = -1;
public SmoothScrollRunnable(int fromY, int toY, long duration, OnSmoothScrollFinishedListener listener) {
mScrollFromY = fromY;
mScrollToY = toY;
mInterpolator = mScrollAnimationInterpolator;
mDuration = duration;
mListener = listener;
}
@Override
public void run() {
/**
* Only set mStartTime if this is the first time we're starting,
* else actually calculate the Y delta
*/
if (mStartTime == -1) {
mStartTime = System.currentTimeMillis();
} else {
/**
* We do do all calculations in long to reduce software float
* calculations. We use 1000 as it gives us good accuracy and
* small rounding errors
*/
long normalizedTime = (1000 * (System.currentTimeMillis() - mStartTime)) / mDuration;
normalizedTime = Math.max(Math.min(normalizedTime, 1000), 0);
final int deltaY = Math.round((mScrollFromY - mScrollToY)
* mInterpolator.getInterpolation(normalizedTime / 1000f));
mCurrentY = mScrollFromY - deltaY;
//根據計算的距離設置Hearlayout的滑動,該方法控制HeaderLayout、FooterLayout的顯示與否,同時還根據參數控制硬件加速渲染相關,最終目的調用了scrollTo方法。
setHeaderScroll(mCurrentY);
}
// If we're not at the target Y, keep going...
if (mContinueRunning && mScrollToY != mCurrentY) {
ViewCompat.postOnAnimation(PullToRefreshBase.this, this);
} else {
if (null != mListener) {
//滑動結束了回調
mListener.onSmoothScrollFinished();
}
}
}
public void stop() {
//停止滑動,並移除監聽
mContinueRunning = false;
removeCallbacks(this);
}
}
static interface OnSmoothScrollFinishedListener {
void onSmoothScrollFinished();
}
PullToRefreshBase類構造函數初始化了觸摸敏感系數mTouchSlop,並創建添加HeaderLayout、FooterLayout, 再調用updateUIForMode方法更具Mode修改調整UI,refreshLoadingViewsSize方法調整LoadingLayout相關大小,而影響其本質的因素,先看下面這個方法
private int getMaximumPullScroll() {
switch (getPullToRefreshScrollDirection()) {
case HORIZONTAL:
return Math.round(getWidth() / FRICTION);
case VERTICAL:
default:
return Math.round(getHeight() / FRICTION);
}
}
FRICTION這個參數固定值2.0,根據父控件寬高/固定系數得到(左右上下方向)上拉下拉對應的HeaderLayout 、FooterLayout的寬高,如果我們想縮小HeaderLayout的高度只需要加大固定系數FRICTION,但是的注意,別改得太大了導致布局顯示出問題。
onInterceptTouchEvent方法重寫MotionEvent.ACTION_DOWN && mIsBeingDragged先攔截觸摸事件,在action_move 時,根據設置刷新ing能否繼續滑動的參數以及是否能刷新, 判斷是否攔截觸摸事件if mScrollingWhileRefreshingEnabled && isRefreshing(),以及根據觸摸滑動距離和Mode判斷攔截Touch事件。
當我們HeaderLayout 、FooterLayout視圖彈出,請求完了數據需要隱藏掉它們,這時候就需要用到它
@Override
public final void onRefreshComplete() {
if (isRefreshing()) {
setState(State.RESET);
}
}
setStatue方法裡面調用onReset,繼續跟進發現LoadingLayout調用了reset方法,並且smoothScrollTo方法調用,間接的new 了SmoothScrollRunnable,一個定時長的減速scrollTo動畫執行
/**
* Called when the UI has been to be updated to be in the
* {@link State#RESET} state.
*/
protected void onReset() {
mIsBeingDragged = false;
mLayoutVisibilityChangesEnabled = true;
// Always reset both layouts, just in case...
mHeaderLayout.reset();
mFooterLayout.reset();
smoothScrollTo(0);
}
而onTouchEvent方法 內部則是根據各種狀態判斷設置當前的狀態枚舉類型State
@Override
public final boolean onTouchEvent(MotionEvent event) {
if (!isPullToRefreshEnabled()) {
return false;
}
// If we're refreshing, and the flag is set. Eat the event
if (!mScrollingWhileRefreshingEnabled && isRefreshing()) {
return true;
}
if (event.getAction() == MotionEvent.ACTION_DOWN && event.getEdgeFlags() != 0) {
return false;
}
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE: {
if (mIsBeingDragged) {
mLastMotionY = event.getY();
mLastMotionX = event.getX();
pullEvent();
return true;
}
break;
}
case MotionEvent.ACTION_DOWN: {
if (isReadyForPull()) {
mLastMotionY = mInitialMotionY = event.getY();
mLastMotionX = mInitialMotionX = event.getX();
return true;
}
break;
}
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP: {
if (mIsBeingDragged) {
mIsBeingDragged = false;
if (mState == State.RELEASE_TO_REFRESH
&& (null != mOnRefreshListener || null != mOnRefreshListener2)) {
setState(State.REFRESHING, true);
return true;
}
// If we're already refreshing, just scroll back to the top
if (isRefreshing()) {
smoothScrollTo(0);
return true;
}
// If we haven't returned by here, then we're not in a state
// to pull, so just reset
setState(State.RESET);
return true;
}
break;
}
}
return false;
}
ILoadingLayout 接口的實現類LoadingLayoutProxy ,也是LoadingLayout的代理,通過HashSet存儲LoadingLayout,設置LoadingLayout的屬性則通過該代理來設置,實例如下:
/**
* @deprecated You should now call this method on the result of
* {@link #getLoadingLayoutProxy()}.
*/
public void setPullLabel(CharSequence pullLabel) {
getLoadingLayoutProxy().setPullLabel(pullLabel);
}
方法setRefreshing (boolean ) 原理是在改變State狀態,從而改變ui
@Override
public final void setRefreshing(boolean doScroll) {
if (!isRefreshing()) {
setState(State.MANUAL_REFRESHING, doScroll);
}
}
onPullToRefresh方法根據mCurrentMode調用HeaderLayout、FooterLayout(LoadingLayout)各自的抽象方法具體實現稍後再說,諸如此類方法就不一一列舉
/**
* Called when the UI has been to be updated to be in the
* {@link State#PULL_TO_REFRESH} state.
*/
protected void onPullToRefresh() {
switch (mCurrentMode) {
case PULL_FROM_END:
mFooterLayout.pullToRefresh();
break;
case PULL_FROM_START:
mHeaderLayout.pullToRefresh();
break;
default:
// NO-OP
break;
}
}
基本涉及到的類別粗略過了一遍,接著我們挨著來了解怎麼用這些自定義控件,至於這些控件源碼就不分析了,大同小異,代碼量太大太累了
首先需要在xml引用控件,activity獲取實例得到PullToRefreshListView,進行初始化
protected void initialPullToRefreshListView() {
adapter = new SimpleAdapter(this, null);
mListView = mPullToRefreshListView.getRefreshableView();
mListView.setAdapter(adapter);
//Adapter的List.size=0的時候用到的,建議工廠生產view以適應多種情景
mPullToRefreshListView.setEmptyView(getEmptyView());
//不能刷新
mPullToRefreshListView.setMode(Mode.DISABLED);
onRefresh();
}
調用方法刷新獲取數據,改變Mode和監聽,setOnLastItemVisibleListener是否滑動到底部的監聽,而mPullToRefreshListView.getOnRefreshListener()相關方法在源碼中不存在,自己添加的一個返回方法
public void onRefresh() {
mPullToRefreshListView.postDelayed(new Runnable() {
@Override
public void run() {
pageSize=0;
int [] arrays ={5,10};
int size = new Random().nextInt(2);
size = arrays[size];
adapter.onRefresh(getData(pageSize, size));
mPullToRefreshListView.onRefreshComplete();
if (size == 10) {
pageSize++;
setRefreshListener2();
mPullToRefreshListView
.setOnLastItemVisibleListener(null);
}else {
if(size==0){
mPullToRefreshListView.setMode(Mode.PULL_FROM_START);
Toast.makeText(getApplicationContext(), "暫無更多數據,請稍後再試", Toast.LENGTH_SHORT).show();
}
if(mPullToRefreshListView.getMode()!=Mode.PULL_FROM_START||mPullToRefreshListView.getOnRefreshListener()==null){
setRefreshListener1();
}
}
}
}, 3000);
}
setRefreshListener1 和setRefreshListener2方法分別是支持只刷新和刷新加載更多都支持的接口building,例如setRefreshLisenter1:
public void setRefreshListener1() {
mPullToRefreshListView.setMode(Mode.PULL_FROM_START);
mPullToRefreshListView
.setOnRefreshListener(new OnRefreshListener() {
@Override
public void onRefresh(
PullToRefreshBase refreshView) {
setLable(refreshView);
MainActivity.this.onRefresh();
}
});
setLastItemVisibleListener();
}
GridView、ViewPager、ExpandListView、WebView等相關控件的關於刷新加載更多的這塊的調用實例都大同小異,不一一列舉,如果實在搞不定可以參考官方simple,這裡有個實踐:PullToRefreshScrollView+NoScrollListView 實現不能滑動的ListView嵌套到PullToRefreshScrollView裡面,隨便添加header 或者其他任意布局,PullToRefreshScrollView 的刷新和加載更多監聽加載數據從而調用NoScrollView的adapter.notifyChangeData();已達到無縫銜接的滑動。
安卓APP與智能硬件相結合的簡易方案,安卓app智能相結合第1章 概 述 (作者小波QQ463431476) (來源http://blog.chin
手機影音2--軟件架構分析,影音2--架構分析 1.標題欄 <?xml version=1.0 encoding=utf-8?> <com.atgui
寫程序的歡迎界面(運用畫圖方法畫圓球)。,畫圖圓球 1 package com.lixu.drawable; 2 3 import android.app.Acti
編譯器開發系列--Ocelot語言1.抽象語法樹,--ocelot語法從今天開始研究開發自己的編程語言Ocelot,從《自制編譯器》出發,然後再自己不斷完善功能並優化。