XListView實(shí)現(xiàn)下拉刷新和上拉加載原理解析
XListview是一個(gè)非常受歡迎的下拉刷新控件,但是已經(jīng)停止維護(hù)了。之前寫過一篇XListview的使用介紹,用起來非常簡(jiǎn)單,這兩天放假無聊,研究了下XListview的實(shí)現(xiàn)原理,學(xué)到了很多,今天分享給大家。
提前聲明,為了讓代碼更好的理解,我對(duì)代碼進(jìn)行了部分刪減和重構(gòu),如果大家想看原版代碼,請(qǐng)去github自行下載。
Xlistview項(xiàng)目主要是三部分:XlistView,XListViewHeader,XListViewFooter,分別是XListView主體、header、footer的實(shí)現(xiàn)。下面我們分開來介紹。
下面是修改之后的XListViewHeader代碼
public class XListViewHeader extends LinearLayout { private static final String HINT_NORMAL = "下拉刷新"; private static final String HINT_READY = "松開刷新數(shù)據(jù)"; private static final String HINT_LOADING = "正在加載..."; // 正常狀態(tài) public final static int STATE_NORMAL = 0; // 準(zhǔn)備刷新狀態(tài),也就是箭頭方向發(fā)生改變之后的狀態(tài) public final static int STATE_READY = 1; // 刷新狀態(tài),箭頭變成了progressBar public final static int STATE_REFRESHING = 2; // 布局容器,也就是根布局 private LinearLayout container; // 箭頭圖片 private ImageView mArrowImageView; // 刷新狀態(tài)顯示 private ProgressBar mProgressBar; // 說明文本 private TextView mHintTextView; // 記錄當(dāng)前的狀態(tài) private int mState; // 用于改變箭頭的方向的動(dòng)畫 private Animation mRotateUpAnim; private Animation mRotateDownAnim; // 動(dòng)畫持續(xù)時(shí)間 private final int ROTATE_ANIM_DURATION = 180; public XListViewHeader(Context context) { super(context); initView(context); } public XListViewHeader(Context context, AttributeSet attrs) { super(context, attrs); initView(context); } private void initView(Context context) { mState = STATE_NORMAL; // 初始情況下,設(shè)置下拉刷新view高度為0 LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( LayoutParams.MATCH_PARENT, 0); container = (LinearLayout) LayoutInflater.from(context).inflate( R.layout.xlistview_header, null); addView(container, lp); // 初始化控件 mArrowImageView = (ImageView) findViewById(R.id.xlistview_header_arrow); mHintTextView = (TextView) findViewById(R.id.xlistview_header_hint_textview); mProgressBar = (ProgressBar) findViewById(R.id.xlistview_header_progressbar); // 初始化動(dòng)畫 mRotateUpAnim = new RotateAnimation(0.0f, -180.0f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); mRotateUpAnim.setDuration(ROTATE_ANIM_DURATION); mRotateUpAnim.setFillAfter(true); mRotateDownAnim = new RotateAnimation(-180.0f, 0.0f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); mRotateDownAnim.setDuration(ROTATE_ANIM_DURATION); mRotateDownAnim.setFillAfter(true); } // 設(shè)置header的狀態(tài) public void setState(int state) { if (state == mState) return; // 顯示進(jìn)度 if (state == STATE_REFRESHING) { mArrowImageView.clearAnimation(); mArrowImageView.setVisibility(View.INVISIBLE); mProgressBar.setVisibility(View.VISIBLE); } else { // 顯示箭頭 mArrowImageView.setVisibility(View.VISIBLE); mProgressBar.setVisibility(View.INVISIBLE); } switch (state) { case STATE_NORMAL: if (mState == STATE_READY) { mArrowImageView.startAnimation(mRotateDownAnim); } if (mState == STATE_REFRESHING) { mArrowImageView.clearAnimation(); } mHintTextView.setText(HINT_NORMAL); break; case STATE_READY: if (mState != STATE_READY) { mArrowImageView.clearAnimation(); mArrowImageView.startAnimation(mRotateUpAnim); mHintTextView.setText(HINT_READY); } break; case STATE_REFRESHING: mHintTextView.setText(HINT_LOADING); break; } mState = state; } public void setVisiableHeight(int height) { if (height < 0) height = 0; LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) container .getLayoutParams(); lp.height = height; container.setLayoutParams(lp); } public int getVisiableHeight() { return container.getHeight(); } public void show() { container.setVisibility(View.VISIBLE); } public void hide() { container.setVisibility(View.INVISIBLE); } }
XListViewHeader繼承自linearLayout,用來實(shí)現(xiàn)下拉刷新時(shí)的界面展示,可以分為三種狀態(tài):正常、準(zhǔn)備刷新、正在加載。
在Linearlayout布局里面,主要有指示箭頭、說明文本、圓形加載條三個(gè)控件。在構(gòu)造函數(shù)中,調(diào)用了initView()進(jìn)行控件的初始化操作。在添加布局文件的時(shí)候,指定高度為0,這是為了隱藏header,然后初始化動(dòng)畫,是為了完成箭頭的旋轉(zhuǎn)動(dòng)作。
setState()是設(shè)置header的狀態(tài),因?yàn)閔eader需要根據(jù)不同的狀態(tài),完成控件隱藏、顯示、改變文字等操作,這個(gè)方法主要是在XListView里面調(diào)用。除此之外,還有setVisiableHeight()和getVisiableHeight(),這兩個(gè)方法是為了設(shè)置和獲取Header中根布局文件的高度屬性,從而完成拉伸和收縮的效果,而show()和hide()則顯然就是完成顯示和隱藏的效果。
下面是Header的布局文件
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="bottom" > <RelativeLayout android:id="@+id/xlistview_header_content" android:layout_width="match_parent" android:layout_height="60dp" tools:ignore="UselessParent" > <TextView android:id="@+id/xlistview_header_hint_textview" android:layout_width="100dp" android:layout_height="wrap_content" android:layout_centerInParent="true" android:gravity="center" android:text="正在加載" android:textColor="@android:color/black" android:textSize="14sp" /> <ImageView android:id="@+id/xlistview_header_arrow" android:layout_width="30dp" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_toLeftOf="@id/xlistview_header_hint_textview" android:src="@drawable/xlistview_arrow" /> <ProgressBar android:id="@+id/xlistview_header_progressbar" style="@style/progressbar_style" android:layout_width="30dp" android:layout_height="30dp" android:layout_centerVertical="true" android:layout_toLeftOf="@id/xlistview_header_hint_textview" android:visibility="invisible" /> </RelativeLayout> </LinearLayout>
說完了Header,我們?cè)倏纯碏ooter。Footer是為了完成加載更多功能時(shí)候的界面展示,基本思路和Header是一樣的,下面是Footer的代碼
public class XListViewFooter extends LinearLayout { // 正常狀態(tài) public final static int STATE_NORMAL = 0; // 準(zhǔn)備狀態(tài) public final static int STATE_READY = 1; // 加載狀態(tài) public final static int STATE_LOADING = 2; private View mContentView; private View mProgressBar; private TextView mHintView; public XListViewFooter(Context context) { super(context); initView(context); } public XListViewFooter(Context context, AttributeSet attrs) { super(context, attrs); initView(context); } private void initView(Context context) { LinearLayout moreView = (LinearLayout) LayoutInflater.from(context) .inflate(R.layout.xlistview_footer, null); addView(moreView); moreView.setLayoutParams(new LinearLayout.LayoutParams( LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); mContentView = moreView.findViewById(R.id.xlistview_footer_content); mProgressBar = moreView.findViewById(R.id.xlistview_footer_progressbar); mHintView = (TextView) moreView .findViewById(R.id.xlistview_footer_hint_textview); } /** * 設(shè)置當(dāng)前的狀態(tài) * * @param state */ public void setState(int state) { mProgressBar.setVisibility(View.INVISIBLE); mHintView.setVisibility(View.INVISIBLE); switch (state) { case STATE_READY: mHintView.setVisibility(View.VISIBLE); mHintView.setText(R.string.xlistview_footer_hint_ready); break; case STATE_NORMAL: mHintView.setVisibility(View.VISIBLE); mHintView.setText(R.string.xlistview_footer_hint_normal); break; case STATE_LOADING: mProgressBar.setVisibility(View.VISIBLE); break; } } public void setBottomMargin(int height) { if (height > 0) { LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mContentView .getLayoutParams(); lp.bottomMargin = height; mContentView.setLayoutParams(lp); } } public int getBottomMargin() { LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mContentView .getLayoutParams(); return lp.bottomMargin; } public void hide() { LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mContentView .getLayoutParams(); lp.height = 0; mContentView.setLayoutParams(lp); } public void show() { LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mContentView .getLayoutParams(); lp.height = LayoutParams.WRAP_CONTENT; mContentView.setLayoutParams(lp); } }
從上面的代碼里面,我們可以看出,footer和header的思路是一樣的,只不過,footer的拉伸和顯示效果不是通過高度來模擬的,而是通過設(shè)置BottomMargin來完成的。
下面是Footer的布局文件
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="fill_parent" android:layout_height="wrap_content" > <RelativeLayout android:id="@+id/xlistview_footer_content" android:layout_width="fill_parent" android:layout_height="wrap_content" android:padding="5dp" tools:ignore="UselessParent" > <ProgressBar android:id="@+id/xlistview_footer_progressbar" style="@style/progressbar_style" android:layout_width="30dp" android:layout_height="30dp" android:layout_centerInParent="true" android:visibility="invisible" /> <TextView android:id="@+id/xlistview_footer_hint_textview" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:text="@string/xlistview_footer_hint_normal" android:textColor="@android:color/black" android:textSize="14sp" /> </RelativeLayout> </LinearLayout>
在了解了Header和footer之后,我們就要介紹最核心的XListView的代碼實(shí)現(xiàn)了。
在介紹代碼實(shí)現(xiàn)之前,我先介紹一下XListView的實(shí)現(xiàn)原理。
首先,一旦使用XListView,F(xiàn)ooter和Header就已經(jīng)添加到我們的ListView上面了,XListView就是通過繼承ListView,然后處理了屏幕點(diǎn)擊事件和控制滑動(dòng)實(shí)現(xiàn)效果的。所以,如果我們的Adapter中g(shù)etCount()返回的值是20,那么其實(shí)XListView里面是有20+2個(gè)item的,這個(gè)數(shù)量即使我們關(guān)閉了XListView的刷新和加載功能,也是不會(huì)變化的。Header和Footer通過addHeaderView和addFooterView添加上去之后,如果想實(shí)現(xiàn)下拉刷新和上拉加載功能,那么就必須有拉伸效果,所以就像上面的那樣,Header是通過設(shè)置height,F(xiàn)ooter是通過設(shè)置BottomMargin來模擬拉伸效果。那么回彈效果呢??jī)H僅通過設(shè)置高度或者是間隔是達(dá)不到模擬回彈效果的,因此,就需要用Scroller來實(shí)現(xiàn)模擬回彈效果。在說明原理之后,我們開始介紹XListView的核心實(shí)現(xiàn)原理。
再次提示,下面的代碼經(jīng)過我重構(gòu)了,只是為了看起來更好的理解。
public class XListView extends ListView { private final static int SCROLLBACK_HEADER = 0; private final static int SCROLLBACK_FOOTER = 1; // 滑動(dòng)時(shí)長(zhǎng) private final static int SCROLL_DURATION = 400; // 加載更多的距離 private final static int PULL_LOAD_MORE_DELTA = 100; // 滑動(dòng)比例 private final static float OFFSET_RADIO = 2f; // 記錄按下點(diǎn)的y坐標(biāo) private float lastY; // 用來回滾 private Scroller scroller; private IXListViewListener mListViewListener; private XListViewHeader headerView; private RelativeLayout headerViewContent; // header的高度 private int headerHeight; // 是否能夠刷新 private boolean enableRefresh = true; // 是否正在刷新 private boolean isRefreashing = false; // footer private XListViewFooter footerView; // 是否可以加載更多 private boolean enableLoadMore; // 是否正在加載 private boolean isLoadingMore; // 是否footer準(zhǔn)備狀態(tài) private boolean isFooterAdd = false; // total list items, used to detect is at the bottom of listview. private int totalItemCount; // 記錄是從header還是footer返回 private int mScrollBack; private static final String TAG = "XListView"; public XListView(Context context) { super(context); initView(context); } public XListView(Context context, AttributeSet attrs) { super(context, attrs); initView(context); } public XListView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); initView(context); } private void initView(Context context) { scroller = new Scroller(context, new DecelerateInterpolator()); headerView = new XListViewHeader(context); footerView = new XListViewFooter(context); headerViewContent = (RelativeLayout) headerView .findViewById(R.id.xlistview_header_content); headerView.getViewTreeObserver().addOnGlobalLayoutListener( new OnGlobalLayoutListener() { @SuppressWarnings("deprecation") @Override public void onGlobalLayout() { headerHeight = headerViewContent.getHeight(); getViewTreeObserver() .removeGlobalOnLayoutListener(this); } }); addHeaderView(headerView); } @Override public void setAdapter(ListAdapter adapter) { // 確保footer最后添加并且只添加一次 if (isFooterAdd == false) { isFooterAdd = true; addFooterView(footerView); } super.setAdapter(adapter); } @Override public boolean onTouchEvent(MotionEvent ev) { totalItemCount = getAdapter().getCount(); switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: // 記錄按下的坐標(biāo) lastY = ev.getRawY(); break; case MotionEvent.ACTION_MOVE: // 計(jì)算移動(dòng)距離 float deltaY = ev.getRawY() - lastY; lastY = ev.getRawY(); // 是第一項(xiàng)并且標(biāo)題已經(jīng)顯示或者是在下拉 if (getFirstVisiblePosition() == 0 && (headerView.getVisiableHeight() > 0 || deltaY > 0)) { updateHeaderHeight(deltaY / OFFSET_RADIO); } else if (getLastVisiblePosition() == totalItemCount - 1 && (footerView.getBottomMargin() > 0 || deltaY < 0)) { updateFooterHeight(-deltaY / OFFSET_RADIO); } break; case MotionEvent.ACTION_UP: if (getFirstVisiblePosition() == 0) { if (enableRefresh && headerView.getVisiableHeight() > headerHeight) { isRefreashing = true; headerView.setState(XListViewHeader.STATE_REFRESHING); if (mListViewListener != null) { mListViewListener.onRefresh(); } } resetHeaderHeight(); } else if (getLastVisiblePosition() == totalItemCount - 1) { if (enableLoadMore && footerView.getBottomMargin() > PULL_LOAD_MORE_DELTA) { startLoadMore(); } resetFooterHeight(); } break; } return super.onTouchEvent(ev); } @Override public void computeScroll() { // 松手之后調(diào)用 if (scroller.computeScrollOffset()) { if (mScrollBack == SCROLLBACK_HEADER) { headerView.setVisiableHeight(scroller.getCurrY()); } else { footerView.setBottomMargin(scroller.getCurrY()); } postInvalidate(); } super.computeScroll(); } public void setPullRefreshEnable(boolean enable) { enableRefresh = enable; if (!enableRefresh) { headerView.hide(); } else { headerView.show(); } } public void setPullLoadEnable(boolean enable) { enableLoadMore = enable; if (!enableLoadMore) { footerView.hide(); footerView.setOnClickListener(null); } else { isLoadingMore = false; footerView.show(); footerView.setState(XListViewFooter.STATE_NORMAL); footerView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { startLoadMore(); } }); } } public void stopRefresh() { if (isRefreashing == true) { isRefreashing = false; resetHeaderHeight(); } } public void stopLoadMore() { if (isLoadingMore == true) { isLoadingMore = false; footerView.setState(XListViewFooter.STATE_NORMAL); } } private void updateHeaderHeight(float delta) { headerView.setVisiableHeight((int) delta + headerView.getVisiableHeight()); // 未處于刷新狀態(tài),更新箭頭 if (enableRefresh && !isRefreashing) { if (headerView.getVisiableHeight() > headerHeight) { headerView.setState(XListViewHeader.STATE_READY); } else { headerView.setState(XListViewHeader.STATE_NORMAL); } } } private void resetHeaderHeight() { // 當(dāng)前的可見高度 int height = headerView.getVisiableHeight(); // 如果正在刷新并且高度沒有完全展示 if ((isRefreashing && height <= headerHeight) || (height == 0)) { return; } // 默認(rèn)會(huì)回滾到header的位置 int finalHeight = 0; // 如果是正在刷新狀態(tài),則回滾到header的高度 if (isRefreashing && height > headerHeight) { finalHeight = headerHeight; } mScrollBack = SCROLLBACK_HEADER; // 回滾到指定位置 scroller.startScroll(0, height, 0, finalHeight - height, SCROLL_DURATION); // 觸發(fā)computeScroll invalidate(); } private void updateFooterHeight(float delta) { int height = footerView.getBottomMargin() + (int) delta; if (enableLoadMore && !isLoadingMore) { if (height > PULL_LOAD_MORE_DELTA) { footerView.setState(XListViewFooter.STATE_READY); } else { footerView.setState(XListViewFooter.STATE_NORMAL); } } footerView.setBottomMargin(height); } private void resetFooterHeight() { int bottomMargin = footerView.getBottomMargin(); if (bottomMargin > 0) { mScrollBack = SCROLLBACK_FOOTER; scroller.startScroll(0, bottomMargin, 0, -bottomMargin, SCROLL_DURATION); invalidate(); } } private void startLoadMore() { isLoadingMore = true; footerView.setState(XListViewFooter.STATE_LOADING); if (mListViewListener != null) { mListViewListener.onLoadMore(); } } public void setXListViewListener(IXListViewListener l) { mListViewListener = l; } public interface IXListViewListener { public void onRefresh(); public void onLoadMore(); } }
在三個(gè)構(gòu)造函數(shù)中,都調(diào)用initView進(jìn)行了header和footer的初始化,并且定義了一個(gè)Scroller,并傳入了一個(gè)減速的插值器,為了模仿回彈效果。在initView方法里面,因?yàn)閔eader可能還沒初始化完畢,所以通過GlobalLayoutlistener來獲取了header的高度,然后addHeaderView添加到了listview上面。
通過重寫setAdapter方法,保證Footer最后天假,并且只添加一次。
最重要的,要屬onTouchEvent了。在方法開始之前,通過getAdapter().getCount()獲取到了item的總數(shù),便于計(jì)算位置。這個(gè)操作在源代碼中是通過scrollerListener完成的,因?yàn)镾crollerListener在這里沒大有用,所以我直接去掉了,然后把位置改到了這里。如果在setAdapter里面獲取的話,只能獲取到?jīng)]有header和footer的item數(shù)量。
在ACTION_DOWN里面,進(jìn)行了lastY的初始化,lastY是為了判斷移動(dòng)方向的,因?yàn)樵贏CTION_MOVE里面,通過ev.getRawY()-lastY可以計(jì)算出手指的移動(dòng)趨勢(shì),如果>0,那么就是向下滑動(dòng),反之向上。getRowY()是獲取元Y坐標(biāo),意思就是和Window和View坐標(biāo)沒有關(guān)系的坐標(biāo),代表在屏幕上的絕對(duì)位置。然后在下面的代碼里面,如果第一項(xiàng)可見并且header的可見高度>0或者是向下滑動(dòng),就說明用戶在向下拉動(dòng)或者是向上拉動(dòng)header,也就是指示箭頭顯示的時(shí)候的狀態(tài),這時(shí)候調(diào)用了updateHeaderHeight,來更新header的高度,實(shí)現(xiàn)header可以跟隨手指動(dòng)作上下移動(dòng)。這里有個(gè)OFFSET_RADIO,這個(gè)值是一個(gè)移動(dòng)比例,就是說,你手指在Y方向上移動(dòng)400px,如果比例是2,那么屏幕上的控件移動(dòng)就是400px/2=200px,可以通過這個(gè)值來控制用戶的滑動(dòng)體驗(yàn)。下面的關(guān)于footer的判斷與此類似,不再贅述。
當(dāng)用戶移開手指之后,ACTION_UP方法就會(huì)被調(diào)用。在這里面,只對(duì)可見位置是0和item總數(shù)-1的位置進(jìn)行了處理,其實(shí)正好對(duì)應(yīng)header和footer。如果位置是0,并且可以刷新,然后當(dāng)前的header可見高度>原始高度的話,就說明用戶確實(shí)是要進(jìn)行刷新操作,所以通過setState改變header的狀態(tài),如果有監(jiān)聽器的話,就調(diào)用onRefresh方法,然后調(diào)用resetHeaderHeight初始化header的狀態(tài),因?yàn)閒ooter的操作如出一轍,所以不再贅述。但是在footer中有一個(gè)PULL_LOAD_MORE_DELTA,這個(gè)值是加載更多觸發(fā)條件的臨界值,只有footer的間隔超過這個(gè)值之后,才能夠觸發(fā)加載更多的功能,因此我們可以修改這個(gè)值來改變用戶體驗(yàn)。
說到現(xiàn)在,大家應(yīng)該明白基本的原理了,其實(shí)XListView就是通過對(duì)用戶手勢(shì)的方向和距離的判斷,來動(dòng)態(tài)的改變Header和Footer實(shí)現(xiàn)的功能,所以如果我們也有類似的需求,就可以參照這種思路進(jìn)行自定義。
下面再說幾個(gè)比較重要的方法。
前面我們說道,在ACTION_MOVE里面,會(huì)不斷的調(diào)用下面的updateXXXX方法,來動(dòng)態(tài)的改變header和fooer的狀態(tài),
private void updateHeaderHeight(float delta) { headerView.setVisiableHeight((int) delta + headerView.getVisiableHeight()); // 未處于刷新狀態(tài),更新箭頭 if (enableRefresh && !isRefreashing) { if (headerView.getVisiableHeight() > headerHeight) { headerView.setState(XListViewHeader.STATE_READY); } else { headerView.setState(XListViewHeader.STATE_NORMAL); } } } private void updateFooterHeight(float delta) { int height = footerView.getBottomMargin() + (int) delta; if (enableLoadMore && !isLoadingMore) { if (height > PULL_LOAD_MORE_DELTA) { footerView.setState(XListViewFooter.STATE_READY); } else { footerView.setState(XListViewFooter.STATE_NORMAL); } } footerView.setBottomMargin(height); }
在移開手指之后,會(huì)調(diào)用下面的resetXXX來初始化header和footer的狀態(tài)
private void resetHeaderHeight() { // 當(dāng)前的可見高度 int height = headerView.getVisiableHeight(); // 如果正在刷新并且高度沒有完全展示 if ((isRefreashing && height <= headerHeight) || (height == 0)) { return; } // 默認(rèn)會(huì)回滾到header的位置 int finalHeight = 0; // 如果是正在刷新狀態(tài),則回滾到header的高度 if (isRefreashing && height > headerHeight) { finalHeight = headerHeight; } mScrollBack = SCROLLBACK_HEADER; // 回滾到指定位置 scroller.startScroll(0, height, 0, finalHeight - height, SCROLL_DURATION); // 觸發(fā)computeScroll invalidate(); } private void resetFooterHeight() { int bottomMargin = footerView.getBottomMargin(); if (bottomMargin > 0) { mScrollBack = SCROLLBACK_FOOTER; scroller.startScroll(0, bottomMargin, 0, -bottomMargin, SCROLL_DURATION); invalidate(); } }
我們可以看到,滾動(dòng)操作不是通過直接的設(shè)置高度來實(shí)現(xiàn)的,而是通過Scroller.startScroll()來實(shí)現(xiàn)的,通過調(diào)用此方法,computeScroll()就會(huì)被調(diào)用,然后在這個(gè)里面,根據(jù)mScrollBack區(qū)分是哪一個(gè)滾動(dòng),然后再通過設(shè)置高度和間隔,就可以完成收縮的效果了。
至此,整個(gè)XListView的實(shí)現(xiàn)原理就完全的搞明白了,以后如果做滾動(dòng)類的自定義控件,應(yīng)該也有思路了。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android實(shí)現(xiàn)在屏幕上移動(dòng)圖片的方法
這篇文章主要介紹了Android實(shí)現(xiàn)在屏幕上移動(dòng)圖片的方法,實(shí)例分析了Android操作圖片的相關(guān)技巧,需要的朋友可以參考下2015-06-06Android實(shí)現(xiàn)雙向滑動(dòng)特效的實(shí)例代碼
這篇文章主要介紹了Android實(shí)現(xiàn)雙向滑動(dòng)特效的實(shí)例代碼,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,一起跟隨小編過來看看吧2018-05-05Android Room數(shù)據(jù)庫(kù)自動(dòng)升級(jí)與遷移的策略
在 Android 應(yīng)用開發(fā)中,Room 是 Google 提供的一個(gè)輕量級(jí)數(shù)據(jù)庫(kù)框架,用于簡(jiǎn)化與 SQLite 的交互,本文將介紹 Room 數(shù)據(jù)庫(kù)升級(jí)的幾種場(chǎng)景和常見的處理方法,包括手動(dòng)遷移和自動(dòng)遷移的策略,需要的朋友可以參考下2024-09-09Android實(shí)現(xiàn)瘋狂連連看游戲之開發(fā)游戲界面(二)
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)瘋狂連連看游戲之開發(fā)游戲界面,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-03-03Android利用Paint自定義View實(shí)現(xiàn)進(jìn)度條控件方法示例
這篇文章主要給大家介紹了關(guān)于Android利用Paint自定義View實(shí)現(xiàn)進(jìn)度條控件的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)各位Android開發(fā)者們具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2017-11-11單獨(dú)編譯Android 源代碼中的模塊實(shí)現(xiàn)方法
本文主要講解單獨(dú)編譯Android 源代碼中的模塊,這里對(duì)Android源碼單獨(dú)編譯模塊,做出了詳細(xì)的步驟,希望能幫助研究Android 源代碼的朋友2016-08-08Android?Jetpack組件中LifeCycle作用詳細(xì)介紹
Jetpack是谷歌在Google?I/O?2017大會(huì)上發(fā)布一套幫助開發(fā)者解決Android架構(gòu)設(shè)計(jì)的方案,而Lifecycle是Jetpack?architecture下的一部分,一起來看一下Lifecycle的使用及原理分析2022-09-09