最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

android仿音悅臺頁面交互效果實(shí)例代碼

 更新時(shí)間:2017年01月06日 11:13:40   作者:code_xzh  
這篇文章主要介紹了android 仿音悅臺頁面播放效果實(shí)例代碼,新版的音悅臺 APP 播放頁面交互非常有意思,可以把播放器往下拖動,然后在底部懸浮一個小框,還可以左右拖動。

概述

新版的音悅臺 APP 播放頁面交互非常有意思,可以把播放器往下拖動,然后在底部懸浮一個小框,還可以左右拖動,然后回彈的時(shí)候也會有相應(yīng)的效果,這種交互效果在頭條視頻和一些專注于視頻的app也是很常見的。

前幾天看網(wǎng)友有仿這個 效果,覺得不錯,現(xiàn)在分享出來,代碼可以再優(yōu)化,這里的播放器使用的是B站的ijkplayer,先上兩張動圖。


當(dāng)圖片到達(dá)底部后,左右拖動

實(shí)現(xiàn)的思路

首先,要是拖動視圖縮小的效果,我們肯定需要自定義一個View,而根據(jù)我們項(xiàng)目的場景我們這里需要兩個View,一個是拖動的View,另一個是浮動上下的View(可以縮小的View),為了實(shí)現(xiàn)拖動,我們知道必定會用到ViewDragHelper這個類,這個類專門為了拖動而設(shè)計(jì)的。

然后,對于拖動到底部的View,我們需要實(shí)現(xiàn)左右拖動的效果,這個其實(shí)也是比較容易實(shí)現(xiàn)的,我們通過ViewDragHelper的onViewPositionChanged方法來判斷當(dāng)前視圖的狀況,就可以做View進(jìn)行縮放和漸變了。

代碼分析

首先我們會自定義一個容器,容器的init方法會初始化兩個View:mFlexView (到底拖動的View)和mFollowView (跟隨觸摸縮放的View)

 private void init(Context context, AttributeSet attrs) {

    final float density = getResources().getDisplayMetrics().density;
    final float minVel = MIN_FLING_VELOCITY * density;

    ViewGroupCompat.setMotionEventSplittingEnabled(this, false);
    FlexCallback flexCallback = new FlexCallback();
    mDragHelper = ViewDragHelper.create(this, 1.0f, flexCallback);
    // 最小拖動速度
    mDragHelper.setMinVelocity(minVel);

    post(new Runnable() {
      @Override
      public void run() {

        // 需要添加的兩個子View,其中mFlexView作為拖動的響應(yīng)View,mLinkView作為跟隨View
        mFlexView = getChildAt(0);
        mFollowView = getChildAt(1);

        mDragHeight = getMeasuredHeight() - mFlexView.getMeasuredHeight();

        mFlexWidth = mFlexView.getMeasuredWidth();
        mFlexHeight = mFlexView.getMeasuredHeight();

      }
    });

  }

ViewDragHelper 的回調(diào)需要做的事情比較多,在 mFlexView 拖動的時(shí)候需要同時(shí)設(shè)置 mFlexView 和 mFollowView 的相應(yīng)變化效果,在 mFlexView 釋放的時(shí)候需要處理關(guān)閉或收起等效果。所以這里我們需要對ViewDragHelper個各種回調(diào)事件進(jìn)行監(jiān)聽。這也是本功能最核心的:

 private class FlexCallback extends ViewDragHelper.Callback {

    @Override
    public boolean tryCaptureView(View child, int pointerId) {
      // mFlexView來響應(yīng)觸摸事件
      return mFlexView == child;
    }

    @Override
    public int clampViewPositionHorizontal(View child, int left, int dx) {
      return Math.max(Math.min(mDragWidth, left), -mDragWidth);
    }

    @Override
    public int getViewHorizontalDragRange(View child) {
      return mDragWidth * 2;
    }

    @Override
    public int clampViewPositionVertical(View child, int top, int dy) {
      if (!mVerticalDragEnable) {
        // 不允許垂直拖動的時(shí)候是mFlexView在底部水平拖動一定距離時(shí)設(shè)置的,返回mDragHeight就不能再垂直做拖動了
        return mDragHeight;
      }
      return Math.max(Math.min(mDragHeight, top), 0);
    }

    @Override
    public int getViewVerticalDragRange(View child) {
      return mDragHeight;
    }

    @Override
    public void onViewReleased(View releasedChild, float xvel, float yvel) {

      if (mHorizontalDragEnable) {
        // 如果水平拖動有效,首先根據(jù)拖動的速度決定關(guān)閉頁面,方向根據(jù)速度正負(fù)決定
        if (xvel > 1500) {
          mDragHelper.settleCapturedViewAt(mDragWidth, mDragHeight);
          mIsClosing = true;
        } else if (xvel < -1500) {
          mDragHelper.settleCapturedViewAt(-mDragWidth, mDragHeight);
          mIsClosing = true;
        } else {
          // 速度沒到關(guān)閉頁面的要求,根據(jù)透明度來決定關(guān)閉頁面,方向根據(jù)releasedChild.getLeft()正負(fù)決定
          float alpha = releasedChild.getAlpha();
          if (releasedChild.getLeft() < 0 && alpha <= 0.4f) {
            mDragHelper.settleCapturedViewAt(-mDragWidth, mDragHeight);
            mIsClosing = true;
          } else if (releasedChild.getLeft() > 0 && alpha <= 0.4f) {
            mDragHelper.settleCapturedViewAt(mDragWidth, mDragHeight);
            mIsClosing = true;
          } else {
            mDragHelper.settleCapturedViewAt(0, mDragHeight);
          }
        }
      } else {
        // 根據(jù)垂直方向的速度正負(fù)決定布局的展示方式
        if (yvel > 1500) {
          mDragHelper.settleCapturedViewAt(0, mDragHeight);
        } else if (yvel < -1500) {
          mDragHelper.settleCapturedViewAt(0, 0);
        } else {
          // 根據(jù)releasedChild.getTop()決定布局的展示方式
          if (releasedChild.getTop() <= mDragHeight / 2) {
            mDragHelper.settleCapturedViewAt(0, 0);
          } else {
            mDragHelper.settleCapturedViewAt(0, mDragHeight);
          }
        }
      }
      invalidate();
    }

    @Override
    public void onViewPositionChanged(final View changedView, int left, int top, int dx, int dy) {

      float fraction = top * 1.0f / mDragHeight;

      // mFlexView縮放的比率
      mFlexScaleRatio = 1 - 0.5f * fraction;
      mFlexScaleOffset = changedView.getWidth() / 20;
      // 設(shè)置縮放基點(diǎn)
      changedView.setPivotX(changedView.getWidth() - mFlexScaleOffset);
      changedView.setPivotY(changedView.getHeight() - mFlexScaleOffset);
      // 設(shè)置比例
      changedView.setScaleX(mFlexScaleRatio);
      changedView.setScaleY(mFlexScaleRatio);

      // mFollowView透明度的比率
      float alphaRatio = 1 - fraction;
      // 設(shè)置透明度
      mFollowView.setAlpha(alphaRatio);
      // 根據(jù)垂直方向的dy設(shè)置top,產(chǎn)生跟隨mFlexView的效果
      mFollowView.setTop(mFollowView.getTop() + dy);

      // 到底部的時(shí)候,changedView的top剛好等于mDragHeight,以此作為水平拖動的基準(zhǔn)
      mHorizontalDragEnable = top == mDragHeight;

      if (mHorizontalDragEnable) {
        // 如果水平拖動允許的話,由于設(shè)置縮放不會影響mFlexView的寬高(比如getWidth),所以水平拖動距離為mFlexView寬度一半
        mDragWidth = (int) (changedView.getMeasuredWidth() * 0.5f);

        // 設(shè)置mFlexView的透明度,這里向左右水平拖動透明度都隨之變化
        changedView.setAlpha(1 - Math.abs(left) * 1.0f / mDragWidth);

        // 水平拖動一定距離的話,垂直拖動將被禁止
        mVerticalDragEnable = left < 0 && left >= -mDragWidth * 0.05;

      } else {
        // 不是水平拖動的處理
        changedView.setAlpha(1);
        mDragWidth = 0;

        mVerticalDragEnable = true;

      }

      if (mFlexLayoutPosition == null) {
        // 創(chuàng)建子元素位置緩存
        mFlexLayoutPosition = new ChildLayoutPosition();
        mFollowLayoutPosition = new ChildLayoutPosition();
      }

      // 記錄子元素的位置
      mFlexLayoutPosition.setPosition(mFlexView.getLeft(), mFlexView.getRight(), mFlexView.getTop(), mFlexView.getBottom());
      mFollowLayoutPosition.setPosition(mFollowView.getLeft(), mFollowView.getRight(), mFollowView.getTop(), mFollowView.getBottom());

      //      Log.e("FlexCallback", "225行-onViewPositionChanged(): 【" + mFlexView.getLeft() + ":" + mFlexView.getRight() + ":" + mFlexView.getTop() + ":" + mFlexView
      //          .getBottom() + "】 【" + mFollowView.getLeft() + ":" + mFollowView.getRight() + ":" + mFollowView.getTop() + ":" + mFollowView.getBottom() + "】");

    }

  }

接下來是處理測量和定位,我們實(shí)現(xiàn)的排列效果類似 LinearLayout 垂直排列的效果,這里需要對 measureChildWithMargins 的 heightUse 重新設(shè)置;onLayout 的時(shí)候在位置緩存不為空的時(shí)候直接定位是因?yàn)?ViewDragHelper 在處理觸摸事件子元素在做一些平移之類的,若是有元素更新了 UI 會導(dǎo)致重新 Layout,因此在 FlexCallback 的 onViewPositionChanged 方法記錄位置,然后在回彈的時(shí)候需要通過Layout 恢復(fù)之前的視圖。

@Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

    int desireHeight = 0;
    int desireWidth = 0;

    int tmpHeight = 0;

    if (getChildCount() != 2) {
      throw new IllegalArgumentException("只允許容器添加兩個子View!");
    }

    if (getChildCount() > 0) {
      for (int i = 0; i < getChildCount(); i++) {
        final View child = getChildAt(i);
        // 測量子元素并考慮外邊距
        // 參數(shù)heightUse:父容器豎直已經(jīng)被占用的空間,比如被父容器的其他子 view 所占用的空間;這里我們需要的是子View垂直排列,所以需要設(shè)置這個值
        measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, tmpHeight);
        // 獲取子元素的布局參數(shù)
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
        // 計(jì)算子元素寬度,取子控件最大寬度
        desireWidth = Math.max(desireWidth, child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
        // 計(jì)算子元素高度
        tmpHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
        desireHeight += tmpHeight;
      }
      // 考慮父容器內(nèi)邊距
      desireWidth += getPaddingLeft() + getPaddingRight();
      desireHeight += getPaddingTop() + getPaddingBottom();
      // 嘗試比較建議最小值和期望值的大小并取大值
      desireWidth = Math.max(desireWidth, getSuggestedMinimumWidth());
      desireHeight = Math.max(desireHeight, getSuggestedMinimumHeight());
    }
    // 設(shè)置最終測量值
    setMeasuredDimension(resolveSize(desireWidth, widthMeasureSpec), resolveSize(desireHeight, heightMeasureSpec));
  }

  @Override
  protected void onLayout(boolean changed, int l, int t, int r, int b) {

    if (mFlexLayoutPosition != null) {
      // 因?yàn)樵谟玫絍iewDragHelper處理布局交互的時(shí)候,若是有子View的UI更新導(dǎo)致重新Layout的話,需要我們自己處理ViewDragHelper拖動時(shí)子View的位置,否則會導(dǎo)致位置錯誤
      // Log.e("YytLayout1", "292行-onLayout(): " + "自己處理布局位置");
      mFlexView.layout(mFlexLayoutPosition.getLeft(), mFlexLayoutPosition.getTop(), mFlexLayoutPosition.getRight(), mFlexLayoutPosition.getBottom());
      mFollowView.layout(mFollowLayoutPosition.getLeft(), mFollowLayoutPosition.getTop(), mFollowLayoutPosition.getRight(), mFollowLayoutPosition.getBottom());
      return;
    }

    final int paddingLeft = getPaddingLeft();
    final int paddingTop = getPaddingTop();

    int multiHeight = 0;

    int count = getChildCount();

    if (count != 2) {
      throw new IllegalArgumentException("此容器的子元素個數(shù)必須為2!");
    }

    for (int i = 0; i < count; i++) {
      // 遍歷子元素并對其進(jìn)行定位布局
      final View child = getChildAt(i);
      MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

      int left = paddingLeft + lp.leftMargin;
      int right = child.getMeasuredWidth() + left;

      int top = (i == 0 ? paddingTop : 0) + lp.topMargin + multiHeight;
      int bottom = child.getMeasuredHeight() + top;

      child.layout(left, top, right, bottom);

      multiHeight += (child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
    }

  }

觸摸事件的處理,由于縮放不會影響 mFlexView 真實(shí)寬高,ViewDragHelper 仍然會阻斷 mFlexView 的真實(shí)寬高的區(qū)域,所以這里判斷手指是否落在 mFlexView 視覺上的范圍內(nèi),在才去調(diào) ViewDragHelper 的 shouldInterceptTouchEvent 方法。

 @Override
  public boolean onInterceptTouchEvent(MotionEvent ev) {

    // Log.e("YytLayout", mFlexView.getLeft() + ";" + mFlexView.getTop() + " --- " + ev.getX() + ":" + ev.getY());

    // 由于縮放不會影響mFlexView真實(shí)寬高,這里手動計(jì)算視覺上的范圍
    float left = mFlexView.getLeft() + mFlexWidth * (1 - mFlexScaleRatio) - mFlexScaleOffset * (1 - mFlexScaleRatio);
    float top = mFlexView.getTop() + mFlexHeight * (1 - mFlexScaleRatio) - mFlexScaleOffset * (1 - mFlexScaleRatio);

    // 這里所做的是判斷手指是否落在mFlexView視覺上的范圍內(nèi)
    mInFlexViewTouchRange = ev.getX() >= left && ev.getY() >= top;

    if (mInFlexViewTouchRange) {

      return mDragHelper.shouldInterceptTouchEvent(ev);

    } else {
      return super.onInterceptTouchEvent(ev);
    }
  }

  @Override
  public boolean onTouchEvent(MotionEvent event) {
    if (mInFlexViewTouchRange) {
      // 這里還要做判斷是因?yàn)?,即使我不阻斷事件,但是此Layout的子View不消費(fèi)的話,事件還是給回此Layout
      mDragHelper.processTouchEvent(event);
      return true;
    } else {
      // 不在mFlexView觸摸范圍內(nèi),并且子View沒有消費(fèi),返回false,把事件傳遞回去
      return false;
    }
  }

同時(shí)我們需要對滾動事件進(jìn)行監(jiān)聽,我們需要在此關(guān)閉的整個平移執(zhí)行事件。

 @Override
  public void computeScroll() {
    if (mDragHelper.continueSettling(true)) {
      invalidate();
    } else if (mIsClosing && mOnLayoutStateListener != null) {
      // 正在關(guān)閉的情況下,并且拖動結(jié)束后,告知將要關(guān)閉頁面
      mOnLayoutStateListener.onClose();
      mIsClosing = false;
    }
  }

  /**
   * 監(jiān)聽布局是否水平拖動關(guān)閉了
   */
  public interface OnLayoutStateListener {

    void onClose();

  }

  public void setOnLayoutStateListener(OnLayoutStateListener onLayoutStateListener) {
    mOnLayoutStateListener = onLayoutStateListener;
  }

  /**
   * 展開布局
   */
  public void expand() {
    mDragHelper.smoothSlideViewTo(mFlexView, 0, 0);
    invalidate();
  }

而在實(shí)際的應(yīng)用中要實(shí)現(xiàn)回彈后詳情頁面的效果,我們需要自己實(shí)現(xiàn)一個組合View,這個大家可以自己看源碼音悅臺源碼

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

相關(guān)文章

  • Android studio2.3.3升級到3.1.2坑(小記)

    Android studio2.3.3升級到3.1.2坑(小記)

    這篇文章主要介紹了Android studio2.3.3升級3.1.2坑(小記),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-09-09
  • Android studio設(shè)置文件頭定制代碼注釋的方法

    Android studio設(shè)置文件頭定制代碼注釋的方法

    這篇文章主要介紹了Android studio設(shè)置文件頭定制代碼注釋的方法,非常不錯,具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2018-08-08
  • Flutter集成高德地圖并添加自定義Maker的實(shí)踐

    Flutter集成高德地圖并添加自定義Maker的實(shí)踐

    本文主要介紹了Flutter集成高德地圖并添加自定義Maker的實(shí)踐,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-04-04
  • Android復(fù)選框?qū)υ捒蛴梅▽?shí)例簡析

    Android復(fù)選框?qū)υ捒蛴梅▽?shí)例簡析

    這篇文章主要介紹了Android復(fù)選框?qū)υ捒蛴梅?結(jié)合實(shí)例形式簡單分析了Android復(fù)選對話框的創(chuàng)建與使用技巧,需要的朋友可以參考下
    2016-01-01
  • 用Android Studio3.0新功能加快構(gòu)建速度

    用Android Studio3.0新功能加快構(gòu)建速度

    本文主要介紹了使用Android Studio3.0新功能,加快Android Studio的構(gòu)建速度等相關(guān)做法。
    2017-11-11
  • Android 高德地圖之poi搜索功能的實(shí)現(xiàn)代碼

    Android 高德地圖之poi搜索功能的實(shí)現(xiàn)代碼

    這篇文章主要介紹了android 高德地圖之poi搜索功能的實(shí)現(xiàn)代碼,在實(shí)現(xiàn)此功能時(shí)遇到很多問題,在文章都給大家提到,需要的朋友可以參考下
    2017-08-08
  • Android中ExpandableListView使用示例詳解

    Android中ExpandableListView使用示例詳解

    這篇文章主要為大家詳細(xì)介紹了Android中ExpandableListView使用示例,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-10-10
  • Android實(shí)現(xiàn)仿Windows7圖片預(yù)覽窗格效果

    Android實(shí)現(xiàn)仿Windows7圖片預(yù)覽窗格效果

    這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)仿Windows7圖片預(yù)覽窗格效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-12-12
  • Android使用貝塞爾曲線畫心形

    Android使用貝塞爾曲線畫心形

    這篇文章主要為大家詳細(xì)介紹了Android使用貝塞爾曲線畫心形,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-06-06
  • flutter 輸入框組件TextField的實(shí)現(xiàn)代碼

    flutter 輸入框組件TextField的實(shí)現(xiàn)代碼

    這篇文章主要介紹了flutter 輸入框組件TextField的實(shí)現(xiàn)代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-07-07

最新評論

大庆市| 分宜县| 穆棱市| 屏东市| 静乐县| 城固县| 青神县| 西林县| 吉安市| 高要市| 三门峡市| 微山县| 东港市| 桓仁| 富蕴县| 江安县| 牡丹江市| 集贤县| 青河县| 界首市| 略阳县| 深州市| 湘潭县| 花莲市| 西城区| 谷城县| 霍城县| 社旗县| 元阳县| 黑河市| 西乌珠穆沁旗| 益阳市| 科技| 平潭县| 嘉禾县| 台南市| 招远市| 东莞市| 镇原县| 定襄县| 运城市|