仿IOS效果 带弹簧动画的ListView
程序员文章站
2022-06-19 19:31:04
最近项目打算做一个界面,类似于dayone首页的界面效果,dayone 是一款付费应用,目前只有ios端。作为一个资深懒惰的程序员,奉行的宗旨是绝对不重复造一个*。于是乎...
最近项目打算做一个界面,类似于dayone首页的界面效果,dayone 是一款付费应用,目前只有ios端。作为一个资深懒惰的程序员,奉行的宗旨是绝对不重复造一个*。于是乎,去网上找一大堆开源项目,发现没有找到合适的,然后,只能硬着头皮自己来了。先看看效果:
效果图
其实写起来也比较简单,就是控制listview的头部和底部的高度就可以了, 如果用recycleview实现起来也是一样,只是recycleview添加头和尾巴稍微麻烦一点,处理点击事件也不是很方便,所以就基于listview去实现了。实现的代码, 我已经上传到github上了。
1、使用方法
compile 'com.a520wcf.yllistview:yllistview:1.0.1
2、使用介绍:
1)、布局:
布局注意一个小细节android:layout_height 最好是match_parent, 否则listview每次滑动的时候都有可能需要重新计算条目高度,比较耗费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); // 不添加也有默认的头和底 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默认有头和底 处理点击事件位置注意减去 listview.setonitemclicklistener(new adapterview.onitemclicklistener() { @override public void onitemclick(adapterview<?> parent, view view, int position, long id) { position=position-listview.getheaderviewscount(); } }); }
3、源码介绍
其实这个项目里面只有一个类,大家不需要依赖,直接把这个类复制到项目中就可以了,来看看源码:
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(); } } // 计算滑动 当invalidate()后 系统会自动调用 @override public void computescroll() { if (mscroller.computescrolloffset()) { if (mscrollback == scrollback_header) { setheaderheight(mscroller.getcurry()); } else { setfooterviewheight(mscroller.getcurry()); } postinvalidate(); } super.computescroll(); } // 设置顶部高度 private void setheaderheight(int height) { layoutparams layoutparams = (layoutparams) mheaderview.getlayoutparams(); layoutparams.height = height; mheaderview.setlayoutparams(layoutparams); } // 设置底部高度 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(); } /** * 设置顶部高度 如果不设置高度,默认就是布局本身的高度 * @param height 顶部高度 */ public void setfinaltopheight(int height) { this.finaltopheight = height; } /** * 设置底部高度 如果不设置高度,默认就是布局本身的高度 * @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)); } }
以上就是本文的全部内容,希望对大家的学习有所帮助。