Android应用开发之自定义Behavior实现AppBarLayout越界弹性效果
白羽 2018-08-10 来源 :网络 阅读 1050 评论 0

摘要:本文将带你了解Android应用开发之自定义Behavior实现AppBarLayout越界弹性效果,希望本文对大家学Android有所帮助

        本文将带你了解Android应用开发之自定义Behavior实现AppBarLayout越界弹性效果,希望本文对大家学Android有所帮助


一、继承AppBarLayout.Behavior

AppBarLayout有一个默认的Behavior,即AppBarLayout.Behavior,AppBarLayout.Behavior已注解的方式设置给AppBarLayout。

Java代码  @CoordinatorLayout.DefaultBehavior(AppBarLayout.Behavior.class)  public class AppBarLayout extends LinearLayout {      ...  }  @CoordinatorLayout.DefaultBehavior(AppBarLayout.Behavior.class)
public class AppBarLayout extends LinearLayout {
    ...
}
1.继承AppBarLayout.Behavior自定义Behavior

我们可以继承AppBarLayout.Behavior并重新设置给AppBarLayout来修改AppBarLayout的默认滚动行为,实现AppBarLayout的弹性越界效果就可以通过这种方式实现。

继承AppBarLayout.Behavior需要重写构造方法

Java代码  public class AppBarLayoutOverScrollViewBehavior extends AppBarLayout.Behavior {        public AppBarLayoutOverScrollViewBehavior() {      }        public AppBarLayoutOverScrollViewBehavior(Context context, AttributeSet attrs) {          super(context, attrs);      }    }  public class AppBarLayoutOverScrollViewBehavior extends AppBarLayout.Behavior {

    public AppBarLayoutOverScrollViewBehavior() {
    }

    public AppBarLayoutOverScrollViewBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

}

2.将自定义的Behavior设置给AppBarLayout

可以通过两种方式将自定义的Behavior设置给AppBarLayout

在布局文件中设置

 Xml代码       <android.support.design.widget.AppBarLayout
     ...
     app:layout_behavior="packageName.AppBarLayoutOverScrollViewBehavior">
 
在代码中设置

Java代码  AppBarLayout appBar = (AppBarLayout) findViewById(R.id.appbar);  CoordinatorLayout.LayoutParams params = new CoordinatorLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);  params.setBehavior(new AppBarLayoutOverScrollViewBehavior());  appBar.setLayoutParams(params);   AppBarLayout appBar = (AppBarLayout) findViewById(R.id.appbar);
 CoordinatorLayout.LayoutParams params = new CoordinatorLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
 params.setBehavior(new AppBarLayoutOverScrollViewBehavior());
 appBar.setLayoutParams(params);
设置完成后,自定义的Behavior就会生效,但是因为没有重写任何方法,所以AppBarLayout的滚动行为不会发生变化。

二、Behavior中的回调方法分析

将自定义的Behavior设置给AppBarLayout后,可以在自定义的Behavior中重写滚动相关回调方法

