仿IOS效果 帶彈簧動(dòng)畫的ListView
最近項(xiàng)目打算做一個(gè)界面,類似于dayone首頁(yè)的界面效果,dayone 是一款付費(fèi)應(yīng)用,目前只有IOS端。作為一個(gè)資深懶惰的程序員,奉行的宗旨是絕對(duì)不重復(fù)造一個(gè)輪子。于是乎,去網(wǎng)上找一大堆開源項(xiàng)目,發(fā)現(xiàn)沒有找到合適的,然后,只能硬著頭皮自己來了。先看看效果:
效果圖
其實(shí)寫起來也比較簡(jiǎn)單,就是控制ListView的頭部和底部的高度就可以了, 如果用RecycleView實(shí)現(xiàn)起來也是一樣,只是RecycleView添加頭和尾巴稍微麻煩一點(diǎn),處理點(diǎn)擊事件也不是很方便,所以就基于ListView去實(shí)現(xiàn)了。實(shí)現(xiàn)的代碼, 我已經(jīng)上傳到github上了。
1、使用方法
compile 'com.a520wcf.yllistview:YLListView:1.0.1
2、使用介紹:
1)、布局:
布局注意一個(gè)小細(xì)節(jié)android:layout_height 最好是match_parent, 否則ListView每次滑動(dòng)的時(shí)候都有可能需要重新計(jì)算條目高度,比較耗費(fèi)CPU;
<com.a520wcf.yllistview.YLListView android:divider="@android:color/transparent" android:id="@+id/listView" android:layout_width="match_parent" android:layout_height="match_parent" />
2)、代碼:
private YLListView listView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); listView = (YLListView) findViewById(R.id.listView); // 不添加也有默認(rèn)的頭和底 View topView=View.inflate(this,R.layout.top,null); listView.addHeaderView(topView); View bottomView=new View(getApplicationContext()); listView.addFooterView(bottomView); // 頂部和底部也可以固定最終的高度 不固定就使用布局本身的高度 listView.setFinalBottomHeight(100); listView.setFinalTopHeight(100); listView.setAdapter(new DemoAdapter()); //YLListView默認(rèn)有頭和底 處理點(diǎn)擊事件位置注意減去 listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { position=position-listView.getHeaderViewsCount(); } }); }
3、源碼介紹
其實(shí)這個(gè)項(xiàng)目里面只有一個(gè)類,大家不需要依賴,直接把這個(gè)類復(fù)制到項(xiàng)目中就可以了,來看看源碼:
package com.a520wcf.yllistview; import android.content.Context; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.ViewTreeObserver.OnGlobalLayoutListener; import android.view.animation.DecelerateInterpolator; import android.widget.AbsListView; import android.widget.ListView; import android.widget.Scroller; public class YLListView extends ListView implements AbsListView.OnScrollListener { private Scroller mScroller; // used for scroll back private float mLastY = -1; private int mScrollBack; private final static int SCROLLBACK_HEADER = 0; private final static int SCROLLBACK_FOOTER = 1; private final static int SCROLL_DURATION = 400; // scroll back duration private final static float OFFSET_RADIO = 1.8f; // total list items, used to detect is at the bottom of ListView. private int mTotalItemCount; private View mHeaderView; // 頂部圖片 private View mFooterView; // 底部圖片 private int finalTopHeight; private int finalBottomHeight; public YLListView(Context context) { super(context); initWithContext(context); } public YLListView(Context context, AttributeSet attrs) { super(context, attrs); initWithContext(context); } public YLListView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); initWithContext(context); } private void initWithContext(Context context) { mScroller = new Scroller(context, new DecelerateInterpolator()); super.setOnScrollListener(this); this.getViewTreeObserver().addOnGlobalLayoutListener( new OnGlobalLayoutListener() { @Override public void onGlobalLayout() { if(mHeaderView==null){ View view=new View(getContext()); addHeaderView(view); } if(mFooterView==null){ View view=new View(getContext()); addFooterView(view); } getViewTreeObserver() .removeGlobalOnLayoutListener(this); } }); } @Override public boolean onTouchEvent(MotionEvent ev) { if (mLastY == -1) { mLastY = ev.getRawY(); } switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: mLastY = ev.getRawY(); break; case MotionEvent.ACTION_MOVE: final float deltaY = ev.getRawY() - mLastY; mLastY = ev.getRawY(); if (getFirstVisiblePosition() == 0 && (mHeaderView.getHeight() > finalTopHeight || deltaY > 0) && mHeaderView.getTop() >= 0) { // the first item is showing, header has shown or pull down. updateHeaderHeight(deltaY / OFFSET_RADIO); } else if (getLastVisiblePosition() == mTotalItemCount - 1 && (getFootHeight() >finalBottomHeight || deltaY < 0)) { updateFooterHeight(-deltaY / OFFSET_RADIO); } break; default: mLastY = -1; // reset if (getFirstVisiblePosition() == 0 && getHeaderHeight() > finalTopHeight) { resetHeaderHeight(); } if (getLastVisiblePosition() == mTotalItemCount - 1 ){ if(getFootHeight() > finalBottomHeight) { resetFooterHeight(); } } break; } return super.onTouchEvent(ev); } /** * 重置底部高度 */ private void resetFooterHeight() { int bottomHeight = getFootHeight(); if (bottomHeight > finalBottomHeight) { mScrollBack = SCROLLBACK_FOOTER; mScroller.startScroll(0, bottomHeight, 0, -bottomHeight+finalBottomHeight, SCROLL_DURATION); invalidate(); } } // 計(jì)算滑動(dòng) 當(dāng)invalidate()后 系統(tǒng)會(huì)自動(dòng)調(diào)用 @Override public void computeScroll() { if (mScroller.computeScrollOffset()) { if (mScrollBack == SCROLLBACK_HEADER) { setHeaderHeight(mScroller.getCurrY()); } else { setFooterViewHeight(mScroller.getCurrY()); } postInvalidate(); } super.computeScroll(); } // 設(shè)置頂部高度 private void setHeaderHeight(int height) { LayoutParams layoutParams = (LayoutParams) mHeaderView.getLayoutParams(); layoutParams.height = height; mHeaderView.setLayoutParams(layoutParams); } // 設(shè)置底部高度 private void setFooterViewHeight(int height) { LayoutParams layoutParams = (LayoutParams) mFooterView.getLayoutParams(); layoutParams.height =height; mFooterView.setLayoutParams(layoutParams); } // 獲取頂部高度 public int getHeaderHeight() { AbsListView.LayoutParams layoutParams = (AbsListView.LayoutParams) mHeaderView.getLayoutParams(); return layoutParams.height; } // 獲取底部高度 public int getFootHeight() { AbsListView.LayoutParams layoutParams = (AbsListView.LayoutParams) mFooterView.getLayoutParams(); return layoutParams.height; } private void resetHeaderHeight() { int height = getHeaderHeight(); if (height == 0) // not visible. return; mScrollBack = SCROLLBACK_HEADER; mScroller.startScroll(0, height, 0, finalTopHeight - height, SCROLL_DURATION); invalidate(); } /** * 設(shè)置頂部高度 如果不設(shè)置高度,默認(rèn)就是布局本身的高度 * @param height 頂部高度 */ public void setFinalTopHeight(int height) { this.finalTopHeight = height; } /** * 設(shè)置底部高度 如果不設(shè)置高度,默認(rèn)就是布局本身的高度 * @param height 底部高度 */ public void setFinalBottomHeight(int height){ this.finalBottomHeight=height; } @Override public void addHeaderView(View v) { mHeaderView = v; super.addHeaderView(mHeaderView); mHeaderView.getViewTreeObserver().addOnGlobalLayoutListener( new OnGlobalLayoutListener() { @Override public void onGlobalLayout() { if(finalTopHeight==0) { finalTopHeight = mHeaderView.getMeasuredHeight(); } setHeaderHeight(finalTopHeight); getViewTreeObserver() .removeGlobalOnLayoutListener(this); } }); } @Override public void addFooterView(View v) { mFooterView = v; super.addFooterView(mFooterView); mFooterView.getViewTreeObserver().addOnGlobalLayoutListener( new OnGlobalLayoutListener() { @Override public void onGlobalLayout() { if(finalBottomHeight==0) { finalBottomHeight = mFooterView.getMeasuredHeight(); } setFooterViewHeight(finalBottomHeight); getViewTreeObserver() .removeGlobalOnLayoutListener(this); } }); } private OnScrollListener mScrollListener; // user's scroll listener @Override public void setOnScrollListener(OnScrollListener l) { mScrollListener = l; } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { if (mScrollListener != null) { mScrollListener.onScrollStateChanged(view, scrollState); } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { // send to user's listener mTotalItemCount = totalItemCount; if (mScrollListener != null) { mScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount); } } private void updateHeaderHeight(float delta) { setHeaderHeight((int) (getHeaderHeight()+delta)); setSelection(0); // scroll to top each time } private void updateFooterHeight(float delta) { setFooterViewHeight((int) (getFootHeight()+delta)); } }
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助。
- IOS等待時(shí)動(dòng)畫效果的實(shí)現(xiàn)
- IOS開發(fā)代碼分享之獲取啟動(dòng)畫面圖片的string
- IOS實(shí)戰(zhàn)之自定義轉(zhuǎn)場(chǎng)動(dòng)畫詳解
- IOS繪制動(dòng)畫顏色漸變折線條
- 詳解iOS開發(fā)中的轉(zhuǎn)場(chǎng)動(dòng)畫和組動(dòng)畫以及UIView封裝動(dòng)畫
- IOS框架Spring常用的動(dòng)畫效果
- 利用iOS動(dòng)畫來模擬音量振動(dòng)條的實(shí)現(xiàn)
- IOS CoreAnimation中l(wèi)ayer動(dòng)畫閃爍的解決方法
- iOS中UIActivityIndicatorView的用法及齒輪等待動(dòng)畫實(shí)例
- iOS基礎(chǔ)動(dòng)畫教程分享
相關(guān)文章
IOS開發(fā)UIPasteboard類的粘貼板全面詳解
這篇文章主要為大家介紹了IOS開發(fā)UIPasteboard類的粘貼板全面詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06iOS自定義日歷控件的簡(jiǎn)單實(shí)現(xiàn)過程
這篇文章主要為大家詳細(xì)介紹了iOS自定義日歷控件的簡(jiǎn)單實(shí)現(xiàn)過程,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-09-09iOS11.3以下modal中input光標(biāo)錯(cuò)位的解決方法
這篇文章主要介紹了iOS11.3以下modal中input光標(biāo)錯(cuò)位的解決方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-12-12iOS利用攝像頭獲取環(huán)境光感參數(shù)的方法
本篇文章主要介紹了iOS利用攝像頭獲取環(huán)境光感參數(shù)的方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-11-11iOS UIScrollView滾動(dòng)視圖/無限循環(huán)滾動(dòng)/自動(dòng)滾動(dòng)的實(shí)例代碼
這篇文章主要介紹了iOS UIScrollView滾動(dòng)視圖/無限循環(huán)滾動(dòng)/自動(dòng)滾動(dòng),需要的朋友可以參考下2017-02-02一個(gè)iOS上的秒表小應(yīng)用的實(shí)現(xiàn)方法分享
這篇文章主要介紹了一個(gè)iOS上的秒表小應(yīng)用的實(shí)現(xiàn)方法分享,代碼基于傳統(tǒng)的Objective-C,需要的朋友可以參考下2015-10-10