仿IOS效果 帶彈簧動(dòng)畫的ListView
最近項(xiàng)目打算做一個(gè)界面,類似于dayone首頁的界面效果,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-06
iOS自定義日歷控件的簡(jiǎn)單實(shí)現(xiàn)過程
這篇文章主要為大家詳細(xì)介紹了iOS自定義日歷控件的簡(jiǎn)單實(shí)現(xiàn)過程,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-09-09
iOS11.3以下modal中input光標(biāo)錯(cuò)位的解決方法
這篇文章主要介紹了iOS11.3以下modal中input光標(biāo)錯(cuò)位的解決方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-12-12
iOS利用攝像頭獲取環(huán)境光感參數(shù)的方法
本篇文章主要介紹了iOS利用攝像頭獲取環(huán)境光感參數(shù)的方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-11-11
iOS 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

