这篇文章主要介绍了 Android 制作简单垂直上拉下滑 View 效果的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
Android 是一种基于 Linux 的自由及开放源代码的操作系统,主要使用于移动设备,如智能手机和平板电脑,由 Google 公司和开放手机联盟领导及开发。尚未有统一中文名称,中国大陆地区较多人使用 "安卓" 或 "安致"。
一、简介
最近朋友公司需要实现一个垂直上拉下滑的 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 的用法,可参考郭霖的这篇博客:{aa0aa}
四、完整代码:
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();
- }
- }
- }
来源: http://www.phperz.com/article/17/0312/320598.html