溫馨提示×

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

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

Android下拉阻尼效果實(shí)現(xiàn)原理及簡單實(shí)例

發(fā)布時(shí)間:2020-10-21 15:07:43 來源:腳本之家 閱讀:161 作者:codog_main 欄目:移動(dòng)開發(fā)

前言

本文將通過代碼講解下拉阻尼效果的實(shí)現(xiàn)原理。

實(shí)現(xiàn)靈感來源于這篇博客,但是這篇博客的代碼并不能讓我滿意,或者說是糟糕的,不過還是非常感謝作者帶給我的啟發(fā)。

現(xiàn)在大部分資訊類安卓APP都有一個(gè)下拉刷新的功能,又如微信聯(lián)系人列表頂部的小程序入口,也使用了這種下拉阻尼的效果。

我的代碼主要是解釋其實(shí)現(xiàn)原理,為方便讀者理解,所以代碼邏輯非常簡單,但如果想要實(shí)現(xiàn)例如下拉刷新轉(zhuǎn)動(dòng)的進(jìn)度圈,還需要修改代碼中的MoveHeaderTask類中的onProgressUpdate方法;如果要實(shí)現(xiàn)滑動(dòng)列表頂部加入這種下拉阻尼效果,則需要修改代碼中的onTouch方法,通過判斷是否到達(dá)列表頂部來決定是否觸發(fā)下拉阻尼效果的邏輯代碼。

最新的微信版本還實(shí)現(xiàn)了一個(gè)具有慣性的滑動(dòng)列表(不清楚這樣表述是否正確),滑動(dòng)的速度大小和小程序入口的下拉阻尼效果會(huì)形成互動(dòng),但這已不是本文討論的重點(diǎn),這需要感興趣的讀者自行對(duì)我的代碼進(jìn)行迭代。

運(yùn)行效果如下:

Android下拉阻尼效果實(shí)現(xiàn)原理及簡單實(shí)例

如圖,拉動(dòng)"可見主體"到達(dá)一定高度,"隱藏頭部"就會(huì)彈出,反之,向上滑動(dòng)到一定高度,"隱藏頭部"則會(huì)收回,如果未到達(dá)指定高度,則恢復(fù)原狀。

實(shí)際運(yùn)行效果其實(shí)很流暢,也不會(huì)出現(xiàn)上圖中,頭部無法完全隱藏的情況,只是AS自帶的錄屏工具比較差勁。我不建議把這個(gè)自定義控件用在對(duì)話框類型的activity上,因?yàn)榍耙粋€(gè)activity處于可見狀態(tài),可能會(huì)占用大量算力,導(dǎo)致動(dòng)畫效果不流暢,親測。

原理

這種效果是通過自定義控件的方式來實(shí)現(xiàn)的,我自定義了一個(gè)控件類型,這個(gè)自定義控件(PullDownDumperLayout)繼承自線性布局(LinearLayout) 。

用戶可以下拉彈出的那個(gè)視圖,例如微信的小程序列表,開發(fā)者只是將這個(gè)視圖移出了父元素之外,所以不可見,我們暫且稱之為隱藏頭部,只有下拉到一定程度才會(huì)彈出,而主體,例如微信的聯(lián)系人列表,則是可見的,布局見下圖。

Android下拉阻尼效果實(shí)現(xiàn)原理及簡單實(shí)例

實(shí)現(xiàn)這個(gè)效果需要我們做三件工作:

1.隱藏作為頭部的控件
2.監(jiān)聽用戶對(duì)屏幕的操作事件
3.實(shí)現(xiàn)下拉回彈的動(dòng)畫效果

我們這個(gè)自定義控件會(huì)自動(dòng)獲取內(nèi)部第一個(gè)子元素充當(dāng)頭部,其余的元素則是充當(dāng)可見的主體(詳見代碼中的注釋)。

基本的布局原理差不多就這樣了,但是我們還需要讓自定義控件監(jiān)聽用戶的手勢操作,例如上下滑動(dòng)等。這里我和靈感來源的那篇博客一樣,讓自定義控件實(shí)現(xiàn)View.OnTouchListener接口,實(shí)現(xiàn)內(nèi)部的onTouch方法可以監(jiān)聽來自屏幕的所有觸摸操作。代碼中我讓頭部和第二個(gè)子元素(可見的主體)注冊(cè)了這個(gè)監(jiān)聽器,這是為了方便讀者理解,讀者可根據(jù)自己的需求進(jìn)行修改。

