一、简介

最近朋友公司需要实现一个垂直上拉下滑的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的用法,可参考郭霖的这篇博客:http://blog.csdn.net/guolin_blog/article/details/48719871
四、完整代码:
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();
}
}
}
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
# Android垂直上拉下滑View
# Android垂直上拉View
# Android垂直下滑View
# Android下拉刷新上拉加载控件(适用于所有View)
# android开发教程之实现listview下拉刷新和上拉刷新效果
# Android实现上拉加载更多以及下拉刷新功能(ListView)
# Android RecyclerView实现下拉刷新和上拉加载
# Android RecyclerView 上拉加载更多及下拉刷新功能的实现方法
# android搜索框上下滑动变色效果
# Android中 视频屏幕左半部分上下滑动改变亮度右半部分上下滑动改变声音
# Android RecyclerView下拉刷新和上拉加载更多
# Android自定义listview布局实现上拉加载下拉刷新功能
# Android实战教程第四十三篇之上拉加载与下拉刷新
# 重写
# 按下
# 有一
# 最初
# 屏幕上
# 让它
# 想把
# 大可
# 将该
# 不能超过
# 创建一个
# 为了防止
# 时需
# 的是
# 滑到
# 放在
# 超过了
# 我还
# 是这样
# 有很多
相关文章:
制作网站建设的公司有哪些,网站建设比较好的公司都有哪些?
早安海报制作网站推荐大全,企业早安海报怎么每天更换?
详解ASP.NET 生成二维码实例(采用ThoughtWorks.QRCode和QrCode.Net两种方式)
洛阳网站制作公司有哪些,洛阳的招聘网站都有哪些?
如何高效利用200m空间完成建站?
建站之星3.0如何解决常见操作问题?
如何用搬瓦工VPS快速搭建个人网站?
免费的流程图制作网站有哪些,2025年教师初级职称申报网上流程?
建站主机类型有哪些?如何正确选型
如何正确下载安装西数主机建站助手?
如何打造高效商业网站?建站目的决定转化率
测试制作网站有哪些,测试性取向的权威测试或者网站?
实例解析Array和String方法
如何在七牛云存储上搭建网站并设置自定义域名?
最好的网站制作公司,网购哪个网站口碑最好,推荐几个?谢谢?
建站之星客服服务时间及联系方式如何?
武汉网站设计制作公司,武汉有哪些比较大的同城网站或论坛,就是里面都是武汉人的?
网站制作价目表怎么做,珍爱网婚介费用多少?
网站制作中优化长尾关键字挖掘的技巧,建一个视频网站需要多少钱?
宝塔新建站点为何无法访问?如何排查?
建站之星后台密码如何安全设置与找回?
常州自助建站工具推荐:低成本搭建与模板选择技巧
网站制作怎么样才能赚钱,用自己的电脑做服务器架设网站有什么利弊,能赚钱吗?
如何在云指建站中生成FTP站点?
微信推文制作网站有哪些,怎么做微信推文,急?
深圳企业网站制作设计,在深圳如何网上全流程注册公司?
建站主机核心功能解析:服务器选择与网站搭建流程指南
建站中国官网:模板定制+SEO优化+建站流程一站式指南
如何通过wdcp面板快速创建网站?
代购小票制作网站有哪些,购物小票的简要说明?
如何通过西部数码建站助手快速创建专业网站?
企业网站制作费用多少,企业网站空间一般需要多大,费用是多少?
如何通过山东自助建站平台快速注册域名?
合肥做个网站多少钱,合肥本地有没有比较靠谱的交友平台?
如何用5美元大硬盘VPS安全高效搭建个人网站?
建站主机选购指南:核心配置优化与品牌推荐方案
如何处理“XML格式不正确”错误 常见XML well-formed问题解决方法
制作网站的过程怎么写,用凡科建站如何制作自己的网站?
如何在Golang中处理模块冲突_解决依赖版本不兼容问题
,怎么用自己头像做动态表情包?
如何选择高性价比服务器搭建个人网站?
零服务器AI建站解决方案:快速部署与云端平台低成本实践
如何通过商城免费建站系统源码自定义网站主题?
建站主机选哪家性价比最高?
建站之星代理如何获取技术支持?
网站制作报价单模板图片,小松挖机官方网站报价?
打鱼网站制作软件,波克捕鱼官方号怎么注册?
XML的“混合内容”是什么 怎么用DTD或XSD定义
天河区网站制作公司,广州天河区如何办理身份证?需要什么资料有预约的网站吗?
广州商城建站系统开发成本与周期如何控制?
*请认真填写需求信息,我们会在24小时内与您取得联系。