Java代码  public class AppBarLayoutOverScrollViewBehavior extends AppBarLayout.Behavior {        ...        /**      * AppBarLayout布局时调用      *      * @param parent 父布局CoordinatorLayout      * @param abl 使用此Behavior的AppBarLayout      * @param layoutDirection 布局方向      * @return 返回true表示子View重新布局,返回false表示请求默认布局      */      @Override      public boolean onLayoutChild(CoordinatorLayout parent, AppBarLayout abl, int layoutDirection) {          return super.onLayoutChild(parent, abl, layoutDirection);      }        /**      * 当CoordinatorLayout的子View尝试发起嵌套滚动时调用      *      * @param parent 父布局CoordinatorLayout      * @param child 使用此Behavior的AppBarLayout      * @param directTargetChild CoordinatorLayout的子View,或者是包含嵌套滚动操作的目标View      * @param target 发起嵌套滚动的目标View(即AppBarLayout下面的ScrollView或RecyclerView)      * @param nestedScrollAxes 嵌套滚动的方向      * @return 返回true表示接受滚动      */      @Override      public boolean onStartNestedScroll(CoordinatorLayout parent, AppBarLayout child, View directTargetChild, View target, int nestedScrollAxes) {          return super.onStartNestedScroll(parent, child, directTargetChild, target, nestedScrollAxes);      }        /**      * 当嵌套滚动已由CoordinatorLayout接受时调用      *      * @param coordinatorLayout 父布局CoordinatorLayout      * @param child 使用此Behavior的AppBarLayout      * @param directTargetChild CoordinatorLayout的子View,或者是包含嵌套滚动操作的目标View      * @param target 发起嵌套滚动的目标View(即AppBarLayout下面的ScrollView或RecyclerView)      * @param nestedScrollAxes 嵌套滚动的方向      */      @Override      public void onNestedScrollAccepted(CoordinatorLayout coordinatorLayout, AppBarLayout child, View directTargetChild, View target, int nestedScrollAxes) {          super.onNestedScrollAccepted(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes);      }        /**      * 当准备开始嵌套滚动时调用      *      * @param coordinatorLayout 父布局CoordinatorLayout      * @param child 使用此Behavior的AppBarLayout      * @param target 发起嵌套滚动的目标View(即AppBarLayout下面的ScrollView或RecyclerView)      * @param dx 用户在水平方向上滑动的像素数      * @param dy 用户在垂直方向上滑动的像素数      * @param consumed 输出参数,consumed[0]为水平方向应该消耗的距离,consumed[1]为垂直方向应该消耗的距离      */      @Override      public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, int dx, int dy, int[] consumed) {          super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);      }        /**      * 嵌套滚动时调用      *      * @param coordinatorLayout 父布局CoordinatorLayout      * @param child 使用此Behavior的AppBarLayout      * @param target 发起嵌套滚动的目标View(即AppBarLayout下面的ScrollView或RecyclerView)      * @param dxConsumed 由目标View滚动操作消耗的水平像素数      * @param dyConsumed 由目标View滚动操作消耗的垂直像素数      * @param dxUnconsumed 由用户请求但是目标View滚动操作未消耗的水平像素数      * @param dyUnconsumed 由用户请求但是目标View滚动操作未消耗的垂直像素数      */      @Override      public void onNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {          super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);      }        /**      * 当嵌套滚动的子View准备快速滚动时调用      *      * @param coordinatorLayout 父布局CoordinatorLayout      * @param child 使用此Behavior的AppBarLayout      * @param target 发起嵌套滚动的目标View(即AppBarLayout下面的ScrollView或RecyclerView)      * @param velocityX 水平方向的速度      * @param velocityY 垂直方向的速度      * @return 如果Behavior消耗了快速滚动返回true      */      @Override      public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, float velocityX, float velocityY) {          return super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY);      }        /**      * 当嵌套滚动的子View快速滚动时调用      *      * @param coordinatorLayout 父布局CoordinatorLayout      * @param child 使用此Behavior的AppBarLayout      * @param target 发起嵌套滚动的目标View(即AppBarLayout下面的ScrollView或RecyclerView)      * @param velocityX 水平方向的速度      * @param velocityY 垂直方向的速度      * @param consumed 如果嵌套的子View消耗了快速滚动则为true      * @return 如果Behavior消耗了快速滚动返回true      */      @Override      public boolean onNestedFling(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, float velocityX, float velocityY, boolean consumed) {          return super.onNestedFling(coordinatorLayout, child, target, velocityX, velocityY, consumed);      }        /**      * 当定制滚动时调用      *      * @param coordinatorLayout 父布局CoordinatorLayout      * @param abl 使用此Behavior的AppBarLayout      * @param target 发起嵌套滚动的目标View(即AppBarLayout下面的ScrollView或RecyclerView)      */      @Override      public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout abl, View target) {          super.onStopNestedScroll(coordinatorLayout, abl, target);      }  }  public class AppBarLayoutOverScrollViewBehavior extends AppBarLayout.Behavior {

    ...

    /**
     * AppBarLayout布局时调用
     *
     * @param parent 父布局CoordinatorLayout
     * @param abl 使用此Behavior的AppBarLayout
     * @param layoutDirection 布局方向
     * @return 返回true表示子View重新布局,返回false表示请求默认布局
     */
    @Override
    public boolean onLayoutChild(CoordinatorLayout parent, AppBarLayout abl, int layoutDirection) {
        return super.onLayoutChild(parent, abl, layoutDirection);
    }

    /**
     * 当CoordinatorLayout的子View尝试发起嵌套滚动时调用
     *
     * @param parent 父布局CoordinatorLayout
     * @param child 使用此Behavior的AppBarLayout
     * @param directTargetChild CoordinatorLayout的子View,或者是包含嵌套滚动操作的目标View
     * @param target 发起嵌套滚动的目标View(即AppBarLayout下面的ScrollView或RecyclerView)
     * @param nestedScrollAxes 嵌套滚动的方向
     * @return 返回true表示接受滚动
     */
    @Override
    public boolean onStartNestedScroll(CoordinatorLayout parent, AppBarLayout child, View directTargetChild, View target, int nestedScrollAxes) {
        return super.onStartNestedScroll(parent, child, directTargetChild, target, nestedScrollAxes);
    }

    /**
     * 当嵌套滚动已由CoordinatorLayout接受时调用
     *
     * @param coordinatorLayout 父布局CoordinatorLayout
     * @param child 使用此Behavior的AppBarLayout
     * @param directTargetChild CoordinatorLayout的子View,或者是包含嵌套滚动操作的目标View
     * @param target 发起嵌套滚动的目标View(即AppBarLayout下面的ScrollView或RecyclerView)
     * @param nestedScrollAxes 嵌套滚动的方向
     */
    @Override
    public void onNestedScrollAccepted(CoordinatorLayout coordinatorLayout, AppBarLayout child, View directTargetChild, View target, int nestedScrollAxes) {
        super.onNestedScrollAccepted(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes);
    }

    /**
     * 当准备开始嵌套滚动时调用
     *
     * @param coordinatorLayout 父布局CoordinatorLayout
     * @param child 使用此Behavior的AppBarLayout
     * @param target 发起嵌套滚动的目标View(即AppBarLayout下面的ScrollView或RecyclerView)
     * @param dx 用户在水平方向上滑动的像素数
     * @param dy 用户在垂直方向上滑动的像素数
     * @param consumed 输出参数,consumed[0]为水平方向应该消耗的距离,consumed[1]为垂直方向应该消耗的距离
     */
    @Override
    public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, int dx, int dy, int[] consumed) {
        super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
    }

    /**
     * 嵌套滚动时调用
     *
     * @param coordinatorLayout 父布局CoordinatorLayout
     * @param child 使用此Behavior的AppBarLayout
     * @param target 发起嵌套滚动的目标View(即AppBarLayout下面的ScrollView或RecyclerView)
     * @param dxConsumed 由目标View滚动操作消耗的水平像素数
     * @param dyConsumed 由目标View滚动操作消耗的垂直像素数
     * @param dxUnconsumed 由用户请求但是目标View滚动操作未消耗的水平像素数
     * @param dyUnconsumed 由用户请求但是目标View滚动操作未消耗的垂直像素数
     */
    @Override
    public void onNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
        super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
    }

    /**
     * 当嵌套滚动的子View准备快速滚动时调用
     *
     * @param coordinatorLayout 父布局CoordinatorLayout
     * @param child 使用此Behavior的AppBarLayout
     * @param target 发起嵌套滚动的目标View(即AppBarLayout下面的ScrollView或RecyclerView)
     * @param velocityX 水平方向的速度
     * @param velocityY 垂直方向的速度
     * @return 如果Behavior消耗了快速滚动返回true
     */
    @Override
    public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, float velocityX, float velocityY) {
        return super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY);
    }

    /**
     * 当嵌套滚动的子View快速滚动时调用
     *
     * @param coordinatorLayout 父布局CoordinatorLayout
     * @param child 使用此Behavior的AppBarLayout
     * @param target 发起嵌套滚动的目标View(即AppBarLayout下面的ScrollView或RecyclerView)
     * @param velocityX 水平方向的速度
     * @param velocityY 垂直方向的速度
     * @param consumed 如果嵌套的子View消耗了快速滚动则为true
     * @return 如果Behavior消耗了快速滚动返回true
     */
    @Override
    public boolean onNestedFling(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, float velocityX, float velocityY, boolean consumed) {
        return super.onNestedFling(coordinatorLayout, child, target, velocityX, velocityY, consumed);
    }

    /**
     * 当定制滚动时调用
     *
     * @param coordinatorLayout 父布局CoordinatorLayout
     * @param abl 使用此Behavior的AppBarLayout
     * @param target 发起嵌套滚动的目标View(即AppBarLayout下面的ScrollView或RecyclerView)
     */
    @Override
    public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout abl, View target) {
        super.onStopNestedScroll(coordinatorLayout, abl, target);
    }
}
可以通过打印log来观察AppBarLayout在滚动时Behavior中回调方法的调用情况。