注意,對(duì)于不能監(jiān)聽屏幕觸摸事件的控件需要添加:

android:clickable="true"

至此,我們已經(jīng)可以進(jìn)行布局和監(jiān)聽用戶手勢了,但是還需要實(shí)現(xiàn)一個(gè)頭部展開和隱藏的動(dòng)畫效果。當(dāng)用戶將隱藏頭部下拉或上滑到一定高度時(shí),這個(gè)效果就會(huì)被觸發(fā),這需要依賴上面所述的onTouch方法。動(dòng)畫效果的實(shí)現(xiàn)需要另開一個(gè)線程進(jìn)行操作,線程的啟動(dòng)方式我們可以采用繼承AsyncTask類來實(shí)現(xiàn)。

除此之外,我們可能會(huì)多次復(fù)用這個(gè)控件,所以在自定義控件類的最后還需要一些調(diào)整參數(shù)的set方法。

這里提個(gè)醒,在接下來的代碼中,我們的自定義控件因?yàn)槔^承自LinearLayout,里面需要重寫onLayout方法,而onLayout方法顧名思義就是布局,這個(gè)方法在Activity中的onCreate方法執(zhí)行之后才會(huì)被調(diào)用,所以我們可以在ActivityonCreate方法中利用findViewById獲取實(shí)例,調(diào)用上面提到的set方法進(jìn)行參數(shù)的初始化。

LinearLayout中不止onLayout一個(gè)方法,詳細(xì)解析請(qǐng)讀者移步其他關(guān)于XML標(biāo)簽加載過程的文章,這里不做贅述。

代碼

PullDownDumperLayout .java:

public class PullDownDumperLayout extends LinearLayout implements View.OnTouchListener {

  /**
   * 取布局中的第一個(gè)子元素為下拉隱藏頭部
   */
  private View mHeadLayout;

  /**
   * 隱藏頭部布局的高的負(fù)值
   */
  private int mHeadLayoutHeight;

  /**
   * 隱藏頭部的布局參數(shù)
   */
  private MarginLayoutParams mHeadLayoutParams;

  /**
   * 判斷是否為第一次初始化,第一次初始化需要把headView移出界面外
   */
  private boolean mOnLayoutIsInit=false;

  /**
   * 移動(dòng)時(shí),前一個(gè)坐標(biāo)
   */
  private float mMoveY;

  /**
   * 如果為false,會(huì)退出頭部展開或隱藏動(dòng)畫
   */
  private boolean mChangeHeadLayoutTopMargin;

  /**
   * 觸發(fā)動(dòng)畫的分界線,由mRatio計(jì)算得到
   */
  private int mBoundary;

  /**
   * 頭部布局的隱藏和展開速度,以及單次執(zhí)行時(shí)間
   */
  private int mHeadLayoutHideSpeed;
  private int mHeadLayoutUnfoldSpeed;
  private long mSleepTime;

  /**
   * 觸發(fā)動(dòng)畫的分界線,頭部布局上半部分和整體高度的比例
   */
  private double mRatio;

  public PullDownDumperLayout(Context context, AttributeSet attrs) {
    super(context, attrs);
    //初始化參數(shù),根據(jù)自己的需求調(diào)整
    mHeadLayoutHideSpeed=-20;
    mHeadLayoutUnfoldSpeed=20;
    mSleepTime=10;
    mRatio=0.5;
  }

  /**
   * 布局開始設(shè)置每一個(gè)控件
   * 在activity的onCreate執(zhí)行之后才會(huì)執(zhí)行
   * 因此可以在onCreate中調(diào)用set方法設(shè)置參數(shù)
   */
  @Override
  protected void onLayout(boolean changed, int l, int t, int r, int b) {
    super.onLayout(changed, l, t, r, b);
    if(!mOnLayoutIsInit && changed) {
      //將第一個(gè)子元素作為頭部移出界面外
      mHeadLayout = this.getChildAt(0);
      mHeadLayoutHeight=-mHeadLayout.getHeight();
      mBoundary=(int)(mRatio*mHeadLayoutHeight);//計(jì)算觸發(fā)動(dòng)畫分界線
      mHeadLayoutParams=(MarginLayoutParams) mHeadLayout.getLayoutParams();
      mHeadLayoutParams.topMargin=mHeadLayoutHeight;
      mHeadLayout.setLayoutParams(mHeadLayoutParams);
      //TODO 設(shè)置手勢監(jiān)聽器,不能觸碰的控件需要添加android:clickable="true"
      getChildAt(1).setOnTouchListener(this);
      mHeadLayout.setOnTouchListener(this);
      //標(biāo)記已被初始化
      mOnLayoutIsInit=true;
    }
  }

