仿蜻蜓FM详情页嵌套滑动效果(IOS版本效果)

闲来无事,看到蜻蜓FM的详情页面效果挺好玩的,于是乎仿一下,因为IOS版本和Android版本的效果不一致,看了下Android实现上更加复杂一点,所以先实现了IOS的方式,效果上大致的功能效果都有了,细节没有打磨,终究只是demo,为了探究滑动处理方式。

老规矩,先上图,看一下效果。

在这里插入图片描述

不知道看了效果之后,你还有没有看下去的兴趣😄😄😄

实现思路

一看到这种折叠效果,我第一感觉应该是使用BottomSheetBehavior,当然我本次使用也是使用的这个。

对于BottomSheetBehavior我打算的后续会和Behavior单独写文章说明下使用和自定义相关的。😄主要是目前还没有研究太深,说太多容易暴露出来o(╥﹏╥)o

<?xml version="1.0" encoding="utf-8"?>
<org.fireking.laboratory.qingtingfm.detail_ios.widget.BottomSheetRootFrameLayout 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="org.fireking.laboratory.qingtingfm.detail_ios.QtDetailsIosActivity">

    <androidx.appcompat.widget.LinearLayoutCompat
        android:id="@+id/flAudioInfo"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@drawable/sp_audio_bg"
        android:orientation="vertical">

        <View
            android:id="@+id/vStatusBar"
            android:layout_width="match_parent"
            android:layout_height="25dp" />

        <org.fireking.laboratory.qingtingfm.detail_ios.widget.QtFmAppbar
            android:id="@+id/fmAppbar"
            android:layout_width="match_parent"
            android:layout_height="44dp" />

        <org.fireking.laboratory.qingtingfm.detail_ios.widget.QtFmContent
            android:id="@+id/qtFmContent"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    </androidx.appcompat.widget.LinearLayoutCompat>

    <androidx.coordinatorlayout.widget.CoordinatorLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:visibility="gone">

        <androidx.appcompat.widget.LinearLayoutCompat
            android:id="@+id/bottomSheetLayout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@drawable/sp_round_12"
            android:orientation="vertical"
            app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">

            <net.lucode.hackware.magicindicator.MagicIndicator
                android:id="@+id/magicIndicator"
                android:layout_width="match_parent"
                android:layout_height="50dp" />

            <View
                android:layout_width="match_parent"
                android:layout_height="0.7dp"
                android:background="#EEEEEE" />

            <androidx.viewpager.widget.ViewPager
                android:id="@+id/vPager"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:nestedScrollingEnabled="false" />
        </androidx.appcompat.widget.LinearLayoutCompat>
    </androidx.coordinatorlayout.widget.CoordinatorLayout>
</org.fireking.laboratory.qingtingfm.detail_ios.widget.BottomSheetRootFrameLayout>

BottomSheetRootFrameLayout 中主要做的事情就是用来计算BottomSheetBehavior默认展开状态下最大应该展示多少距离,这个距离是不能写死的,因为需要根据请求到的内容来动态计算,所以这里重写了onMeasure,来根据内容变化动态计算出来一个实际的高度并设置给BottomSheetBehaviorpeekHeight方法。

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
     super.onMeasure(widthMeasureSpec, heightMeasureSpec)
     bottomSheetLayout?.also {
         val newBottomSheetHeight =
             (measuredHeight - dip2px(44F) - StatusBarUtil.getStatusBarHeight(context)).toInt()
         if (bottomSheetHeight != newBottomSheetHeight) {
             it.layoutParams.height = newBottomSheetHeight
             bottomSheetHeight = newBottomSheetHeight
         }
         val newPeekHeight =
             (measuredHeight - qtFmContent!!.measuredHeight - dip2px(44F) - StatusBarUtil.getStatusBarHeight(
                 context
             )).toInt()
         if (currentPeekHeight != newPeekHeight && !isFinal) {
             val mBottomSheetBehavior = BottomSheetBehavior.from(it)
             mBottomSheetBehavior.peekHeight = newPeekHeight
             currentPeekHeight = newPeekHeight
             //设置高度后,进行二次计算,确保布局同步
             super.onMeasure(widthMeasureSpec, heightMeasureSpec)
             isFinal = true
         }
     }
 }

完成上面的计算设置之后,你会发现BottomSheetBehavior在没有填充SmartRefreshLayoutRecyclerView的情况下,可以正常使用了,且基本满足折叠需求,而且折叠展示的高度都是满足实际需求的。