通过观察可以发现:

上滑时
当AppBarLayout由展开到收起时,会依次调用onStartNestedScroll()->onNestedScrollAccepted()->onNestedPreScroll()->onStopNestedScroll()
当AppBarLayout收起后继续向上滑动时,会依次调用onStartNestedScroll()->onNestedScrollAccepted()->onNestedPreScroll()->onNestedScroll()->onStopNestedScroll()
下滑时
当AppBarLayout全部展开时(即未到顶部时),会依次调用onStartNestedScroll()->onNestedScrollAccepted()->onNestedPreScroll()->onNestedScroll()->onStopNestedScroll()
当AppBarLayout全部展开时(即到顶部时),继续向下滑动屏幕,会依次调用onStartNestedScroll()->onNestedScrollAccepted()->onNestedPreScroll()->onNestedScroll()->onStopNestedScroll()
当有快速滑动时会在onStopNestedScroll()前依次调用onNestedPreFling()->onNestedFling()
所以要修改AppBarLayout的越界行为可以重写onNestedPreScroll()或onNestedScroll(),因为AppBarLayout收起时不会调用onNestedScroll(),所以只能选择重写onNestedPreScroll(),具体原因下面会有说明。

三、重写Behavior的相关方法

1.获取越界时需要改变尺寸的View

