Android仿淘宝商品浏览界面图片滚动效果
用手机淘宝浏览商品详情时,商品图片是放在后面的,在第一个scrollview滚动到最底下时会有提示,继续拖动才能浏览图片。仿照这个效果写一个出来并不难,只要定义一个layout管理两个scrollview就行了,当第一个scrollview滑到底部时,再次向上滑动进入第二个scrollview。效果如下:
需要注意的地方是:
1、如果是手动滑到底部需要再次按下才能继续往下滑,自动滚动到底部则不需要
2、在由上一个scrollview滑动到下一个scrollview的过程中多只手指相继拖动也不会导致布局的剧变,也就是多个pointer的滑动不会导致move距离的剧变。
这个layout的实现思路是:
在布局中放置两个scrollview,并为其设置ontouchlistener,时刻判断scrollview的滚动距离,一旦第一个scrollview滚动到底部,则标识改为可向上拖动,此时开始记录滑动距离mmovelen,根据mmovelen重新layout两个scrollview;同理,监听第二个scrollview是否滚动到顶部,以往下拖动。
ok,明白了原理之后可以看代码了:
package com.jingchen.tbviewer; import java.util.timer; import java.util.timertask; import android.content.context; import android.os.handler; import android.os.message; import android.util.attributeset; import android.view.motionevent; import android.view.velocitytracker; import android.view.view; import android.widget.relativelayout; import android.widget.scrollview; /** * 包含两个scrollview的容器 * * @author chenjing * */ public class scrollviewcontainer extends relativelayout { /** * 自动上滑 */ public static final int auto_up = 0; /** * 自动下滑 */ public static final int auto_down = 1; /** * 动画完成 */ public static final int done = 2; /** * 动画速度 */ public static final float speed = 6.5f; private boolean ismeasured = false; /** * 用于计算手滑动的速度 */ private velocitytracker vt; private int mviewheight; private int mviewwidth; private view topview; private view bottomview; private boolean canpulldown; private boolean canpullup; private int state = done; /** * 记录当前展示的是哪个view,0是topview,1是bottomview */ private int mcurrentviewindex = 0; /** * 手滑动距离,这个是控制布局的主要变量 */ private float mmovelen; private mytimer mtimer; private float mlasty; /** * 用于控制是否变动布局的另一个条件,mevents==0时布局可以拖拽了,mevents==-1时可以舍弃将要到来的第一个move事件, * 这点是去除多点拖动剧变的关键 */ private int mevents; private handler handler = new handler() { @override public void handlemessage(message msg) { if (mmovelen != 0) { if (state == auto_up) { mmovelen -= speed; if (mmovelen <= -mviewheight) { mmovelen = -mviewheight; state = done; mcurrentviewindex = 1; } } else if (state == auto_down) { mmovelen += speed; if (mmovelen >= 0) { mmovelen = 0; state = done; mcurrentviewindex = 0; } } else { mtimer.cancel(); } } requestlayout(); } }; public scrollviewcontainer(context context) { super(context); init(); } public scrollviewcontainer(context context, attributeset attrs) { super(context, attrs); init(); } public scrollviewcontainer(context context, attributeset attrs, int defstyle) { super(context, attrs, defstyle); init(); } private void init() { mtimer = new mytimer(handler); } @override public boolean dispatchtouchevent(motionevent ev) { switch (ev.getactionmasked()) { case motionevent.action_down: if (vt == null) vt = velocitytracker.obtain(); else vt.clear(); mlasty = ev.gety(); vt.addmovement(ev); mevents = 0; break; case motionevent.action_pointer_down: case motionevent.action_pointer_up: // 多一只手指按下或抬起时舍弃将要到来的第一个事件move,防止多点拖拽的bug mevents = -1; break; case motionevent.action_move: vt.addmovement(ev); if (canpullup && mcurrentviewindex == 0 && mevents == 0) { mmovelen += (ev.gety() - mlasty); // 防止上下越界 if (mmovelen > 0) { mmovelen = 0; mcurrentviewindex = 0; } else if (mmovelen < -mviewheight) { mmovelen = -mviewheight; mcurrentviewindex = 1; } if (mmovelen < -8) { // 防止事件冲突 ev.setaction(motionevent.action_cancel); } } else if (canpulldown && mcurrentviewindex == 1 && mevents == 0) { mmovelen += (ev.gety() - mlasty); // 防止上下越界 if (mmovelen < -mviewheight) { mmovelen = -mviewheight; mcurrentviewindex = 1; } else if (mmovelen > 0) { mmovelen = 0; mcurrentviewindex = 0; } if (mmovelen > 8 - mviewheight) { // 防止事件冲突 ev.setaction(motionevent.action_cancel); } } else mevents++; mlasty = ev.gety(); requestlayout(); break; case motionevent.action_up: mlasty = ev.gety(); vt.addmovement(ev); vt.computecurrentvelocity(700); // 获取y方向的速度 float myv = vt.getyvelocity(); if (mmovelen == 0 || mmovelen == -mviewheight) break; if (math.abs(myv) < 500) { // 速度小于一定值的时候当作静止释放,这时候两个view往哪移动取决于滑动的距离 if (mmovelen <= -mviewheight / 2) { state = auto_up; } else if (mmovelen > -mviewheight / 2) { state = auto_down; } } else { // 抬起手指时速度方向决定两个view往哪移动 if (myv < 0) state = auto_up; else state = auto_down; } mtimer.schedule(2); try { vt.recycle(); } catch (exception e) { e.printstacktrace(); } break; } super.dispatchtouchevent(ev); return true; } @override protected void onlayout(boolean changed, int l, int t, int r, int b) { topview.layout(0, (int) mmovelen, mviewwidth, topview.getmeasuredheight() + (int) mmovelen); bottomview.layout(0, topview.getmeasuredheight() + (int) mmovelen, mviewwidth, topview.getmeasuredheight() + (int) mmovelen + bottomview.getmeasuredheight()); } @override protected void onmeasure(int widthmeasurespec, int heightmeasurespec) { super.onmeasure(widthmeasurespec, heightmeasurespec); if (!ismeasured) { ismeasured = true; mviewheight = getmeasuredheight(); mviewwidth = getmeasuredwidth(); topview = getchildat(0); bottomview = getchildat(1); bottomview.setontouchlistener(bottomviewtouchlistener); topview.setontouchlistener(topviewtouchlistener); } } private ontouchlistener topviewtouchlistener = new ontouchlistener() { @override public boolean ontouch(view v, motionevent event) { scrollview sv = (scrollview) v; if (sv.getscrolly() == (sv.getchildat(0).getmeasuredheight() - sv .getmeasuredheight()) && mcurrentviewindex == 0) canpullup = true; else canpullup = false; return false; } }; private ontouchlistener bottomviewtouchlistener = new ontouchlistener() { @override public boolean ontouch(view v, motionevent event) { scrollview sv = (scrollview) v; if (sv.getscrolly() == 0 && mcurrentviewindex == 1) canpulldown = true; else canpulldown = false; return false; } }; class mytimer { private handler handler; private timer timer; private mytask mtask; public mytimer(handler handler) { this.handler = handler; timer = new timer(); } public void schedule(long period) { if (mtask != null) { mtask.cancel(); mtask = null; } mtask = new mytask(handler); timer.schedule(mtask, 0, period); } public void cancel() { if (mtask != null) { mtask.cancel(); mtask = null; } } class mytask extends timertask { private handler handler; public mytask(handler handler) { this.handler = handler; } @override public void run() { handler.obtainmessage().sendtotarget(); } } } }
注释写的很清楚了,有几个关键点需要讲一下:
1、由于这里为两个scrollview设置了ontouchlistener,所以在其他地方不能再设置了,否则就白搭了。
2、两个scrollview的layout参数统一由mmovelen决定。
3、变量mevents有两个作用:一是防止手动滑到底部或顶部时继续滑动而改变布局,必须再次按下才能继续滑动;二是在新的pointer down或up时把mevents设置成-1可以舍弃将要到来的第一个move事件,防止mmovelen出现剧变。为什么会出现剧变呢?因为假设一开始只有一只手指在滑动,记录的坐标值是这个pointer的事件坐标点,这时候另一只手指按下了导致事件又多了一个pointer,这时候到来的move事件的坐标可能就变成了新的pointer的坐标,这时计算与上一次坐标的差值就会出现剧变,变化的距离就是两个pointer间的距离。所以要把这个move事件舍弃掉,让mlasty值记录这个pointer的坐标再开始计算mmovelen。pointer up的时候也一样。
理解了这几点,看起来就没什么难度了,代码量也很小。
mainactivity的布局:
<relativelayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <com.jingchen.tbviewer.scrollviewcontainer android:layout_width="match_parent" android:layout_height="match_parent" > <scrollview android:layout_width="match_parent" android:layout_height="match_parent" > <relativelayout android:layout_width="wrap_content" android:layout_height="wrap_content" > <linearlayout android:id="@+id/imageslayout" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_horizontal" android:orientation="vertical" > <imageview android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/h" /> <imageview android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/i" /> <imageview android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/j" /> <imageview android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/k" /> <imageview android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/l" /> <imageview android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/m" /> </linearlayout> <textview android:layout_width="match_parent" android:layout_height="60dp" android:layout_below="@id/imageslayout" android:background="#eeeeee" android:gravity="center" android:text="继续拖动,查看更多美女" android:textsize="20sp" /> </relativelayout> </scrollview> <scrollview android:layout_width="match_parent" android:layout_height="match_parent" android:background="#000000" > <linearlayout android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center_horizontal" android:orientation="vertical" > <imageview android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/a" /> <imageview android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/b" /> <imageview android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/c" /> <imageview android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/d" /> <imageview android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/e" /> <imageview android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/f" /> <imageview android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/g" /> </linearlayout> </scrollview> </com.jingchen.tbviewer.scrollviewcontainer> </relativelayout>
在scrollview中放了几张图片而已。
mainactivity的代码:
package com.jingchen.tbviewer; import android.app.activity; import android.os.bundle; import android.view.menu; public class mainactivity extends activity { @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); } @override public boolean oncreateoptionsmenu(menu menu) { getmenuinflater().inflate(r.menu.main, menu); return true; } }
以上就是本文的全部内容,希望对大家的学习有所帮助。