标题栏的展示隐藏控制,可以直接使用`BottomSheetBehavior#onStateChanged进行处理

val bottomSheetBehavior = BottomSheetBehavior.from(bottomSheetLayout)
   bottomSheetBehavior.setBottomSheetCallback(object :
       BottomSheetBehavior.BottomSheetCallback() {
       override fun onStateChanged(bottomSheet: View, newState: Int) {
           if (newState == BottomSheetBehavior.STATE_EXPANDED) {
               fmAppbar.showTitle()
           } else if (newState == BottomSheetBehavior.STATE_COLLAPSED) {
               fmAppbar.hideTitle()
           }
       }

       override fun onSlide(bottomSheet: View, slideOffset: Float) {

       }
   })

至此BottomBehavior也和标题栏联动起来了,嗯,好像标题栏变化还有个动画效果。安排

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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:id="@+id/ctlTransitionPanel"
    android:layout_width="match_parent"
    android:layout_height="44dp"
    android:animateLayoutChanges="true"
    tools:background="#CC297F87">

    <androidx.appcompat.widget.AppCompatImageView
        android:id="@+id/ivBackIcon"
        android:layout_width="28dp"
        android:layout_height="28dp"
        android:layout_marginStart="16dp"
        android:src="@drawable/ic_back_white"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <androidx.appcompat.widget.LinearLayoutCompat
        android:id="@+id/llAppbarTransitionContainer"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:gravity="center_vertical"
        android:visibility="gone"
        app:layout_constraintEnd_toStartOf="@+id/ivWhiteWechat"
        app:layout_constraintStart_toEndOf="@+id/ivBackIcon">

        <androidx.appcompat.widget.AppCompatTextView
            android:id="@+id/tvTitle"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="12dp"
            android:text="隋唐演义"
            android:textColor="@android:color/white"
            android:textSize="18dp" />

        <View
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1" />

        <org.fireking.basic.textview.widget.ShapeTextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginEnd="16dp"
            android:paddingStart="16dp"
            android:paddingTop="4dp"
            android:paddingEnd="16dp"
            android:paddingBottom="4dp"
            android:text="收藏"
            android:textColor="@android:color/white"
            app:stv_corners="12dp"
            app:stv_solid="#FF2442" />
    </androidx.appcompat.widget.LinearLayoutCompat>

    <androidx.appcompat.widget.AppCompatImageView
        android:id="@+id/ivWhiteWechat"
        android:layout_width="32dp"
        android:layout_height="32dp"
        android:layout_marginEnd="16dp"
        android:src="@drawable/ic_wechat_white"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/ivWhiteMore"
        app:layout_constraintTop_toTopOf="parent" />

    <androidx.appcompat.widget.AppCompatImageView
        android:id="@+id/ivWhiteMore"
        android:layout_width="32dp"
        android:layout_height="32dp"
        android:layout_marginEnd="16dp"
        android:src="@drawable/ic_more_white"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

QtFmAppbar设置动画效果,这里使用LayoutTransition实现动画,可自行检索下用法,比较简单,就不过多说明了

class QtFmAppbar @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null
) : RelativeLayout(context, attrs) {

    private var viewBinding: QtfmAppbarBinding? = null

    init {
        viewBinding = QtfmAppbarBinding.inflate(LayoutInflater.from(context), this, true)

        val transitionHeight =
            TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 44F, resources.displayMetrics)

        val transition = LayoutTransition()
        viewBinding?.apply {
            ctlTransitionPanel.layoutTransition = transition
        }

        val appearingAnimator =
            ObjectAnimator.ofFloat(null, "translationY", transitionHeight, 0F)
                .setDuration(transition.getDuration(LayoutTransition.APPEARING))
        transition.setAnimator(LayoutTransition.APPEARING, appearingAnimator)

        val disAppearingAnimator =
            ObjectAnimator.ofFloat(null, "translationY", 0F, transitionHeight)
                .setDuration(transition.getDuration(LayoutTransition.APPEARING))
        transition.setAnimator(LayoutTransition.DISAPPEARING, disAppearingAnimator)
    }

    fun showTitle() {
        viewBinding?.apply {
            llAppbarTransitionContainer.visibility = View.VISIBLE
        }
    }

    fun hideTitle() {
        viewBinding?.apply {
            llAppbarTransitionContainer.visibility = View.GONE
        }
    }
}

现在标题栏的动画也有了。

剩下来就是Viewpager里面设置两个Fragment,分别放置对应的列表数据。

一切都井然有序。Nice~

但是当没有问题的时候,我们发现问题来了。

当设置了SmartRefreshLayoutRecyclerView之后,BottomSheetBehavior的滑动出现了冲突,不能流畅滑动了,而且之后滑动的时候只是下面的RecyclerView列表再动,并没有使整体滑动。

简单阅读了下BottomSheetBehavior源码之后,发现他里面也处理了Nestedscrolling,那么问题就一目了然了,其实我们根本就不需要SmartRefreshLayout再去处理NestedScrolling,直接交付给BottomSheetBehavior一个人去处理就ok了。最简答的方式就是使用requestDisallowInterceptTouchEvent=false直接让SmartRefresh父类处理即可,但是看了下,发现继承实现SmartRefreshLayout的话,对于事件拦截也不是很方便。这个时候发现了一个属性,srlEnableNestedScrolling,发现使用的地方刚好就是用来处理是否拦截相关的操作。所以,直接将SmartRefeshLayoutapp:srlEnableRefresh="false"设置即可。

简单尝试了下,困境解决了。Nice~

最后说明一下点击的时候收起和展开布局,这个相对比较简单,就是使用BottomSheetBehavior的状态实现的。

BottomSheetBehavior有如下几种状态


  /** The bottom sheet is dragging. */
  public static final int STATE_DRAGGING = 1;

  /** The bottom sheet is settling. */
  public static final int STATE_SETTLING = 2;

  /** The bottom sheet is expanded. */
  public static final int STATE_EXPANDED = 3;

  /** The bottom sheet is collapsed. */
  public static final int STATE_COLLAPSED = 4;

  /** The bottom sheet is hidden. */
  public static final int STATE_HIDDEN = 5;

只需要我们点击的时候,如果想要折叠,就是设置支持hide即可。

 if (it) {
     bottomSheetBehavior.isHideable = false
     bottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
 } else {
     bottomSheetBehavior.isHideable = true
     bottomSheetBehavior.state = BottomSheetBehavior.STATE_HIDDEN
 }

到这里所有的讲解都完成了,核心代码也都写出来了,相信如果认真去看的话,你也能实现出来对应的效果。

夜深了,再见!

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值