布局时会调用onLayoutChild(),所以在该方法中可获取需要改变尺寸的View,可以使用View的findViewWithTag方法获取指定的View,并初始化属性。

Java代码  public class AppBarLayoutOverScrollViewBehavior extends AppBarLayout.Behavior {      private static final String TAG = "overScroll";      private View mTargetView;       // 目标View      private int mParentHeight;      // AppBarLayout的初始高度      private int mTargetViewHeight;  // 目标View的高度        @Override      public boolean onLayoutChild(CoordinatorLayout parent, AppBarLayout abl, int layoutDirection) {          boolean handled = super.onLayoutChild(parent, abl, layoutDirection);          // 需要在调用过super.onLayoutChild()方法之后获取          if (mTargetView == null) {              mTargetView = parent.findViewWithTag(TAG);              if (mTargetView != null) {                  initial(abl);              }          }          return handled;      }        private void initial(AppBarLayout abl) {          // 必须设置ClipChildren为false,这样目标View在放大时才能超出布局的范围          abl.setClipChildren(false);          mParentHeight = abl.getHeight();          mTargetViewHeight = mTargetView.getHeight();      }        ...    }  public class AppBarLayoutOverScrollViewBehavior extends AppBarLayout.Behavior {
    private static final String TAG = "overScroll";
    private View mTargetView;       // 目标View
    private int mParentHeight;      // AppBarLayout的初始高度
    private int mTargetViewHeight;  // 目标View的高度

    @Override
    public boolean onLayoutChild(CoordinatorLayout parent, AppBarLayout abl, int layoutDirection) {
        boolean handled = super.onLayoutChild(parent, abl, layoutDirection);
        // 需要在调用过super.onLayoutChild()方法之后获取
        if (mTargetView == null) {
            mTargetView = parent.findViewWithTag(TAG);
            if (mTargetView != null) {
                initial(abl);
            }
        }
        return handled;
    }

    private void initial(AppBarLayout abl) {
        // 必须设置ClipChildren为false,这样目标View在放大时才能超出布局的范围
        abl.setClipChildren(false);
        mParentHeight = abl.getHeight();
        mTargetViewHeight = mTargetView.getHeight();
    }

    ...

}
需要在布局文件或代码中给目标View指定tag,如下:

