溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Android開發(fā)中使用View實現(xiàn)一個垂直上拉下滑功能

發(fā)布時間:2020-11-20 15:43:53 來源:億速云 閱讀:297 作者:Leah 欄目:移動開發(fā)

Android開發(fā)中使用View實現(xiàn)一個垂直上拉下滑功能?很多新手對此不是很清楚,為了幫助大家解決這個難題,下面小編將為大家詳細講解,有這方面需求的人可以來學習下,希望你能有所收獲。

效果

Android開發(fā)中使用View實現(xiàn)一個垂直上拉下滑功能

二、實現(xiàn)思路

1、這個效果其實有很多實現(xiàn)方法,為了讓松手時有一個viewpager一樣的緩慢滑動的效果我選擇用scrollBy配合Scroller,應(yīng)該是既方便又實用的。
2、這個View的設(shè)計是這樣的:
(1)將這個View的子view通過layout放在該View下面;
(2)通過重寫onTouchEvent方法給這個子View滑動效果,在MOVE_UP的動作給這個子View加上Scroller平滑到View的頂部或者底部。
見圖:

Android開發(fā)中使用View實現(xiàn)一個垂直上拉下滑功能

三、實現(xiàn)

1、先自定義一個屬性,表示子View應(yīng)該有多少部分露在外面,也就是上圖中紅色和綠色相交的部分。
在res文件夾-values文件夾下面創(chuàng)建一個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>

在代碼中調(diào)用該屬性(該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之前調(diào)用。

 @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方法里做一些初始化操作,比如說創(chuàng)建一個Scroller對象,給View的背景設(shè)為透明:

 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內(nèi)可見的高度
 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、關(guān)于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內(nèi)可見的高度
 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();
  }
 }
}

看完上述內(nèi)容是否對您有幫助呢?如果還想對相關(guān)知識有進一步的了解或閱讀更多相關(guān)文章,請關(guān)注億速云行業(yè)資訊頻道,感謝您對億速云的支持。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI