国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 系統 > Android > 正文

android仿音悅臺頁面交互效果實例代碼

2019-12-12 04:06:27
字體:
來源:轉載
供稿:網友

概述

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

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


當圖片到達底部后,左右拖動

實現的思路

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

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

代碼分析

首先我們會自定義一個容器,容器的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作為拖動的響應View,mLinkView作為跟隨View        mFlexView = getChildAt(0);        mFollowView = getChildAt(1);        mDragHeight = getMeasuredHeight() - mFlexView.getMeasuredHeight();        mFlexWidth = mFlexView.getMeasuredWidth();        mFlexHeight = mFlexView.getMeasuredHeight();      }    });  }

ViewDragHelper 的回調需要做的事情比較多,在 mFlexView 拖動的時候需要同時設置 mFlexView 和 mFollowView 的相應變化效果,在 mFlexView 釋放的時候需要處理關閉或收起等效果。所以這里我們需要對ViewDragHelper個各種回調事件進行監聽。這也是本功能最核心的:

 private class FlexCallback extends ViewDragHelper.Callback {    @Override    public boolean tryCaptureView(View child, int pointerId) {      // mFlexView來響應觸摸事件      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) {        // 不允許垂直拖動的時候是mFlexView在底部水平拖動一定距離時設置的,返回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) {        // 如果水平拖動有效,首先根據拖動的速度決定關閉頁面,方向根據速度正負決定        if (xvel > 1500) {          mDragHelper.settleCapturedViewAt(mDragWidth, mDragHeight);          mIsClosing = true;        } else if (xvel < -1500) {          mDragHelper.settleCapturedViewAt(-mDragWidth, mDragHeight);          mIsClosing = true;        } else {          // 速度沒到關閉頁面的要求,根據透明度來決定關閉頁面,方向根據releasedChild.getLeft()正負決定          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 {        // 根據垂直方向的速度正負決定布局的展示方式        if (yvel > 1500) {          mDragHelper.settleCapturedViewAt(0, mDragHeight);        } else if (yvel < -1500) {          mDragHelper.settleCapturedViewAt(0, 0);        } else {          // 根據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;      // 設置縮放基點      changedView.setPivotX(changedView.getWidth() - mFlexScaleOffset);      changedView.setPivotY(changedView.getHeight() - mFlexScaleOffset);      // 設置比例      changedView.setScaleX(mFlexScaleRatio);      changedView.setScaleY(mFlexScaleRatio);      // mFollowView透明度的比率      float alphaRatio = 1 - fraction;      // 設置透明度      mFollowView.setAlpha(alphaRatio);      // 根據垂直方向的dy設置top,產生跟隨mFlexView的效果      mFollowView.setTop(mFollowView.getTop() + dy);      // 到底部的時候,changedView的top剛好等于mDragHeight,以此作為水平拖動的基準      mHorizontalDragEnable = top == mDragHeight;      if (mHorizontalDragEnable) {        // 如果水平拖動允許的話,由于設置縮放不會影響mFlexView的寬高(比如getWidth),所以水平拖動距離為mFlexView寬度一半        mDragWidth = (int) (changedView.getMeasuredWidth() * 0.5f);        // 設置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) {        // 創建子元素位置緩存        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() + "】");    }  }

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

@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);        // 測量子元素并考慮外邊距        // 參數heightUse:父容器豎直已經被占用的空間,比如被父容器的其他子 view 所占用的空間;這里我們需要的是子View垂直排列,所以需要設置這個值        measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, tmpHeight);        // 獲取子元素的布局參數        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();        // 計算子元素寬度,取子控件最大寬度        desireWidth = Math.max(desireWidth, child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);        // 計算子元素高度        tmpHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;        desireHeight += tmpHeight;      }      // 考慮父容器內邊距      desireWidth += getPaddingLeft() + getPaddingRight();      desireHeight += getPaddingTop() + getPaddingBottom();      // 嘗試比較建議最小值和期望值的大小并取大值      desireWidth = Math.max(desireWidth, getSuggestedMinimumWidth());      desireHeight = Math.max(desireHeight, getSuggestedMinimumHeight());    }    // 設置最終測量值    setMeasuredDimension(resolveSize(desireWidth, widthMeasureSpec), resolveSize(desireHeight, heightMeasureSpec));  }  @Override  protected void onLayout(boolean changed, int l, int t, int r, int b) {    if (mFlexLayoutPosition != null) {      // 因為在用到ViewDragHelper處理布局交互的時候,若是有子View的UI更新導致重新Layout的話,需要我們自己處理ViewDragHelper拖動時子View的位置,否則會導致位置錯誤      // 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("此容器的子元素個數必須為2!");    }    for (int i = 0; i < count; i++) {      // 遍歷子元素并對其進行定位布局      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 真實寬高,ViewDragHelper 仍然會阻斷 mFlexView 的真實寬高的區域,所以這里判斷手指是否落在 mFlexView 視覺上的范圍內,在才去調 ViewDragHelper 的 shouldInterceptTouchEvent 方法。

 @Override  public boolean onInterceptTouchEvent(MotionEvent ev) {    // Log.e("YytLayout", mFlexView.getLeft() + ";" + mFlexView.getTop() + " --- " + ev.getX() + ":" + ev.getY());    // 由于縮放不會影響mFlexView真實寬高,這里手動計算視覺上的范圍    float left = mFlexView.getLeft() + mFlexWidth * (1 - mFlexScaleRatio) - mFlexScaleOffset * (1 - mFlexScaleRatio);    float top = mFlexView.getTop() + mFlexHeight * (1 - mFlexScaleRatio) - mFlexScaleOffset * (1 - mFlexScaleRatio);    // 這里所做的是判斷手指是否落在mFlexView視覺上的范圍內    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) {      // 這里還要做判斷是因為,即使我不阻斷事件,但是此Layout的子View不消費的話,事件還是給回此Layout      mDragHelper.processTouchEvent(event);      return true;    } else {      // 不在mFlexView觸摸范圍內,并且子View沒有消費,返回false,把事件傳遞回去      return false;    }  }

同時我們需要對滾動事件進行監聽,我們需要在此關閉的整個平移執行事件。

 @Override  public void computeScroll() {    if (mDragHelper.continueSettling(true)) {      invalidate();    } else if (mIsClosing && mOnLayoutStateListener != null) {      // 正在關閉的情況下,并且拖動結束后,告知將要關閉頁面      mOnLayoutStateListener.onClose();      mIsClosing = false;    }  }  /**   * 監聽布局是否水平拖動關閉了   */  public interface OnLayoutStateListener {    void onClose();  }  public void setOnLayoutStateListener(OnLayoutStateListener onLayoutStateListener) {    mOnLayoutStateListener = onLayoutStateListener;  }  /**   * 展開布局   */  public void expand() {    mDragHelper.smoothSlideViewTo(mFlexView, 0, 0);    invalidate();  }

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

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持武林網。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 哈尔滨市| 临城县| 金门县| 永城市| 颍上县| 秭归县| 平罗县| 临西县| 顺义区| 梓潼县| 越西县| 云安县| 屯昌县| 亳州市| 梁山县| 阿图什市| 敦煌市| 嫩江县| 静海县| 全椒县| 和政县| 巴林左旗| 安多县| 海淀区| 望江县| 阜宁县| 化德县| 江安县| 昌宁县| 罗城| 红安县| 潍坊市| 仙居县| 莆田市| 台东县| 贡嘎县| 张家川| 珲春市| 弋阳县| 许昌县| 洞口县|