Java代码                                                                                          ...              <android.support.design.widget.CoordinatorLayout xmlns:android="//schemas.android.com/apk/res/android"
    xmlns:app="//schemas.android.com/apk/res-auto"
    xmlns:tools="//schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/appbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:fitsSystemWindows="true"
        android:theme="@style/AppTheme.AppBarOverlay"
        android:transitionName="picture"
        app:layout_behavior="com.zly.exifviewer.widget.behavior.AppBarLayoutOverScrollViewBehavior"
        tools:targetApi="lollipop">

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/collapsingToolbarLayout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:contentScrim="@color/colorPrimary"
            app:layout_scrollFlags="scroll|enterAlways|enterAlwaysCollapsed"
            app:statusBarScrim="@color/colorPrimaryDark">

            <ImageView
                android:id="@+id/siv_picture"
                android:layout_width="match_parent"
                android:layout_height="200dp"
                android:fitsSystemWindows="true"
                android:foreground="@drawable/shape_fg_picture"
                android:scaleType="centerCrop"
                android:tag="overScroll"
                app:layout_collapseMode="parallax"
                tools:src="@android:drawable/sym_def_app_icon" />

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:contentInsetEnd="64dp"
                app:layout_collapseMode="pin"
                app:popupTheme="@style/AppTheme.PopupOverlay" />
        
    

    <android.support.v4.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

        ...

    


2.下滑处理

重写onNestedPreScroll()修改AppBarLayou滑动的顶部后的行为

Java代码  private static final float TARGET_HEIGHT = 500; // 最大滑动距离  private float mTotalDy;     // 总滑动的像素数  private float mLastScale;   // 最终放大比例  private int mLastBottom;    // AppBarLayout的最终Bottom值    @Override  public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, int dx, int dy, int[] consumed) {      // 1.mTargetView不为null      // 2.是向下滑动,dy<0表示向下滑动>= mParentHeight      if (mTargetView != null && dy < 0 && child.getBottom() >= mParentHeight) {          // 累加垂直方向上滑动的像素数          mTotalDy += -dy;          // 不能大于最大滑动距离          mTotalDy = Math.min(mTotalDy, TARGET_HEIGHT);          // 计算目标View缩放比例,不能小于1          mLastScale = Math.max(1f, 1f + mTotalDy / TARGET_HEIGHT);          // 缩放目标View          ViewCompat.setScaleX(mTargetView, mLastScale);          ViewCompat.setScaleY(mTargetView, mLastScale);          // 计算目标View放大后增加的高度          mLastBottom = mParentHeight + (int) (mTargetViewHeight / 2 * (mLastScale - 1));          // 修改AppBarLayout的高度          child.setBottom(mLastBottom);      } else {          super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);      }  }  private static final float TARGET_HEIGHT = 500; // 最大滑动距离
private float mTotalDy;     // 总滑动的像素数
private float mLastScale;   // 最终放大比例
private int mLastBottom;    // AppBarLayout的最终Bottom值

@Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, int dx, int dy, int[] consumed) {
    // 1.mTargetView不为null
    // 2.是向下滑动,dy<0表示向下滑动
    // 3.AppBarLayout已经完全展开,child.getBottom() >= mParentHeight
    if (mTargetView != null && dy < 0 && child.getBottom() >= mParentHeight) {
        // 累加垂直方向上滑动的像素数
        mTotalDy += -dy;
        // 不能大于最大滑动距离
        mTotalDy = Math.min(mTotalDy, TARGET_HEIGHT);
        // 计算目标View缩放比例,不能小于1
        mLastScale = Math.max(1f, 1f + mTotalDy / TARGET_HEIGHT);
        // 缩放目标View
        ViewCompat.setScaleX(mTargetView, mLastScale);
        ViewCompat.setScaleY(mTargetView, mLastScale);
        // 计算目标View放大后增加的高度
        mLastBottom = mParentHeight + (int) (mTargetViewHeight / 2 * (mLastScale - 1));
        // 修改AppBarLayout的高度
        child.setBottom(mLastBottom);
    } else {
        super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
    }
}
此时可以实现下滑越界时目标View放大,AppBarLayout变高的效果。

3.上滑处理

下滑时目标View放大,AppBarLayout变高,如果此时用户不松开手指,直接上滑,需要目标View缩小,并且AppBarLayout变高。

默认情况下AppBarLayout的滑动是通过修改top和bottom实现的,所以上滑时,AppBarLayout为整体向上移动,高度不会发生改变,并且AppBarLayout下面的ScrollView也会向上滚动;而我们需要的是在AppBarLayout的高度大于原始高度时,减小AppBarLayout的高度,top不发生改变,并且AppBarLayout下面的ScrollView不会向上滚动。

AppBarLayout上滑时不会调用onNestedScroll(),所以只能在onNestedPreScroll()方法中修改,这也是为什么选择onNestedPreScroll()方法的原因

Java代码  @Override  public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, int dx, int dy, int[] consumed) {      if (mTargetView != null && dy < 0 && child.getBottom() >= mParentHeight) {          ...      } else       // 1.mTargetView不为null      // 2.是向上滑动,dy>0表示向下滑动      // 3.AppBarLayout尚未恢复到原始高度child.getBottom() > mParentHeight      if (mTargetView != null && dy > 0 && child.getBottom() > mParentHeight) {          // 累减垂直方向上滑动的像素数          mTotalDy -= dy;          // 计算目标View缩放比例,不能小于1          mLastScale = Math.max(1f, 1f + mTotalDy / TARGET_HEIGHT);          // 缩放目标View          ViewCompat.setScaleX(mTargetView, mLastScale);          ViewCompat.setScaleY(mTargetView, mLastScale);          // 计算目标View缩小后减少的高度          mLastBottom = mParentHeight + (int) (mTargetViewHeight / 2 * (mLastScale - 1));          // 修改AppBarLayout的高度          child.setBottom(mLastBottom);          // 保持target不滑动          target.setScrollY(0);      } else {          super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);      }  }  @Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, int dx, int dy, int[] consumed) {
    if (mTargetView != null && dy < 0 && child.getBottom() >= mParentHeight) {
        ...
    } else 
    // 1.mTargetView不为null
    // 2.是向上滑动,dy>0表示向下滑动
    // 3.AppBarLayout尚未恢复到原始高度child.getBottom() > mParentHeight
    if (mTargetView != null && dy > 0 && child.getBottom() > mParentHeight) {
        // 累减垂直方向上滑动的像素数
        mTotalDy -= dy;
        // 计算目标View缩放比例,不能小于1
        mLastScale = Math.max(1f, 1f + mTotalDy / TARGET_HEIGHT);
        // 缩放目标View
        ViewCompat.setScaleX(mTargetView, mLastScale);
        ViewCompat.setScaleY(mTargetView, mLastScale);
        // 计算目标View缩小后减少的高度
        mLastBottom = mParentHeight + (int) (mTargetViewHeight / 2 * (mLastScale - 1));
        // 修改AppBarLayout的高度
        child.setBottom(mLastBottom);
        // 保持target不滑动
        target.setScrollY(0);
    } else {
        super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
    }
}
与上滑的逻辑基本一直,所以可写为一个方法