  /**
   * 屏幕觸摸操作監(jiān)聽器
   * @return false則注冊(cè)本監(jiān)聽器的控件將不會(huì)對(duì)事件做出響應(yīng),true則相反
   */
  @Override
  public boolean onTouch(View v, MotionEvent event) {
    switch (event.getAction()) {
      case MotionEvent.ACTION_DOWN:
        mMoveY=event.getRawY();//捕獲按下時(shí)的坐標(biāo),初始化mMoveY
        mChangeHeadLayoutTopMargin=false;
        break;
      case MotionEvent.ACTION_MOVE:
        float currY=event.getRawY();
        int vector=(int)(currY-mMoveY);//向量,用于判斷手勢的上滑和下滑
        mMoveY=currY;
        //判斷是否為滑動(dòng)
        if(Math.abs(vector)==0){
          return false;
        }
        //頭部完全隱藏時(shí)不再向上滑動(dòng)
        if (vector < 0 && mHeadLayoutParams.topMargin <= mHeadLayoutHeight) {
          return false;
        }
        //頭部完全展開時(shí)不再向下滑動(dòng)
        if (vector > 0 && mHeadLayoutParams.topMargin >= 0) {
          return false;
        }

        //對(duì)增量進(jìn)行修正,對(duì)滑動(dòng)距離進(jìn)行減半
        int topMargin = mHeadLayoutParams.topMargin + (vector/2);//阻尼值
        if(topMargin>0){
          // 瞬間拉動(dòng)的距離超過了頭部高度,因?yàn)檫@一瞬間很短,這里采用直接賦值的方式
          // 如需平滑過渡,要另開線程,并且監(jiān)聽到ACTION_DOWN時(shí)線程可被打斷
          topMargin = 0;
        }
        else if(topMargin<mHeadLayoutHeight){
          // 瞬間拉動(dòng)的距離超過了頭部高度,因?yàn)檫@一瞬間很短,這里采用直接賦值的方式
          // 如需平滑過渡,要另開線程,并且監(jiān)聽ACTION_DOWN時(shí)線程可被打斷
          topMargin = mHeadLayoutHeight;
        }
        //用戶對(duì)屏幕的滑動(dòng)將會(huì)改變控件的TopMargin
        mHeadLayoutParams.topMargin = topMargin ;
        mHeadLayout.setLayoutParams(mHeadLayoutParams);
        break;
      default:
        //TODO 出現(xiàn)其他觸碰事件,如MotionEvent.ACTION_UP時(shí),根據(jù)閾值判斷此時(shí)頭部應(yīng)該彈出還是隱藏
        mChangeHeadLayoutTopMargin=true;
        if(mHeadLayoutParams.topMargin<=mBoundary){
          //隱藏
          new MoveHeaderTask().execute(true);
        }
        else{
          //展開
          new MoveHeaderTask().execute(false);
        }
        break;
    }
    return false;
  }

  /**
   * 新線程,隱藏或者展開頭部布局,線程可被ACTION_DOWN打斷
   */
  class MoveHeaderTask extends AsyncTask<Boolean, Integer, Integer> {


    /**
     *
     * @param opt true為隱藏動(dòng)畫,false為展開動(dòng)畫
     * @return
     */
    @Override
    protected Integer doInBackground(Boolean... opt) {
      int topMargin=mHeadLayoutParams.topMargin;
      //true為隱藏,false為展開
      int speed=(opt[0])?mHeadLayoutHideSpeed:mHeadLayoutUnfoldSpeed;
      while(mChangeHeadLayoutTopMargin){
        topMargin += speed;
        if (topMargin <= mHeadLayoutHeight||topMargin>=0) {
          topMargin=(opt[0])?mHeadLayoutHeight:0;
          publishProgress(topMargin);
          break;
        }
        publishProgress(topMargin);
        sleep(mSleepTime);
      }
      return null;
    }

    //調(diào)用publishProgress后會(huì)執(zhí)行
    @Override
    protected void onProgressUpdate(Integer... topMargin) {
      mHeadLayoutParams.topMargin=topMargin[0];
      mHeadLayout.setLayoutParams(mHeadLayoutParams);
    }

  }

