Android制作简单垂直上拉下滑View效果
一、简介
最近朋友公司需要实现一个垂直上拉下滑的view,该view最初只有一部分显示在屏幕最下方,上拉那一部分可以将该view全部拉出来并全部显示在屏幕上,下滑该view可以将该view隐藏在屏幕下。
先看一下最终实现效果吧。
二、实现思路
1、这个效果其实有很多实现方法,为了让松手时有一个viewpager一样的缓慢滑动的效果我选择用scrollby配合scroller,应该是既方便又实用的。
2、这个view的设计是这样的:
(1)将这个view的子view通过layout放在该view下面;
(2)通过重写ontouchevent方法给这个子view滑动效果,在move_up的动作给这个子view加上scroller平滑到view的顶部或者底部。
见图:
三、实现
1、先自定义一个属性,表示子view应该有多少部分露在外面,也就是上图中红色和绿色相交的部分。
在res文件夹-values文件夹下面创建一个attrs.xml文件
attrs.xml :
<resources> <declare-styleable name="myscrollerview"> <attr name="visibility_height" format="dimension"></attr> </declare-styleable> </resources>
在xml文件中引用该属性:
<com.zw.myfirstapp.myscrollerview android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignparentbottom="true" android:background="@android:color/transparent" android:id="@+id/msv" app:visibility_height="100dp" ></com.zw.myfirstapp.myscrollerview>
在代码中调用该属性(该view名字为myscrollerview,我图方便继承的是linearlayout,继承viewgroup或者其他的布局view都可以):
public myscrollerview(context context) { this(context,null); } public myscrollerview(context context, attributeset attrs) { this(context, attrs,0); } public myscrollerview(context context, attributeset attrs, int defstyleattr) { super(context, attrs, defstyleattr); typedarray ta = context.obtainstyledattributes(attrs,r.styleable.myscrollerview); visibilityheight = ta.getdimension(r.styleable.myscrollerview_visibility_height,200); ta.recycle(); init(context); }
2、重写onfinishinflate方法,重写该方法的原因是我希望我只有一个子view,这样就好确定滑动的高度,不然我还需要重新计算子view们的高度总和,比较麻烦。这个方法会在onmeasure之前调用。
@override protected void onfinishinflate() { super.onfinishinflate(); if(getchildcount() == 0 || getchildat(0) == null){ throw new runtimeexception("没有子控件!"); } if(getchildcount() > 1){ throw new runtimeexception("只能有一个子控件!"); } mchild = getchildat(0); }
3、init方法里做一些初始化操作,比如说创建一个scroller对象,给view的背景设为透明:
private void init(context context) { mscroller = new scroller(context); this.setbackgroundcolor(color.transparent); }
4、重写onmeasure方法和onlayout方法,确定可以滑动的最大高度,以及子view的排列位置(其实可以不用重写onmeasure,我这样写只是习惯)。
@override protected void onmeasure(int widthmeasurespec, int heightmeasurespec) { super.onmeasure(widthmeasurespec, heightmeasurespec); mscrollheight = (int) (mchild.getmeasuredheight() - visibilityheight); } @override protected void onlayout(boolean changed, int l, int t, int r, int b) { super.onlayout(changed, l, t, r, b); mchild.layout(0,mscrollheight,mchild.getmeasuredwidth(),mchild.getmeasuredheight() + mscrollheight); }
5、先看我定义的成员变量的含义吧:
/** * downy:手指按下时距离view顶部的距离 * movey:手指在屏幕上滑动的距离(不停变化) * movedy:手指在屏幕上总共滑动的距离(为了确定手指一共滑动了多少距离,不能超过可滑动的最大距离) */ private int downy,movey,movedy; //子view private view mchild; private scroller mscroller; //可滑动的最大距离 private int mscrollheight; //子view是否在顶部 private boolean istop = false; //最初子view在view内可见的高度 private float visibilityheight;
6、重写ontouchevent方法,做滑动判断,解释都写在注释里了:
@override public boolean ontouchevent(motionevent event) { switch (event.getaction()){ case motionevent.action_down: //手指按下时距离view上面的距离 downy = (int) event.gety(); //如果子view不在顶部 && 按下的位置在子view没有显示的位置,则不消费此次滑动事件,否则消费 if(!istop && downy < mscrollheight ){ return super.ontouchevent(event); } return true; case motionevent.action_move: movey = (int) event.gety(); //dey是滑动的距离,向上滑时dey>0 ,向下滑时dey<0 int dey = downy - movey; //向上滑动时的处理 if(dey > 0){ //将每次滑动的距离相加,为了防止子view的滑动超过view的顶部 movedy += dey; if(movedy > mscrollheight) movedy = mscrollheight; if(movedy < mscrollheight){ scrollby(0,dey); downy = movey; return true; } } //向下滑动时的处理,向下滑动时需要判断子view是否在顶部,如果不在顶部则不消费此次事件 if(dey < 0 && istop){ movedy += dey; if(movedy < 0 ) movedy = 0; if(movedy > 0){ scrollby(0,dey); } downy = movey; return true; } break; case motionevent.action_up: //手指抬起时的处理,如果向上滑动的距离超过了最大可滑动距离的1/4,并且子view不在顶部,就表示想把它拉上去 if(movedy > mscrollheight / 4 && !istop){ mscroller.startscroll(0,getscrolly(),0,(mscrollheight - getscrolly())); invalidate(); movedy = mscrollheight; istop = true; }else { //否则就表示放弃本次滑动,让它滑到最初的位置 mscroller.startscroll(0,getscrolly(),0, -getscrolly()); postinvalidate(); movedy = 0; istop = false; } break; } return super.ontouchevent(event); }
7、最后要重写一个computescroll方法,该方法是用来配合scroller的:
@override public void computescroll() { super.computescroll(); if(mscroller.computescrolloffset()){ scrollto(0,mscroller.getcurry()); postinvalidate(); } }
8、关于scroller的用法,可参考郭霖的这篇博客:
四、完整代码:
xml:
<relativelayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent"> <com.zw.myfirstapp.myscrollerview android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignparentbottom="true" android:background="@android:color/transparent" android:id="@+id/msv" app:visibility_height="100dp" > <linearlayout android:layout_width="match_parent" android:layout_height="300dp" android:background="@mipmap/b" android:gravity="center" android:orientation="vertical"> <button android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/btn" android:text="我是一个按钮"/> </linearlayout> </com.zw.myfirstapp.myscrollerview> </relativelayout>
myscrollerview:
public class myscrollerview extends linearlayout { /** * downy:手指按下时距离view顶部的距离 * movey:手指在屏幕上滑动的距离(不停变化) * movedy:手指在屏幕上总共滑动的距离(为了确定手指一共滑动了多少距离,不能超过可滑动的最大距离) */ private int downy,movey,movedy; //子view private view mchild; private scroller mscroller; //可滑动的最大距离 private int mscrollheight; //子view是否在顶部 private boolean istop = false; //最初子view在view内可见的高度 private float visibilityheight; public myscrollerview(context context) { this(context,null); } public myscrollerview(context context, attributeset attrs) { this(context, attrs,0); } public myscrollerview(context context, attributeset attrs, int defstyleattr) { super(context, attrs, defstyleattr); typedarray ta = context.obtainstyledattributes(attrs,r.styleable.myscrollerview); visibilityheight = ta.getdimension(r.styleable.myscrollerview_visibility_height,200); ta.recycle(); init(context); } @override protected void onmeasure(int widthmeasurespec, int heightmeasurespec) { super.onmeasure(widthmeasurespec, heightmeasurespec); mscrollheight = (int) (mchild.getmeasuredheight() - visibilityheight); } @override protected void onlayout(boolean changed, int l, int t, int r, int b) { super.onlayout(changed, l, t, r, b); mchild.layout(0,mscrollheight,mchild.getmeasuredwidth(),mchild.getmeasuredheight() + mscrollheight); } private void init(context context) { mscroller = new scroller(context); this.setbackgroundcolor(color.transparent); } @override protected void onfinishinflate() { super.onfinishinflate(); if(getchildcount() == 0 || getchildat(0) == null){ throw new runtimeexception("没有子控件!"); } if(getchildcount() > 1){ throw new runtimeexception("只能有一个子控件!"); } mchild = getchildat(0); } @override public boolean ontouchevent(motionevent event) { switch (event.getaction()){ case motionevent.action_down: //手指按下时距离view上面的距离 downy = (int) event.gety(); //如果子view不在顶部 && 按下的位置在子view没有显示的位置,则不消费此次滑动事件,否则消费 if(!istop && downy < mscrollheight ){ return super.ontouchevent(event); } return true; case motionevent.action_move: movey = (int) event.gety(); //dey是滑动的距离,向上滑时dey>0 ,向下滑时dey<0 int dey = downy - movey; //向上滑动时的处理 if(dey > 0){ //将每次滑动的距离相加,为了防止子view的滑动超过view的顶部 movedy += dey; if(movedy > mscrollheight) movedy = mscrollheight; if(movedy < mscrollheight){ scrollby(0,dey); downy = movey; return true; } } //向下滑动时的处理,向下滑动时需要判断子view是否在顶部,如果不在顶部则不消费此次事件 if(dey < 0 && istop){ movedy += dey; if(movedy < 0 ) movedy = 0; if(movedy > 0){ scrollby(0,dey); } downy = movey; return true; } break; case motionevent.action_up: //手指抬起时的处理,如果向上滑动的距离超过了最大可滑动距离的1/4,并且子view不在顶部,就表示想把它拉上去 if(movedy > mscrollheight / 4 && !istop){ mscroller.startscroll(0,getscrolly(),0,(mscrollheight - getscrolly())); invalidate(); movedy = mscrollheight; istop = true; }else { //否则就表示放弃本次滑动,让它滑到最初的位置 mscroller.startscroll(0,getscrolly(),0, -getscrolly()); postinvalidate(); movedy = 0; istop = false; } break; } return super.ontouchevent(event); } @override public void computescroll() { super.computescroll(); if(mscroller.computescrolloffset()){ scrollto(0,mscroller.getcurry()); postinvalidate(); } } }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。