Java代码  @Override  public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, int dx, int dy, int[] consumed) {      if (mTargetView != null && ((dy < 0 && child.getBottom() >= mParentHeight) || (dy > 0 && child.getBottom() > mParentHeight))) {          scale(child, target, dy);      } else {          super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);      }  }    private void scale(AppBarLayout abl, View target, int dy) {      mTotalDy += -dy;      mTotalDy = Math.min(mTotalDy, TARGET_HEIGHT);      mLastScale = Math.max(1f, 1f + mTotalDy / TARGET_HEIGHT);      ViewCompat.setScaleX(mTargetView, mLastScale);      ViewCompat.setScaleY(mTargetView, mLastScale);      mLastBottom = mParentHeight + (int) (mTargetViewHeight / 2 * (mLastScale - 1));      abl.setBottom(mLastBottom);      target.setScrollY(0);  }  @Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, int dx, int dy, int[] consumed) {
    if (mTargetView != null && ((dy < 0 && child.getBottom() >= mParentHeight) || (dy > 0 && child.getBottom() > mParentHeight))) {
        scale(child, target, dy);
    } else {
        super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
    }
}

private void scale(AppBarLayout abl, View target, int dy) {
    mTotalDy += -dy;
    mTotalDy = Math.min(mTotalDy, TARGET_HEIGHT);
    mLastScale = Math.max(1f, 1f + mTotalDy / TARGET_HEIGHT);
    ViewCompat.setScaleX(mTargetView, mLastScale);
    ViewCompat.setScaleY(mTargetView, mLastScale);
    mLastBottom = mParentHeight + (int) (mTargetViewHeight / 2 * (mLastScale - 1));
    abl.setBottom(mLastBottom);
    target.setScrollY(0);
}
4.还原

当AppBarLayout处于越界时,如果用户松开手指,此时应该让目标View和AppBarLayout都还原到原始状态,重写onStopNestedScroll()方法

Java代码  @Override  public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout abl, View target) {      recovery(abl);      super.onStopNestedScroll(coordinatorLayout, abl, target);  }    private void recovery(final AppBarLayout abl) {      if (mTotalDy > 0) {          mTotalDy = 0;          // 使用属性动画还原          ValueAnimator anim = ValueAnimator.ofFloat(mLastScale, 1f).setDuration(200);          anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {              @Override              public void onAnimationUpdate(ValueAnimator animation) {                  float value = (float) animation.getAnimatedValue();                  ViewCompat.setScaleX(mTargetView, value);                  ViewCompat.setScaleY(mTargetView, value);                  abl.setBottom((int) (mLastBottom - (mLastBottom - mParentHeight) * animation.getAnimatedFraction()));              }          });          anim.start();      }  }  @Override
public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout abl, View target) {
    recovery(abl);
    super.onStopNestedScroll(coordinatorLayout, abl, target);
}

private void recovery(final AppBarLayout abl) {
    if (mTotalDy > 0) {
        mTotalDy = 0;
        // 使用属性动画还原
        ValueAnimator anim = ValueAnimator.ofFloat(mLastScale, 1f).setDuration(200);
        anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float value = (float) animation.getAnimatedValue();
                ViewCompat.setScaleX(mTargetView, value);
                ViewCompat.setScaleY(mTargetView, value);
                abl.setBottom((int) (mLastBottom - (mLastBottom - mParentHeight) * animation.getAnimatedFraction()));
            }
        });
        anim.start();
    }
}
5.优化

由于用户在滑动时有可能触发快速滑动,会导致在AppBarLayout收起后触发还原动画,重新修改AppBarLayout的Bottom,从而显示错误,所以当发生快速滑动时需要禁止还原动画,直接还原到初始状态

