我正在尝试实现这个播放器动画
我也希望能够在崩溃的时候和扩展的时候在歌曲上滑动。因此,我们的想法是使用MotionLayout
和RecyclerView
,并让RecyclerView
的每一项都成为MotionLayout
。通过这种方式,我可以在RecyclerView
上应用一个扩展动画,也可以对它的子级应用转换。
转换本身工作良好,如所附视频所示。但是,让拖动在RecyclerView
上工作并不是这样,只有当触摸从RecyclerView
外部开始时才能检测到,如视频中高亮显示的触摸所示,触摸从RecyclerView
下面开始。
如果触摸开始于RecyclerView
,则滚动歌曲会消耗事件。即使禁用附加的LinearLayoutManager
中的滚动也不起作用。我还试图重写onTouch
,使RecyclerView
始终返回false
,而不使用任何触摸事件(理论上),但这也不起作用。
这个项目可以在这里找到,https://github.com/vlatkozelka/PlayerAnimation2 --它不是一个可以生产的应用程序,只是一个测试操场。
这是相关代码
布局
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.motion.widget.MotionLayout 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"
android:background="@color/colorPrimary"
app:layoutDescription="@xml/player_scene"
tools:context=".MainActivity"
android:id="@+id/layout_main"
>
<FrameLayout
android:id="@+id/layout_player"
android:layout_width="match_parent"
android:layout_height="@dimen/mini_player_height"
android:elevation="2dp"
app:layout_constraintBottom_toTopOf="@id/layout_navigation"
app:layout_constraintStart_toStartOf="parent"
android:background="@color/dark_grey"
android:focusable="true"
android:clickable="true"
>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_songs"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:focusable="false"
android:clickable="false"
/>
</FrameLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/layout_navigation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/dark_grey"
android:padding="5dp"
android:weightSum="3"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent">
<ImageView
android:id="@+id/iv_home"
android:layout_width="0dp"
android:layout_height="34dp"
android:layout_weight="1"
android:tint="#fff"
app:layout_constraintEnd_toStartOf="@id/iv_search"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_home_24px" />
<ImageView
android:id="@+id/iv_search"
android:layout_width="0dp"
android:layout_height="34dp"
android:layout_weight="1"
android:tint="#fff"
app:layout_constraintEnd_toStartOf="@id/iv_library"
app:layout_constraintStart_toEndOf="@id/iv_home"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_search_24px" />
<ImageView
android:id="@+id/iv_library"
android:layout_width="0dp"
android:layout_height="34dp"
android:layout_weight="1"
android:tint="#fff"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/iv_search"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_library_music_24px" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.motion.widget.MotionLayout>
MotionScene
<?xml version="1.0" encoding="utf-8"?>
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<Transition
android:id="@+id/dragUp"
app:constraintSetEnd="@id/expanded"
app:constraintSetStart="@id/collapsed">
<OnSwipe
app:dragDirection="dragUp"
app:touchRegionId="@id/layout_player" />
<OnClick
app:clickAction="transitionToEnd"
app:targetId="@id/layout_player" />
</Transition>
<Transition
android:id="@+id/dragDown"
app:constraintSetEnd="@id/collapsed"
app:constraintSetStart="@id/expanded">
<OnSwipe
app:dragDirection="dragDown"
app:touchRegionId="@id/layout_player" />
<OnClick
app:clickAction="transitionToEnd"
app:targetId="@id/layout_player" />
</Transition>
<ConstraintSet android:id="@+id/collapsed">
<Constraint
android:id="@+id/layout_navigation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/dark_grey"
android:orientation="horizontal"
android:padding="5dp"
android:weightSum="3"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<Constraint
android:id="@+id/layout_player"
android:layout_width="match_parent"
android:layout_height="@dimen/mini_player_height"
android:elevation="2dp"
app:layout_constraintBottom_toTopOf="@id/layout_navigation"
app:layout_constraintStart_toStartOf="parent" />
</ConstraintSet>
<ConstraintSet android:id="@+id/expanded">
<Constraint
android:id="@+id/layout_navigation"
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="@color/dark_grey"
android:orientation="horizontal"
android:padding="5dp"
android:weightSum="3"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="parent" />
<Constraint
android:id="@+id/layout_player"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:elevation="2dp"
app:layout_constraintBottom_toTopOf="@id/layout_navigation"
app:layout_constraintStart_toStartOf="parent" />
</ConstraintSet>
</MotionScene>
MainActivity
package com.example.playeranimation2
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.constraintlayout.motion.widget.MotionLayout
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.PagerSnapHelper
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2
import io.reactivex.subjects.PublishSubject
import org.notests.sharedsequence.Driver
data class AppState(
val songs: List<Song> = Song.getRandomSongs(),
val currentSong: Int = 0,
val expandedPercent: Float = 0f
)
class MainActivity : AppCompatActivity() {
companion object {
var appState = AppState()
val appStateObservable = PublishSubject.create<AppState>()
val appStateDriver = Driver(appStateObservable.startWith(appState))
}
lateinit var mainLayout: MotionLayout
lateinit var songsRecycler: RecyclerView
lateinit var playerLayout : ViewGroup
lateinit var adapter: SongsAdapter
lateinit var snapHelper: PagerSnapHelper
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mainLayout = findViewById(R.id.layout_main)
songsRecycler = findViewById(R.id.recycler_songs)
playerLayout = findViewById(R.id.layout_player)
songsRecycler.layoutManager = LinearLayoutManager(this).apply { orientation = LinearLayoutManager.HORIZONTAL }
adapter = SongsAdapter()
songsRecycler.adapter = adapter
adapter.refreshData(appState.songs)
snapHelper = PagerSnapHelper()
snapHelper.attachToRecyclerView(songsRecycler)
mainLayout.setTransitionListener(object : MotionLayout.TransitionListener {
override fun onTransitionTrigger(p0: MotionLayout?, p1: Int, p2: Boolean, p3: Float) {
}
override fun onTransitionStarted(p0: MotionLayout?, p1: Int, p2: Int) {
}
override fun onTransitionChange(p0: MotionLayout?, p1: Int, p2: Int, p3: Float) {
if (p1 == R.id.expanded) {
appState = appState.copy(expandedPercent = 1f - p3)
} else {
appState = appState.copy(expandedPercent = p3)
}
emitNewAppState()
adapter.expandedPercent = appState.expandedPercent
updateAllRecyclerChildren()
}
override fun onTransitionCompleted(p0: MotionLayout?, p1: Int) {
}
})
songsRecycler.addOnScrollListener(object: RecyclerView.OnScrollListener(){
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
updateAllRecyclerChildren()
}
})
}
fun updateAllRecyclerChildren(){
for (i in appState.songs.indices) {
val childView = songsRecycler.getChildAt(i)
if(childView != null){
val songViewHolder = songsRecycler.getChildViewHolder(childView) as? SongsAdapter.SongViewHolder
songViewHolder?.setExpandPercent(appState.expandedPercent)
}
}
}
fun emitNewAppState() {
appStateObservable.onNext(appState)
}
class SongsAdapter : RecyclerView.Adapter<SongsAdapter.SongViewHolder>() {
val data = arrayListOf<Song>()
var expandedPercent : Float = 0f
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SongViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_song, parent, false)
return SongViewHolder(view)
}
override fun getItemCount(): Int {
return data.size
}
override fun onBindViewHolder(holder: SongViewHolder, position: Int) {
holder.bind(data[position], expandedPercent)
}
fun refreshData(data: List<Song>) {
this.data.clear()
this.data.addAll(data)
}
class SongViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var songImageView: ImageView? = itemView.findViewById(R.id.iv_cover_art)
var songTitleView: TextView? = itemView.findViewById(R.id.tv_song_title)
var rootView: MotionLayout? = itemView.findViewById(R.id.root_view)
fun bind(song: Song, expandedPercent: Float) {
songImageView?.setImageResource(song.imageRes)
songTitleView?.text = song.title
setExpandPercent(expandedPercent)
}
fun setExpandPercent(percent: Float) {
rootView?.setInterpolatedProgress(percent)
}
}
}
}
你知道我怎样才能让RecyclerView
用MotionLayout
的拖曳手势表现得很好吗?
发布于 2020-12-08 16:27:25
我也面临着同样的问题,我被困了超过5天,最后,我找到了一个可能适合你的简单解决方案。问题是,当用户触摸屏幕时,回收器视图就会聚焦,而没有将其转发到应用滑动动画的运动布局中。
因此,我只是在回收器视图上添加了一个触摸侦听器,并将其转发到运动布局类的on方法中。检查代码
recyclerView.setOnTouchListener { _, event ->
binding.motionLayout.onTouchEvent(event)
return@setOnTouchListener false
}
只需从onTouchListener获取运动事件,并将其转发给运动布局中的onTouchEvent方法。
希望对你有帮助;)
发布于 2021-01-28 10:03:04
为潜在的修复这里创建了一个拉请求
需要进行两项主要修改
touchRegionId
改为在运动屏幕文件中同时使用touchAnchorId
和touchAnchorSide
<OnSwipe
app:dragDirection="dragUp"
app:touchAnchorId="@id/layout_player"
app:touchAnchorSide="top"/>
RecyclerView
的onTouch
委托给MotionLayout
songsRecycler.setOnTouchListener { _, motionEvent ->
if (mainLayout.onTouchEvent(motionEvent).not()) {
songsRecycler.onTouchEvent(motionEvent)
} else {
songsRecycler.onTouchEvent(motionEvent)
}
}
发布于 2020-10-10 05:48:18
这种做法应该正好相反。在本例中,您应该拦截父视图中的触摸事件,即MotionLayout。当你在上下方向滑动时,你应该显式地返回true,然后这种触摸不会传递到子视图,即RecyclerView。如果你还假的话,它就会像往常一样传递到回收视图。有关更多信息,请查看此资源。对不起,我连续工作了20个小时,所以我需要小睡一会儿。如果你需要密码,我可以明天发出去。
编码愉快!
https://stackoverflow.com/questions/64088595
复制相似问题