  //調(diào)整參數(shù)
  public void setHeadLayoutHideSpeed(int speed){
    this.mHeadLayoutHideSpeed=speed;
  }
  public void setHeadLayoutUnfoldSpeed(int speed){
    this.mHeadLayoutUnfoldSpeed=speed;
  }
  public void setSleepTime(long time){
    this.mSleepTime=time;
  }
  public void setRatio(double ratio){
    this.mRatio=ratio;
  }
}

activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  tools:context=".MainActivity">

  <com.example.pulldowndumpertest.PullDownDumperLayout
    android:tag="記得將這個(gè)標(biāo)簽修改為自己的包名"
    android:id="@+id/PullDownDumper"
    android:layout_width="900px"
    android:layout_height="1920px"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    android:background="@null"
    android:orientation="vertical">
    <LinearLayout
      android:layout_width="match_parent"
      android:layout_height="500px"
      android:orientation="vertical"
      android:background="@color/colorPrimary"
      android:clickable="true">
      <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="隱藏頭部"
        android:textSize="100px"
        android:gravity="center"
        android:textColor="#FFFFFF"
        android:background="@null"/>
    </LinearLayout>
    <LinearLayout
      android:layout_width="match_parent"
      android:layout_height="1700px"
      android:background="@color/colorPrimaryDark"
      android:clickable="true">
      <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="可見主體"
        android:textSize="100px"
        android:gravity="center"
        android:textColor="#FFFFFF"
        android:background="@null"/>
    </LinearLayout>
  </com.example.pulldowndumpertest.PullDownDumperLayout>

</android.support.constraint.ConstraintLayout>

MainActivity.java:

public class MainActivity extends AppCompatActivity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    //TODO 讀者可在這里初始化參數(shù)
    PullDownDumperLayout pddl=findViewById(R.id.PullDownDumper);

  }
}

下面是筆者正在使用的自定義控件,比上述的控件多了一個(gè)效果:

頭部處于隱藏或展開的不同狀態(tài)時(shí),觸發(fā)動(dòng)畫效果的分界線可以隨狀態(tài)不同而改變。

還是拿最新版的微信小程序入口來講,用戶在下拉時(shí),小程序界面會(huì)占用整個(gè)屏幕,如果觸發(fā)動(dòng)畫的分界線太低,這樣導(dǎo)致的結(jié)果是用戶可能無法通過上滑重新返回聯(lián)系人列表,但由于微信沒有對(duì)滑動(dòng)距離進(jìn)行減半處理,所以不存在上述問題,可能是出于防止誤觸的原因,從小程序界面返回聯(lián)系人列表的方式改用點(diǎn)擊底部的一個(gè)按鈕。而我的控件可以通過改變觸發(fā)動(dòng)畫效果的分界線來解決這一問題,感興趣的讀者可以研究一下。

public class PullDownDumperLayout extends LinearLayout implements View.OnTouchListener {

  /**
   * 取布局中的第一個(gè)子元素為下拉隱藏頭部
   */
  private View mHeadLayout;

  /**
   * 隱藏頭部布局的高的負(fù)值
   */
  private int mHeadLayoutHeight;

  /**
   * 隱藏頭部的布局參數(shù)
   */
  private MarginLayoutParams mHeadLayoutParams;

  /**
   * 判斷是否為第一次初始化,第一次初始化需要把headView移出界面外
   */
  private boolean mOnLayoutIsInit=false;

  /**
   * 從配置獲取的滾動(dòng)判斷閾值,為兩點(diǎn)間的距離,超過此閾值判斷為滾動(dòng)
   */
//  private int mScaledTouchSlop;

  /**
   * 按下時(shí)的y軸坐標(biāo)
   */
//  private float mDownY;

  /**
   * 移動(dòng)時(shí),前一個(gè)坐標(biāo)
   */
  private float mMoveY;

  /**
   * 如果為false,會(huì)退出頭部展開或隱藏動(dòng)畫
   */
  private boolean mChangeHeadLayoutTopMargin;

  /**
   * 頭部布局的隱藏和展開速度,以及單次執(zhí)行時(shí)間
   */
  private int mHeadLayoutHideSpeed;
  private int mHeadLayoutUnfoldSpeed;
  private long mSleepTime;

  /**
   * 初始化頭部布局的偏移值,數(shù)值越大,頭部可見部分越多,預(yù)設(shè)值為0,即初始時(shí)頭部完全不可見
   */
  private int mTopMarginOffset;

