android多种滑动冲突的解决方案
一、前言
android 中解决滑动的方案有2种:外部拦截法 和内部拦截法。
滑动冲突也存在2种场景: 横竖滑动冲突、同向滑动冲突。
所以我就写了4个例子来学习如何解决滑动冲突的,这四个例子分别为: 外部拦截法解决横竖冲突、外部拦截法解决同向冲突、内部拦截法解决横竖冲突、内部拦截法解决同向冲突。
先上效果图:
二、实战
1、外部拦截法,解决横竖冲突
思路是,重写父控件的onintercepttouchevent方法,然后根据具体的需求,来决定父控件是否拦截事件。如果拦截返回返回true,不拦截返回false。如果父控件拦截了事件,则在父控件的ontouchevent进行相应的事件处理。
我的这个例子,是一个横向滑动的viewgroup里面包含了3个竖向滑动的listview。下面我附上代码,horizontalex.java:
/** * created by blueberry on 2016/6/20. * * 解决交错的滑动冲突 * * 外部拦截法 */ public class horizontalex extends viewgroup { private static final string tag = "horizontalex"; private boolean isfirsttouch = true; private int childindex; private int childcount; private int lastxintercept, lastyintercept, lastx, lasty; private scroller mscroller; private velocitytracker mvelocitytracker; public horizontalex(context context) { super(context); init(); } public horizontalex(context context, attributeset attrs) { super(context, attrs); init(); } public horizontalex(context context, attributeset attrs, int defstyleattr) { super(context, attrs, defstyleattr); init(); } private void init() { mscroller = new scroller(getcontext()); mvelocitytracker = velocitytracker.obtain(); } @override protected void onmeasure(int widthmeasurespec, int heightmeasurespec) { int width = measurespec.getsize(widthmeasurespec); int height = measurespec.getsize(heightmeasurespec); int widthmode = measurespec.getmode(widthmeasurespec); int heightmode = measurespec.getmode(heightmeasurespec); childcount = getchildcount(); measurechildren(widthmeasurespec, heightmeasurespec); if (childcount == 0) { setmeasureddimension(0, 0); } else if (widthmode == measurespec.at_most && heightmode == measurespec.at_most) { width = childcount * getchildat(0).getmeasuredwidth(); height = getchildat(0).getmeasuredheight(); setmeasureddimension(width, height); } else if (widthmode == measurespec.at_most) { width = childcount * getchildat(0).getmeasuredwidth(); setmeasureddimension(width, height); } else { height = getchildat(0).getmeasuredheight(); setmeasureddimension(width, height); } } @override protected void onlayout(boolean changed, int l, int t, int r, int b) { int left = 0; for (int i = 0; i < getchildcount(); i++) { final view child = getchildat(i); child.layout(left + l, t, r + left, b); left += child.getmeasuredwidth(); } } /** * 拦截事件 * @param ev * @return */ @override public boolean onintercepttouchevent(motionevent ev) { boolean intercepted = false; int x = (int) ev.getx(); int y = (int) ev.gety(); switch (ev.getaction()) { /*如果拦截了down事件,则子类不会拿到这个事件序列*/ case motionevent.action_down: lastxintercept = x; lastyintercept = y; intercepted = false; if (!mscroller.isfinished()) { mscroller.abortanimation(); intercepted = true; } break; case motionevent.action_move: final int deltax = x - lastxintercept; final int deltay = y - lastyintercept; /*根据条件判断是否拦截该事件*/ if (math.abs(deltax) > math.abs(deltay)) { intercepted = true; } else { intercepted = false; } break; case motionevent.action_up: intercepted = false; break; } lastxintercept = x; lastyintercept = y; return intercepted; } @override public boolean ontouchevent(motionevent event) { int x = (int) event.getx(); int y = (int) event.gety(); mvelocitytracker.addmovement(event); viewconfiguration configuration = viewconfiguration.get(getcontext()); switch (event.getaction()) { case motionevent.action_down: if (!mscroller.isfinished()) { mscroller.abortanimation(); } break; case motionevent.action_move: /*因为这里父控件拿不到down事件,所以使用一个布尔值, 当事件第一次来到父控件时,对lastx,lasty赋值*/ if (isfirsttouch) { lastx = x; lasty = y; isfirsttouch = false; } final int deltax = x - lastx; scrollby(-deltax, 0); break; case motionevent.action_up: int scrollx = getscrollx(); final int childwidth = getchildat(0).getwidth(); mvelocitytracker.computecurrentvelocity(1000, configuration.getscaledmaximumflingvelocity()); float xvelocity = mvelocitytracker.getxvelocity(); if (math.abs(xvelocity) > configuration.getscaledminimumflingvelocity()) { childindex = xvelocity < 0 ? childindex + 1 : childindex - 1; } else { childindex = (scrollx + childwidth / 2) / childwidth; } childindex = math.min(getchildcount() - 1, math.max(childindex, 0)); smoothscrollby(childindex * childwidth - scrollx, 0); mvelocitytracker.clear(); isfirsttouch = true; break; } lastx = x; lasty = y; return true; } void smoothscrollby(int dx, int dy) { mscroller.startscroll(getscrollx(), getscrolly(), dx, dy, 500); invalidate(); } @override public void computescroll() { if (mscroller.computescrolloffset()) { scrollto(mscroller.getcurrx(), mscroller.getcurry()); invalidate(); } } @override protected void ondetachedfromwindow() { super.ondetachedfromwindow(); mvelocitytracker.recycle(); } }
调用代码:
@override public void showouthvdata(list<string> data1, list<string> data2, list<string> data3) { listview listview1 = new listview(getcontext()); arrayadapter<string> adapter1 = new arrayadapter<string>(getcontext(), android.r.layout.simple_list_item_1, data1); listview1.setadapter(adapter1); listview listview2 = new listview(getcontext()); arrayadapter<string> adapter2 = new arrayadapter<string>(getcontext(), android.r.layout.simple_list_item_1, data2); listview2.setadapter(adapter2); listview listview3 = new listview(getcontext()); arrayadapter<string> adapter3 = new arrayadapter<string>(getcontext(), android.r.layout.simple_list_item_1, data3); listview3.setadapter(adapter3); viewgroup.layoutparams params = new viewgroup.layoutparams(viewgroup.layoutparams.match_parent, viewgroup.layoutparams.match_parent); mhorizontalex.addview(listview1, params); mhorizontalex.addview(listview2, params); mhorizontalex.addview(listview3, params); }
其实外部拦截的主要思想都在于对onintercepttouchevent的重写。
@override public boolean onintercepttouchevent(motionevent ev) { boolean intercepted = false; int x = (int) ev.getx(); int y = (int) ev.gety(); switch (ev.getaction()) { /*如果拦截了down事件,则子类不会拿到这个事件序列*/ case motionevent.action_down: lastxintercept = x; lastyintercept = y; intercepted = false; if (!mscroller.isfinished()) { mscroller.abortanimation(); intercepted = true; } break; case motionevent.action_move: final int deltax = x - lastxintercept; final int deltay = y - lastyintercept; /*根据条件判断是否拦截该事件*/ if (math.abs(deltax) > math.abs(deltay)) { intercepted = true; } else { intercepted = false; } break; case motionevent.action_up: intercepted = false; break; } lastxintercept = x; lastyintercept = y; return intercepted; }
这几乎是一个实现外部拦截事件的模板,这里一定不要在action_down 中返回 true,否则会让子view没有机会得到事件,因为如果在action_down的时候返回了 true,同一个事件序列viewgroup的dispatchtouchevent就不会在调用onintercepttouchevent方法了。
还有就是 在action_up中返回false,因为如果父控件拦截了action_up,那么子view将得不到up事件,那么将会影响子view的 onclick方法等。但这对父控件是没有影响的,因为如果是父控件子aciton_move中 就拦截了事件,他们up事件必定也会交给它处理,因为有那么一条定律叫做:父控件一但拦截了事件,那么同一个事件序列的所有事件都将交给他处理。这条结论在我的上一篇文章中已经分析过。
最后就是在 action_move中根据需求决定是否拦截。
2、内部拦截法,解决横竖冲突
内部拦截主要依赖于父控件的 requestdisallowintercepttouchevent方法,关于这个方法我的上篇文章其实已经分析过。他设置父控件的一个标志(flag_disallow_intercept)
这个标志可以决定父控件是否拦截事件,如果设置了这个标志则不拦截,如果没设这个标志,它就会调用父控件的onintercepttouchevent()来询问父控件是否拦截。但这个标志对down事件无效。
可以参考一下源码:
// handle an initial down. if (actionmasked == motionevent.action_down) { // throw away all previous state when starting a new touch gesture. // the framework may have dropped the up or cancel event for the previous gesture // due to an app switch, anr, or some other state change. cancelandcleartouchtargets(ev); //清楚标志 resettouchstate(); } // check for interception. final boolean intercepted; if (actionmasked == motionevent.action_down || mfirsttouchtarget != null) { //标志 final boolean disallowintercept = (mgroupflags & flag_disallow_intercept) != 0; if (!disallowintercept) { intercepted = onintercepttouchevent(ev); ev.setaction(action); // restore action in case it was changed } else { intercepted = false; } } else { // there are no touch targets and this action is not an initial down // so this view group continues to intercept touches. intercepted = true; }
那么我们如果想使用 内部拦截法拦截事件。
第一步:
a、我们要重写父控件的onintercepttouchevent,在action_down的时候返回false,负责的话子view调用requestdisallowintercepttouchevent也将无能为力。
b、还有就是其他事件的话都返回true,这样就把能否拦截事件的权利交给了子view。
第二步:
在子view的dispatchtouchevent中 来决定是否让父控件拦截事件。
a. 先要在motionevent.action_down:的时候使用mhorizontalex2.requestdisallowintercepttouchevent(true);,负责的话,下一个事件到来时,就交给父控件了。
b. 然后在motionevent.action_move: 根据业务逻辑决定是否调用mhorizontalex2.requestdisallowintercepttouchevent(false);来决定父控件是否拦截事件。
上代码horizontalex2.java:
/** * created by blueberry on 2016/6/20. * 内部拦截 * 和 listviewex配合使用 */ public class horizontalex2 extends viewgroup { private int lastx, lasty; private int childindex; private scroller mscroller; private velocitytracker mvelocitytracker; public horizontalex2(context context) { super(context); init(); } public horizontalex2(context context, attributeset attrs) { super(context, attrs); init(); } public horizontalex2(context context, attributeset attrs, int defstyleattr) { super(context, attrs, defstyleattr); init(); } private void init() { mscroller = new scroller(getcontext()); mvelocitytracker = velocitytracker.obtain(); } @override protected void onmeasure(int widthmeasurespec, int heightmeasurespec) { int width = measurespec.getsize(widthmeasurespec); int widthmode = measurespec.getmode(widthmeasurespec); int height = measurespec.getsize(heightmeasurespec); int heightmode = measurespec.getmode(heightmeasurespec); int childcount = getchildcount(); measurechildren(widthmeasurespec, heightmeasurespec); if (childcount == 0) { setmeasureddimension(0, 0); } else if (widthmode == measurespec.at_most && heightmode == measurespec.at_most) { height = getchildat(0).getmeasuredheight(); width = childcount * getchildat(0).getmeasuredwidth(); setmeasureddimension(width, height); } else if (widthmode == measurespec.at_most) { width = childcount * getchildat(0).getmeasuredwidth(); setmeasureddimension(width, height); } else { height = getchildat(0).getmeasuredheight(); setmeasureddimension(width, height); } } @override protected void onlayout(boolean changed, int l, int t, int r, int b) { int leftoffset = 0; for (int i = 0; i < getchildcount(); i++) { view child = getchildat(i); child.layout(l + leftoffset, t, r + leftoffset, b); leftoffset += child.getmeasuredwidth(); } } /** * 不拦截down事件,其他一律拦截 * @param ev * @return */ @override public boolean onintercepttouchevent(motionevent ev) { if (ev.getaction() == motionevent.action_down) { if (!mscroller.isfinished()) { mscroller.abortanimation(); return true; } return false; } else { return true; } } private boolean isfirsttouch = true; @override public boolean ontouchevent(motionevent event) { int x = (int) event.getx(); int y = (int) event.gety(); mvelocitytracker.addmovement(event); viewconfiguration configuration = viewconfiguration.get(getcontext()); switch (event.getaction()) { case motionevent.action_down: if (!mscroller.isfinished()) { mscroller.abortanimation(); } break; case motionevent.action_move: if (isfirsttouch) { isfirsttouch = false; lasty = y; lastx = x; } final int deltax = x - lastx; scrollby(-deltax, 0); break; case motionevent.action_up: isfirsttouch = true; int scrollx = getscrollx(); mvelocitytracker.computecurrentvelocity(1000, configuration.getscaledmaximumflingvelocity()); float mvelocityx = mvelocitytracker.getxvelocity(); if (math.abs(mvelocityx) > configuration.getscaledminimumflingvelocity()) { childindex = mvelocityx < 0 ? childindex + 1 : childindex - 1; } else { childindex = (scrollx + getchildat(0).getwidth() / 2) / getchildat(0).getwidth(); } childindex = math.min(getchildcount() - 1, math.max(0, childindex)); smoothscrollby(childindex*getchildat(0).getwidth()-scrollx,0); mvelocitytracker.clear(); break; } lastx = x; lasty = y; return true; } private void smoothscrollby(int dx, int dy) { mscroller.startscroll(getscrollx(), getscrolly(), dx, dy,500); invalidate(); } @override public void computescroll() { if(mscroller.computescrolloffset()){ scrollto(mscroller.getcurrx(),mscroller.getcurry()); postinvalidate(); } } @override protected void ondetachedfromwindow() { super.ondetachedfromwindow(); mvelocitytracker.recycle(); } }
listviewex.java
/** * 内部拦截事件 */ public class listviewex extends listview { private int lastxintercepted, lastyintercepted; private horizontalex2 mhorizontalex2; public listviewex(context context) { super(context); } public listviewex(context context, attributeset attrs) { super(context, attrs); } public listviewex(context context, attributeset attrs, int defstyleattr) { super(context, attrs, defstyleattr); } public horizontalex2 getmhorizontalex2() { return mhorizontalex2; } public void setmhorizontalex2(horizontalex2 mhorizontalex2) { this.mhorizontalex2 = mhorizontalex2; } /** * 使用 outter.requestdisallowintercepttouchevent(); * 来决定父控件是否对事件进行拦截 * @param ev * @return */ @override public boolean dispatchtouchevent(motionevent ev) { int x = (int) ev.getx(); int y = (int) ev.gety(); switch (ev.getaction()) { case motionevent.action_down: mhorizontalex2.requestdisallowintercepttouchevent(true); break; case motionevent.action_move: final int deltax = x-lastyintercepted; final int deltay = y-lastyintercepted; if(math.abs(deltax)>math.abs(deltay)){ mhorizontalex2.requestdisallowintercepttouchevent(false); } break; case motionevent.action_up: break; } lastxintercepted = x; lastyintercepted = y; return super.dispatchtouchevent(ev); } }
调用代码:
@override public void showinnerhvdata(list<string> data1, list<string> data2, list<string> data3) { listviewex listview1 = new listviewex(getcontext()); arrayadapter<string> adapter1 = new arrayadapter<string>(getcontext(), android.r.layout.simple_list_item_1, data1); listview1.setadapter(adapter1); listview1.setmhorizontalex2(mhorizontalex2); listviewex listview2 = new listviewex(getcontext()); arrayadapter<string> adapter2 = new arrayadapter<string>(getcontext(), android.r.layout.simple_list_item_1, data2); listview2.setadapter(adapter2); listview2.setmhorizontalex2(mhorizontalex2); listviewex listview3 = new listviewex(getcontext()); arrayadapter<string> adapter3 = new arrayadapter<string>(getcontext(), android.r.layout.simple_list_item_1, data3); listview3.setadapter(adapter3); listview3.setmhorizontalex2(mhorizontalex2); viewgroup.layoutparams params = new viewgroup.layoutparams(viewgroup.layoutparams.match_parent, viewgroup.layoutparams.match_parent); mhorizontalex2.addview(listview1, params); mhorizontalex2.addview(listview2, params); mhorizontalex2.addview(listview3, params); }
至此,2种拦截方法已经学习完毕,下面我们来学习如何解决同向滑动冲突。
其实和上面的2个例子思路是一样的,只是用来判断是否拦截的那块逻辑不同而已。
下面的例子,是一个下拉刷新的一个控件。
3、外部拦截 解决同向滑动冲突
refreshlayoutbase.java
package com.blueberry.sample.widget.refresh; import android.content.context; import android.graphics.color; import android.util.attributeset; import android.util.displaymetrics; import android.util.log; import android.util.typedvalue; import android.view.motionevent; import android.view.view; import android.view.viewconfiguration; import android.view.viewgroup; import android.view.windowmanager; import android.widget.progressbar; import android.widget.scroller; import android.widget.textview; import com.blueberry.sample.r; /** *外部拦截(同向) * */ public abstract class refreshlayoutbase<t extends view> extends viewgroup { private static final string tag = "refreshlayoutbase"; public static final int status_loading = 1; public static final int status_release_to_refresh = 2; public static final int status_pull_to_refresh = 3; public static final int status_idle = 4; public static final int status_load_more =5; private static int scroll_duration =500; protected viewgroup mheadview; protected viewgroup mfootview; private t contentview; private progressbar headprogressbar; private textview headtv; private progressbar footprogressbar; private textview foottv; private boolean isfisttouch = true; protected int currentstatus = status_idle; private int mscreenwidth; private int mscreenheight; private int mlastxintercepted; private int mlastyintercepted; private int mlastx; private int mlasty; protected int minitscrolly = 0; private int mtouchslop; protected scroller mscoller; private onrefreshlistener monrefreshlistener; public refreshlayoutbase(context context) { this(context, null); } public refreshlayoutbase(context context, attributeset attrs) { this(context, attrs, 0); } public refreshlayoutbase(context context, attributeset attrs, int defstyleattr) { super(context, attrs, defstyleattr); getscreensize(); initview(); mscoller = new scroller(context); mtouchslop = viewconfiguration.get(context).getscaledtouchslop(); setpadding(0, 0, 0, 0); } public void setcontentview(t view) { addview(view, 1); } public onrefreshlistener getonrefreshlistener() { return monrefreshlistener; } public void setonrefreshlistener(onrefreshlistener monrefreshlistener) { this.monrefreshlistener = monrefreshlistener; } private void initview() { setupheadview(); setupfootview(); } private void getscreensize() { windowmanager wm = (windowmanager) getcontext().getsystemservice(context.window_service); displaymetrics metrics = new displaymetrics(); wm.getdefaultdisplay().getmetrics(metrics); mscreenwidth = metrics.widthpixels; mscreenheight = metrics.heightpixels; } private int dp2px(int dp) { windowmanager wm = (windowmanager) getcontext().getsystemservice(context.window_service); displaymetrics metrics = new displaymetrics(); wm.getdefaultdisplay().getmetrics(metrics); return (int) typedvalue.applydimension(typedvalue.complex_unit_dip, dp, metrics); } /** * 设置头布局 */ private void setupheadview() { mheadview = (viewgroup) view.inflate(getcontext(), r.layout.fresh_head_view, null); mheadview.setbackgroundcolor(color.red); headprogressbar = (progressbar) mheadview.findviewbyid(r.id.head_progressbar); headtv = (textview) mheadview.findviewbyid(r.id.head_tv); /*设置 实际高度为 1/4 ,但内容区域只有 100dp*/ viewgroup.layoutparams layoutparams = new viewgroup.layoutparams(layoutparams.match_parent, mscreenheight / 4); mheadview.setlayoutparams(layoutparams); mheadview.setpadding(0, mscreenheight / 4 - dp2px(100), 0, 0); addview(mheadview); } /** * 设置尾布局 */ private void setupfootview() { mfootview = (viewgroup) view.inflate(getcontext(), r.layout.fresh_foot_view, null); mfootview.setbackgroundcolor(color.blue); footprogressbar = (progressbar) mfootview.findviewbyid(r.id.fresh_foot_progressbar); foottv = (textview) mfootview.findviewbyid(r.id.fresh_foot_tv); addview(mfootview); } @override protected void onmeasure(int widthmeasurespec, int heightmeasurespec) { int widthsize = measurespec.getsize(widthmeasurespec); int widthmode = measurespec.getmode(widthmeasurespec); int height = measurespec.getsize(heightmeasurespec); int heightmode = measurespec.getmode(heightmeasurespec); int finalheight = 0; for (int i = 0; i < getchildcount(); i++) { view child = getchildat(i); measurechild(child, widthmeasurespec, heightmeasurespec); finalheight += child.getmeasuredheight(); } if (widthmode == measurespec.at_most && heightmode == measurespec.at_most) { widthsize = getchildat(0).getmeasuredwidth(); setmeasureddimension(widthsize, finalheight); } else if (widthmode == measurespec.at_most) { widthsize = getchildat(0).getmeasuredwidth(); setmeasureddimension(widthsize, height); } else { setmeasureddimension(widthsize, finalheight); } } @override protected void onlayout(boolean changed, int l, int t, int r, int b) { int topoffset = 0; for (int i = 0; i < getchildcount(); i++) { view child = getchildat(i); child.layout(getpaddingleft(), getpaddingtop() + topoffset, r, getpaddingtop() + child.getmeasuredheight() + topoffset); topoffset += child.getmeasuredheight(); } minitscrolly = mheadview.getmeasuredheight() + getpaddingtop(); scrollto(0, minitscrolly); } @override public boolean onintercepttouchevent(motionevent ev) { boolean intercepted = false; int x = (int) ev.getx(); int y = (int) ev.gety(); switch (ev.getaction()) { case motionevent.action_down: mlastxintercepted = x; mlastyintercepted = y; break; case motionevent.action_move: final int deltay = x - mlastyintercepted; if (istop() && deltay > 0 && math.abs(deltay) > mtouchslop) { /*下拉*/ intercepted = true; } break; case motionevent.action_up: break; } mlastxintercepted = x; mlastyintercepted = y; return intercepted; } private void dorefresh() { log.i(tag, "dorefresh: "); if (currentstatus == status_release_to_refresh) { mscoller.startscroll(0, getscrolly(), 0, minitscrolly - getscrolly(), scroll_duration); currentstatus = status_idle; } else if (currentstatus == status_pull_to_refresh) { mscoller.startscroll(0,getscrolly(),0,0-getscrolly(),scroll_duration); if (null != monrefreshlistener) { currentstatus = status_loading; monrefreshlistener.refresh(); } } invalidate(); } @override public boolean ontouchevent(motionevent event) { int x = (int) event.getx(); int y = (int) event.gety(); switch (event.getaction()) { case motionevent.action_down: if (!mscoller.isfinished()) { mscoller.abortanimation(); } mlastx = x; mlasty = y; break; case motionevent.action_move: if (isfisttouch) { isfisttouch = false; mlastx = x; mlasty = y; } final int deltay = y - mlasty; if (currentstatus != status_loading) { changescrolly(deltay); } break; case motionevent.action_up: isfisttouch = true; dorefresh(); break; } mlastx = x; mlasty = y; return true; } private void changescrolly(int deltay) { log.i(tag, "changescrolly: "); int cury = getscrolly(); if (deltay > 0) { /*下拉*/ if (cury - deltay > getpaddingtop()) { scrollby(0, -deltay); } } else { /*上拉*/ if (cury - deltay <= minitscrolly) { scrollby(0, -deltay); } } cury = getscrolly(); int slop = minitscrolly / 2; if (cury > 0 && cury <=slop) { currentstatus = status_pull_to_refresh; } else if (cury > 0 && cury >= slop) { currentstatus = status_release_to_refresh; } } @override public void computescroll() { if (mscoller.computescrolloffset()) { scrollto(mscoller.getcurrx(), mscoller.getcurry()); postinvalidate(); } } /** * 加载完成调用这个方法 */ public void refreshcomplete() { mscoller.startscroll(0, getscrolly(), 0, minitscrolly - getscrolly(), scroll_duration); currentstatus = status_idle; invalidate(); } /** * 显示 footer */ public void showfooter() { if(currentstatus==status_load_more) return ; currentstatus = status_load_more ; mscoller.startscroll(0, getscrolly(), 0, mfootview.getmeasuredheight() , scroll_duration); invalidate(); } /** * loadmore完成之后调用 */ public void footercomplete() { mscoller.startscroll(0, getscrolly(), 0, minitscrolly - getscrolly(), scroll_duration); invalidate(); currentstatus = status_idle; } public interface onrefreshlistener { void refresh(); } abstract boolean istop(); abstract boolean isbottom(); }
它是一个抽象类,需要编写子类继承istop()和 isbottom()方法。下面给出它的一个实现类:
package com.blueberry.sample.widget.refresh; import android.content.context; import android.util.attributeset; import android.widget.abslistview; import android.widget.listview; /** * created by blueberry on 2016/6/21. * * refreshlayoutbase 的一个实现类 */ public class refreshlistview extends refreshlayoutbase<listview> { private static final string tag = "refreshlistview"; private listview listview; private onloadlistener loadlistener; public refreshlistview(context context) { super(context); } public refreshlistview(context context, attributeset attrs) { super(context, attrs); } public refreshlistview(context context, attributeset attrs, int defstyleattr) { super(context, attrs, defstyleattr); } public listview getlistview() { return listview; } public void setlistview(final listview listview) { this.listview = listview; setcontentview(listview); this.listview.setonscrolllistener(new abslistview.onscrolllistener() { @override public void onscrollstatechanged(abslistview view, int scrollstate) { } @override public void onscroll(abslistview view, int firstvisibleitem, int visibleitemcount, int totalitemcount) { /*这里存在一个bug: 当listview滑动到底部的时候,如果下拉也会出现footer * 这是因为,暂时还没有想到如何判断是下拉还是上拉。 * 如果要解决此问题,我觉得应该重写listview 的ontouchevent来判断手势方向 * 次模块主要解决竖向滑动冲突,故现将此问题放下。 * */ if (currentstatus == status_idle && getscrolly() <= minitscrolly && isbottom() ) { showfooter(); if (null != loadlistener) { loadlistener.onloadmore(); } } } }); } public onloadlistener getloadlistener() { return loadlistener; } public void setloadlistener(onloadlistener loadlistener) { this.loadlistener = loadlistener; } @override boolean istop() { return listview.getfirstvisibleposition() == 0 && getscrolly() <= mheadview.getmeasuredheight(); } @override boolean isbottom() { return listview.getlastvisibleposition() == listview.getadapter().getcount() - 1; } public interface onloadlistener { void onloadmore(); } }
4、内部拦截法解决同向滑动
同样是一个下拉刷新组件,因为实现原理都一样,所以这个写的比较随意些。主要还是如果解决滑动冲突。
refreshlayoutbase2.java
package com.blueberry.sample.widget.refresh; import android.content.context; import android.graphics.color; import android.util.attributeset; import android.util.log; import android.view.motionevent; import android.view.view; import android.view.viewgroup; import android.widget.arrayadapter; import android.widget.listview; import android.widget.scroller; import com.blueberry.sample.r; import java.util.arraylist; import java.util.list; /** * created by blueberry on 2016/6/22. * 结合内部类 listvieex * 内部拦截法,同向 */ public class refreshlayoutbase2 extends viewgroup { private static final string tag = "refreshlayoutbase2"; private static list<string> datas; static { datas = new arraylist<>(); for (int i = 0; i < 40; i++) { datas.add("数据—" + i); } } private viewgroup headview; private listviewex lv; private int lasty; public int minitscrolly; private scroller mscroller; public refreshlayoutbase2(context context) { this(context, null); } public refreshlayoutbase2(context context, attributeset attrs) { this(context, attrs, 0); } public refreshlayoutbase2(context context, attributeset attrs, int defstyleattr) { super(context, attrs, defstyleattr); mscroller = new scroller(context); setupheadview(context); setupcontentview(context); } @override protected void onmeasure(int widthmeasurespec, int heightmeasurespec) { int widthsize = measurespec.getsize(widthmeasurespec); int widthmode = measurespec.getmode(widthmeasurespec); int height = measurespec.getsize(heightmeasurespec); int heightmode = measurespec.getmode(heightmeasurespec); int finalheight = 0; for (int i = 0; i < getchildcount(); i++) { view child = getchildat(i); measurechild(child, widthmeasurespec, heightmeasurespec); finalheight += child.getmeasuredheight(); } if (widthmode == measurespec.at_most && heightmode == measurespec.at_most) { widthsize = getchildat(0).getmeasuredwidth(); setmeasureddimension(widthsize, finalheight); } else if (widthmode == measurespec.at_most) { widthsize = getchildat(0).getmeasuredwidth(); setmeasureddimension(widthsize, height); } else { setmeasureddimension(widthsize, finalheight); } } @override protected void onlayout(boolean changed, int l, int t, int r, int b) { int topoffset = 0; for (int i = 0; i < getchildcount(); i++) { view child = getchildat(i); child.layout(getpaddingleft(), getpaddingtop() + topoffset, r, getpaddingtop() + child.getmeasuredheight() + topoffset); topoffset += child.getmeasuredheight(); } minitscrolly = headview.getmeasuredheight() + getpaddingtop(); scrollto(0, minitscrolly); } /** * 不拦截down 其他一律拦截 * @param ev * @return */ @override public boolean onintercepttouchevent(motionevent ev) { if (ev.getaction() == motionevent.action_down) return false; return true; } @override public boolean ontouchevent(motionevent event) { int y = (int) event.gety(); switch (event.getaction()) { case motionevent.action_down: break; case motionevent.action_move: final int deltay = y-lasty; log.i(tag, "ontouchevent: deltay: "+deltay); if (deltay >= 0 && lv.istop() && getscrolly() - deltay >=getpaddingtop()) { scrollby(0, -deltay); } break; case motionevent.action_up: this.postdelayed(new runnable() { @override public void run() { mscroller.startscroll(0,getscrolly(),0,minitscrolly-getscrolly()); invalidate(); } },2000); break; } lasty = y ; return true; } private void setupheadview(context context) { headview = (viewgroup) view.inflate(context, r.layout.fresh_head_view, null); headview.setbackgroundcolor(color.red); viewgroup.layoutparams params = new viewgroup.layoutparams(viewgroup.layoutparams.match_parent, 300); addview(headview, params); } public void setupcontentview(context context) { lv = new listviewex(context, this); lv.setbackgroundcolor(color.blue); arrayadapter<string> adapter = new arrayadapter<string>(getcontext(), android.r.layout.simple_list_item_1, datas); lv.setadapter(adapter); addview(lv, new viewgroup.layoutparams(viewgroup.layoutparams.match_parent, viewgroup.layoutparams.match_parent)); } @override public void computescroll() { if(mscroller.computescrolloffset()){ scrollto(mscroller.getcurrx(),mscroller.getcurry()); postinvalidate(); } } public static class listviewex extends listview { private refreshlayoutbase2 outter; public listviewex(context context, refreshlayoutbase2 outter) { super(context); this.outter = outter; } public listviewex(context context, attributeset attrs) { super(context, attrs); } public listviewex(context context, attributeset attrs, int defstyleattr) { super(context, attrs, defstyleattr); } /** * 使用 outter.requestdisallowintercepttouchevent(); * 来决定父控件是否对事件进行拦截 * @param ev * @return */ @override public boolean dispatchtouchevent(motionevent ev) { switch (ev.getaction()) { case motionevent.action_down: outter.requestdisallowintercepttouchevent(true); break; case motionevent.action_move: if ( istop() && outter.getscrolly() <= outter.minitscrolly) { outter.requestdisallowintercepttouchevent(false); } break; } return super.dispatchtouchevent(ev); } public boolean istop() { return getfirstvisibleposition() ==0; } } }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。