Java代码  private boolean isAnimate;  //是否有动画    @Override  public boolean onStartNestedScroll(CoordinatorLayout parent, AppBarLayout child, View directTargetChild, View target, int nestedScrollAxes) {      // 开始滑动时,启用动画      isAnimate = true;      return super.onStartNestedScroll(parent, child, directTargetChild, target, nestedScrollAxes);  }    @Override  public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, float velocityX, float velocityY) {      // 如果触发了快速滚动且垂直方向上速度大于100,则禁用动画      if (velocityY > 100) {          isAnimate = false;      }      return super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY);  }    private void recovery(final AppBarLayout abl) {      if (mTotalDy > 0) {          mTotalDy = 0;          if (isAnimate) {              ValueAnimator anim = ValueAnimator.ofFloat(mLastScale, 1f).setDuration(200);              anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {                  @Override                  public void onAnimationUpdate(ValueAnimator animation) {                      float value = (float) animation.getAnimatedValue();                      ViewCompat.setScaleX(mTargetView, value);                      ViewCompat.setScaleY(mTargetView, value);                      abl.setBottom((int) (mLastBottom - (mLastBottom - mParentHeight) * animation.getAnimatedFraction()));                  }              });              anim.start();          } else {              ViewCompat.setScaleX(mTargetView, 1f);              ViewCompat.setScaleY(mTargetView, 1f);              abl.setBottom(mParentHeight);          }      }  }  private boolean isAnimate;  //是否有动画

@Override
public boolean onStartNestedScroll(CoordinatorLayout parent, AppBarLayout child, View directTargetChild, View target, int nestedScrollAxes) {
    // 开始滑动时,启用动画
    isAnimate = true;
    return super.onStartNestedScroll(parent, child, directTargetChild, target, nestedScrollAxes);
}

@Override
public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, float velocityX, float velocityY) {
    // 如果触发了快速滚动且垂直方向上速度大于100,则禁用动画
    if (velocityY > 100) {
        isAnimate = false;
    }
    return super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY);
}

private void recovery(final AppBarLayout abl) {
    if (mTotalDy > 0) {
        mTotalDy = 0;
        if (isAnimate) {
            ValueAnimator anim = ValueAnimator.ofFloat(mLastScale, 1f).setDuration(200);
            anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    float value = (float) animation.getAnimatedValue();
                    ViewCompat.setScaleX(mTargetView, value);
                    ViewCompat.setScaleY(mTargetView, value);
                    abl.setBottom((int) (mLastBottom - (mLastBottom - mParentHeight) * animation.getAnimatedFraction()));
                }
            });
            anim.start();
        } else {
            ViewCompat.setScaleX(mTargetView, 1f);
            ViewCompat.setScaleY(mTargetView, 1f);
            abl.setBottom(mParentHeight);
        }
    }
}

 

本文由职坐标整理并发布,希望对同学们有所帮助。了解更多详情请关注职坐标移动开发之Android频道!


本文由 @白羽 发布于职坐标。未经许可,禁止转载。
喜欢 | 0 不喜欢 | 0
看完这篇文章有何感觉?已经有0人表态,0%的人喜欢 快给朋友分享吧~
评论(0)
后参与评论

您输入的评论内容中包含违禁敏感词

我知道了

助您圆梦职场 匹配合适岗位
验证码手机号,获得海同独家IT培训资料
选择就业方向:
人工智能物联网
大数据开发/分析
人工智能Python
Java全栈开发
WEB前端+H5

请输入正确的手机号码

请输入正确的验证码

获取验证码

您今天的短信下发次数太多了,明天再试试吧!

提交

我们会在第一时间安排职业规划师联系您!

您也可以联系我们的职业规划师咨询:

小职老师的微信号:z_zhizuobiao
小职老师的微信号:z_zhizuobiao

版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
 沪公网安备 31011502005948号    

©2015 www.zhizuobiao.com All Rights Reserved

208小时内训课程