  /**
   * 觸發(fā)動(dòng)畫的分界線,頭部布局上半部分和整體高度的比例
   */
  private double mUnfoldRatio;
  private double mHideRatio;

  /**
   * 觸發(fā)動(dòng)畫的分界線,初始值由mRatio計(jì)算得到
   * 頭部處于隱藏時(shí)等于mUnfoldBoundary
   * 頭部處于展開時(shí)等于mHideBoundary
   * mBoundary在onTouch的ACTION_DOWN中變化
   */
  private int mBoundary;
  private int mUnfoldBoundary;
  private int mHideBoundary;

  /**
   * 阻尼值,越大越難拖動(dòng),呈線性趨勢
   */
  private int mDumper;

  public PullDownDumperLayout(Context context, AttributeSet attrs) {
    super(context, attrs);
//    mScaledTouchSlop= ViewConfiguration.get(context).getScaledTouchSlop();
    mHeadLayoutHideSpeed=-30;
    mHeadLayoutUnfoldSpeed=30;
    mSleepTime=10;
    mUnfoldRatio=0.6;
    mHideRatio=mUnfoldRatio;
    mDumper=2;
    mTopMarginOffset=-200;
  }

  /**
   * 布局開始設(shè)置每一個(gè)控件
   * 在activity的onCreate執(zhí)行之后才會(huì)執(zhí)行
   * 因此可以在onCreate中調(diào)用set方法設(shè)置參數(shù)
   */
  @Override
  protected void onLayout(boolean changed, int l, int t, int r, int b) {
    super.onLayout(changed, l, t, r, b);
    //只初始化一次
    if(!mOnLayoutIsInit && changed) {
      //將第一個(gè)子元素作為頭部移出界面外
      mHeadLayout = this.getChildAt(0);
      mHeadLayoutHeight=-mHeadLayout.getHeight();
      mUnfoldBoundary=(int)(mUnfoldRatio*mHeadLayoutHeight);//計(jì)算觸發(fā)展開動(dòng)畫分界線
      mHideBoundary=(int)(mHideRatio*mHeadLayoutHeight);//計(jì)算觸發(fā)隱藏動(dòng)畫分界線
      mBoundary=mUnfoldBoundary;//觸發(fā)動(dòng)畫的分界線初始為mUnfoldBoundary
      mHeadLayoutHeight-=mTopMarginOffset;//頭部隱藏布局可見的部分
      mHeadLayoutParams=(MarginLayoutParams) mHeadLayout.getLayoutParams();
      mHeadLayoutParams.topMargin=mHeadLayoutHeight;
      mHeadLayout.setLayoutParams(mHeadLayoutParams);
      //TODO 設(shè)置手勢監(jiān)聽器,不能觸碰的控件需要添加android:clickable="true"
      getChildAt(1).setOnTouchListener(this);
      mHeadLayout.setOnTouchListener(this);
      //標(biāo)記已被初始化
      mOnLayoutIsInit=true;
    }
  }

