Android自定义StickinessView粘性滑动效果
design包的出现,android界面发生了巨大变化,各种滑动配合的效果,下面我就粘性滑动中的一种进行自定义,效果图如下:
大家看到效果了,这里我是继承了linerlayout,方便一点,若果是viewgroup的话,也就复杂一点点。这里分为三部分:
1.head1,顶部可移动的layout。
2.head2,固定的头部,不会滑动除屏幕外。
3.可滑动的layout(这里只可以是listview,不过也可以是任何可滑动的view,只要给出head可滑动的时机即可)
本stickinessview的难点在于,解决滑动冲突和事件的拦截处理,接下来我一一道来。
一、首先,要确定headlayout什么时候可以拦截事件,那么就要确定listview到达顶部和底部的时机。
@override public void onscroll(abslistview view, int firstvisibleitem, int visibleitemcount, int totalitemcount) { view v = mlistview.getchildat(0); //当firstitem的top为0的时候就认为已经到达listview的顶部了 if (mlistview.getchildcount() > 0 && firstvisibleitem == 0) { //滑动到顶部 if (v.gettop() == 0) { //滑动到顶部了 islistviewtop = true; } else { islistviewbottom = false; } }else if (mlistview.getchildcount()>0&&firstvisibleitem+visibleitemcount==totalitemcount){ final view bottomchildview = mlistview.getchildat(mlistview.getchildcount()-1); //当最后一个itemview的bottom>=listview的高度的时候,那么就认为到达底部了 if (mlistview.getheight()>=bottomchildview.getbottom()){ islistviewbottom = true; }else { islistviewbottom = false; } }else { islistviewbottom = false; islistviewtop = false; }
原因很简单,因为view的gettop和getbottom方法是相对父容器的位置,熟悉layout方法的,想必就会很明白了。
二、知道了headview拦截事件的时机,我们就要搞清楚在此基础之上,我们到底啥时候拦击点击事件,进行滑动。
@override public boolean onintercepttouchevent(motionevent ev) { switch (ev.getaction()) { case motionevent.action_down: touchy = ev.getrawy(); isintercept = false; break; case motionevent.action_move: float distant = ev.getrawy() - touchy; if (islistviewtop) { switch (mheadposition) { case top: if (distant > 0) isintercept = true; break; case center: isintercept = true; break; } } if (islistviewbottom){ switch (mheadposition) { case center: isintercept = true; break; case bottom: if (distant < 0) isintercept = true; break; } } break; case motionevent.action_up: isintercept = true; break; } return isintercept; }
跟大家讲解一下onintercepttouchevent(motionevent ev),这个方法会最先调用,当一个事件序列拦截一次后,那么这个事件的后续事件动作就不会再调用该方法,也就是说,当该viewgroup决定拦截某个事件后,那么它注定要消费后续的事件动作。这里贴出headview的位置状态
public static final int top = 0;//收缩状态 public static final int center = 1;//中间状态 public static final int bottom = 2;//展开状态
关于细节,想必大家画个图就可以知道了,注意一点:在拦截事件序列的时候,一般action_down事件不可以被拦截,因为拦截的话,没得意义了,后续事件就无法控制了,不可能继续往childview传递事件序列。
三、移动headview。
@override public boolean ontouchevent(motionevent event) { switch (event.getaction()) { case motionevent.action_down: //获取不到的 break; case motionevent.action_move: int distant = (int) (touchy - event.getrawy()); if (getscrolly() + distant-1 < maxy && getscrolly() + distant > 0) { scrollto(0, getscrolly() + distant); } break; case motionevent.action_up: if (getscrolly() == 0) mheadposition = bottom; if (getscrolly() == maxy) mheadposition = top; if (getscrolly() > 0 && getscrolly() < maxy) mheadposition = center; if (getscrolly() > maxy / 2) { mscroll.startscroll(0, getscrolly(), 0, maxy-getscrolly(),100); invalidate(); mheadposition = top; } if (getscrolly() < maxy / 2) { mscroll.startscroll(0, getscrolly(),0,-getscrolly(),100); invalidate(); mheadposition = bottom; } break; } return super.ontouchevent(event); }
这里为了使得滑动跟家顺畅我使用了scroller这个类,该类是专门处理弹性滑动的工具类,先初始化构造器,在调用startscroll()方法(其中四个参数:滑动的x,滑动的y,滑动x的偏移量,滑动y的偏移量),然后刷新视图,最后重写computescroll()方法,
@override public void computescroll() { super.computescroll(); if (mscroll.computescrolloffset()){ scrollto(mscroll.getcurrx(),mscroll.getcurry()); postinvalidate(); } }
好了,基本完成,我们还要第一时间获取headview的高度,那么在onmeasure()中获取比较好,并且只获取一次如下
if (maxy == -1) maxy = mheadsecond.getmeasuredheight();
在onfinishinflate()方法中,该方法的执行标志着所有的view都已经add完毕,这里我们进行初始化是比较妥当的。
@override protected void onfinishinflate() { super.onfinishinflate(); int count = getchildcount(); //本粘性布局只支持listview if (count == 3 && getchildat(2) instanceof listview) init(); }
/** * 初始化 */ private void init() { //获得子元素 mheadfiest = getchildat(0); mheadsecond = getchildat(1); mlistview = (listview) getchildat(2); mlistview.setonscrolllistener(this); mscroll = new scroller(getcontext()); }
好了,基本就是这些。
github地址:https://github.com/yzzandroid/lianxinview
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。