  /**
   * 屏幕觸摸操作監(jiān)聽器
   * @return false: 注冊(cè)本監(jiān)聽器的控件將不會(huì)對(duì)事件做出響應(yīng),true則相反
   */
  @Override
  public boolean onTouch(View v, MotionEvent event) {
    switch (event.getAction()) {
      case MotionEvent.ACTION_DOWN:
        //根據(jù)此時(shí)處于完全展開或完全隱藏決定mBoundary的值,如果兩種情況都不滿足則不做改變
        if(mHeadLayoutParams.topMargin==mHeadLayoutHeight)
          mBoundary=mUnfoldBoundary;
        else if(mHeadLayoutParams.topMargin==0)
          mBoundary=mHideBoundary;

//        mDownY=event.getRawY();//獲取按下的屏幕y坐標(biāo)
        mMoveY=event.getRawY();
        mChangeHeadLayoutTopMargin=false;//false會(huì)打斷隱藏或展開頭部布局的動(dòng)畫
        break;
      case MotionEvent.ACTION_MOVE:
        float currY=event.getRawY();
        int vector=(int)(currY-mMoveY);//向量,用于判斷手勢的上滑和下滑
        mMoveY=currY;
        //判斷是否為滑動(dòng)
        if(Math.abs(vector)==0){
          return false;
        }
        //頭部完全隱藏時(shí)不再向上滑動(dòng)
        if (vector < 0 && mHeadLayoutParams.topMargin <= mHeadLayoutHeight) {
          return false;
        }
        //頭部完全展開時(shí)不再向下滑動(dòng)
        else if (vector > 0 && mHeadLayoutParams.topMargin >= 0) {
          return false;
        }

        //對(duì)增量進(jìn)行修正
        int topMargin = mHeadLayoutParams.topMargin + (vector/mDumper);
        if(topMargin>0){
          // 瞬間拉動(dòng)的距離超過了頭部高度,因?yàn)檫@一瞬間很短,這里采用直接賦值的方式
          // 如需實(shí)現(xiàn)平滑過渡,要另開線程,并且監(jiān)聽到ACTION_DOWN時(shí)線程可被打斷
          topMargin = 0;
        }
        else if(topMargin<mHeadLayoutHeight){
          // 瞬間拉動(dòng)的距離超過了頭部高度,因?yàn)檫@一瞬間很短,這里采用直接賦值的方式
          // 如需實(shí)現(xiàn)平滑過渡,要另開線程,并且監(jiān)聽ACTION_DOWN時(shí)線程可被打斷
          topMargin = mHeadLayoutHeight;
        }

        //使參數(shù)生效
        mHeadLayoutParams.topMargin = topMargin ;
        mHeadLayout.setLayoutParams(mHeadLayoutParams);
        break;
      default:
        //出現(xiàn)其他觸碰事件,如MotionEvent.ACTION_UP時(shí),根據(jù)閾值mBoundary判斷此時(shí)頭部應(yīng)該彈出還是隱藏
        mChangeHeadLayoutTopMargin=true;//允許執(zhí)行動(dòng)畫
        if(mHeadLayoutParams.topMargin<=mBoundary){
          //隱藏
          new MoveHeaderTask().execute(true);
        }
        else{
          //展開
          new MoveHeaderTask().execute(false);
        }
        break;
    }
    return false;
  }

  /**
   * 新線程,隱藏或者展開頭部布局,線程可被ACTION_DOWN打斷
   */
  private class MoveHeaderTask extends AsyncTask<Boolean, Integer, Integer> {

    /**
     *
     * @param opt true為隱藏動(dòng)畫,false為展開動(dòng)畫
     * @return
     */
    @Override
    protected Integer doInBackground(Boolean... opt) {
      int topMargin=mHeadLayoutParams.topMargin;
      //true為隱藏,false為展開
      int speed=(opt[0])?mHeadLayoutHideSpeed:mHeadLayoutUnfoldSpeed;
      while(mChangeHeadLayoutTopMargin){
        topMargin += speed;
        if (topMargin <= mHeadLayoutHeight||topMargin>=0) {
          topMargin=(opt[0])?mHeadLayoutHeight:0;
          publishProgress(topMargin);
          break;
        }
        publishProgress(topMargin);
        sleep(mSleepTime);
      }
      return null;
    }

    //調(diào)用publishProgress后會(huì)執(zhí)行
    @Override
    protected void onProgressUpdate(Integer... topMargin) {
      mHeadLayoutParams.topMargin=topMargin[0];
      mHeadLayout.setLayoutParams(mHeadLayoutParams);
    }

  }

  //調(diào)整參數(shù)
  public void setHeadLayoutHideSpeed(int speed){
    this.mHeadLayoutHideSpeed=speed;
  }
  public void setHeadLayoutUnfoldSpeed(int speed){
    this.mHeadLayoutUnfoldSpeed=speed;
  }
  public void setSleepTime(long time){
    this.mSleepTime=time;
  }
  public void setDumper(int dumper){
    this.mDumper=dumper;
  }
  public void setTopMarginOffset(int offset){
    this.mTopMarginOffset=-offset;
  }

  /**
   * 頭部處于隱藏狀態(tài)時(shí),觸發(fā)展開動(dòng)畫的分界線
   * @param ratio 頭部布局上部分與下部分的分界線
   */
  public void setUnfoldRatio(double ratio){
    this.mUnfoldRatio=ratio;
  }

  /**
   * 頭部處于展開狀態(tài)時(shí),觸發(fā)隱藏動(dòng)畫的分界線
   * @param ratio 頭部布局上部分與下部分的分界線
   */
  public void setHideRatio(double ratio){
    this.mHideRatio=ratio;
  }
}```

以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持億速云。

向AI問一下細(xì)節(jié)

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

AI