【光环助手V5.5.0】游戏单广场(UI)https://git.ghzs.com/pm/halo-app-issues/-/issues/1598
【光环助手V5.5.0】首页顶部tab-游戏单广场(UI)https://git.ghzs.com/pm/halo-app-issues/-/issues/1599 【光环助手V5.5.0】首页/版块内容列表-游戏单广场(UI)https://git.ghzs.com/pm/halo-app-issues/-/issues/1600
This commit is contained in:
23
app/src/main/java/com/gh/common/view/StackRecyclerView.kt
Normal file
23
app/src/main/java/com/gh/common/view/StackRecyclerView.kt
Normal file
@ -0,0 +1,23 @@
|
||||
package com.gh.common.view
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.MotionEvent
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
||||
class StackRecyclerView : RecyclerView {
|
||||
constructor(context: Context) : super(context)
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(
|
||||
context,
|
||||
attrs,
|
||||
defStyleAttr
|
||||
)
|
||||
|
||||
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
|
||||
parent.requestDisallowInterceptTouchEvent(true)
|
||||
return super.dispatchTouchEvent(ev)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
package com.gh.common.view.stacklayoutmanager
|
||||
|
||||
import android.view.View
|
||||
import com.gh.common.view.stacklayoutmanager.StackLayoutManager.ScrollOrientation
|
||||
|
||||
abstract class StackAnimation(scrollOrientation: ScrollOrientation, visibleCount: Int) {
|
||||
|
||||
protected val mScrollOrientation = scrollOrientation
|
||||
protected var mVisibleCount = visibleCount
|
||||
|
||||
internal fun setVisibleCount(visibleCount: Int) {
|
||||
mVisibleCount = visibleCount
|
||||
}
|
||||
|
||||
/**
|
||||
* 外部回调,用来做动画.
|
||||
* @param firstMovePercent 第一个可视 item 移动的百分比,当即将完全移出屏幕的时候 firstMovePercent无限接近1.
|
||||
* @param itemView 当前的 itemView.
|
||||
* @param position 当前 itemView 对应的位置,position = 0 until visibleCount.
|
||||
*/
|
||||
abstract fun doAnimation(firstMovePercent: Float, itemView: View, position: Int)
|
||||
}
|
||||
@ -0,0 +1,34 @@
|
||||
package com.gh.common.view.stacklayoutmanager
|
||||
|
||||
import android.view.View
|
||||
|
||||
abstract class StackLayout(scrollOrientation: StackLayoutManager.ScrollOrientation,
|
||||
visibleCount: Int,
|
||||
perItemOffset: Int) {
|
||||
|
||||
protected val mScrollOrientation = scrollOrientation
|
||||
protected var mVisibleCount = visibleCount
|
||||
protected var mPerItemOffset = perItemOffset
|
||||
|
||||
internal fun setItemOffset(offset: Int) {
|
||||
mPerItemOffset = offset
|
||||
}
|
||||
|
||||
internal fun getItemOffset(): Int {
|
||||
return mPerItemOffset
|
||||
}
|
||||
|
||||
/**
|
||||
* 外部回调,用来做布局.
|
||||
* @param firstMovePercent 第一个可视 item 移动的百分比,当即将完全移出屏幕的时候 firstMovePercent无限接近1.
|
||||
* @param itemView 当前的 itemView.
|
||||
* @param position 当前 itemView 对应的位置,position = 0 until visibleCount.
|
||||
*/
|
||||
abstract fun doLayout(stackLayoutManager: StackLayoutManager,
|
||||
scrollOffset: Int,
|
||||
firstMovePercent: Float,
|
||||
itemView: View,
|
||||
position: Int)
|
||||
|
||||
abstract fun requestLayout()
|
||||
}
|
||||
@ -0,0 +1,544 @@
|
||||
package com.gh.common.view.stacklayoutmanager
|
||||
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.RecyclerView.*
|
||||
import com.gh.common.util.DisplayUtils
|
||||
import com.gh.common.util.dip2px
|
||||
import com.lightgame.utils.Utils
|
||||
import kotlin.math.ceil
|
||||
import kotlin.math.floor
|
||||
|
||||
class StackLayoutManager(scrollOrientation: ScrollOrientation,
|
||||
visibleCount: Int,
|
||||
animation: Class<out StackAnimation>,
|
||||
layout: Class<out StackLayout>) : RecyclerView.LayoutManager() {
|
||||
private enum class FlingOrientation{NONE, LEFT_TO_RIGHT, RIGHT_TO_LEFT, TOP_TO_BOTTOM, BOTTOM_TO_TOP}
|
||||
|
||||
enum class ScrollOrientation{LEFT_TO_RIGHT, RIGHT_TO_LEFT, TOP_TO_BOTTOM, BOTTOM_TO_TOP}
|
||||
|
||||
private var mVisibleItemCount = visibleCount
|
||||
|
||||
private var mScrollOrientation = scrollOrientation
|
||||
|
||||
private var mScrollOffset: Int
|
||||
|
||||
var isFlinging = false
|
||||
|
||||
private lateinit var mOnScrollListener: OnScrollListener
|
||||
private lateinit var mOnFlingListener: OnFlingListener
|
||||
|
||||
//做动画的组件,支持自定义
|
||||
private var mAnimation: StackAnimation? = null
|
||||
//做布局的组件,支持自定义
|
||||
private var mLayout: StackLayout? = null
|
||||
|
||||
//是否是翻页效果
|
||||
private var mPagerMode = true
|
||||
|
||||
//触发翻页效果的最低 Fling速度
|
||||
private var mPagerFlingVelocity = 0
|
||||
|
||||
//标志当前滚动是否是调用scrollToCenter之后触发的滚动
|
||||
private var mFixScrolling = false
|
||||
|
||||
//fling的方向,用来判断是前翻还是后翻
|
||||
private var mFlingOrientation = FlingOrientation.NONE
|
||||
|
||||
//当前所处item对应的位置
|
||||
private var itemPosition = 0
|
||||
|
||||
//判断item位置是否发生了改变
|
||||
private var isItemPositionChanged = false
|
||||
|
||||
//item 位置发生改变的回调
|
||||
private var itemChangedListener: ItemChangedListener? = null
|
||||
|
||||
interface ItemChangedListener {
|
||||
fun onItemChanged(position: Int)
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否为ViewPager 式翻页模式.
|
||||
* <p>
|
||||
* 当设置为 true 的时候,可以配合[StackLayoutManager.setPagerFlingVelocity]设置触发翻页的最小速度.
|
||||
* @param isPagerMode 这个值默认是 false,当设置为 true 的时候,会有 viewPager 翻页效果.
|
||||
*/
|
||||
fun setPagerMode(isPagerMode: Boolean) {
|
||||
mPagerMode = isPagerMode
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 当前是否为ViewPager翻页模式.
|
||||
*/
|
||||
fun getPagerMode(): Boolean {
|
||||
return mPagerMode
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置触发ViewPager翻页效果的最小速度.
|
||||
* <p>
|
||||
* 该值仅在 [StackLayoutManager.getPagerMode] == true的时候有效.
|
||||
* @param velocity 默认值是2000.
|
||||
*/
|
||||
fun setPagerFlingVelocity(@androidx.annotation.IntRange(from = 0, to = Int.MAX_VALUE.toLong()) velocity: Int) {
|
||||
mPagerFlingVelocity = Int.MAX_VALUE.coerceAtMost(0.coerceAtLeast(velocity))
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 当前触发翻页的最小 fling 速度.
|
||||
*/
|
||||
fun getPagerFlingVelocity(): Int {
|
||||
return mPagerFlingVelocity
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置recyclerView 静止时候可见的itemView 个数.
|
||||
* @param count 可见 itemView,默认为3
|
||||
*/
|
||||
fun setVisibleItemCount(@androidx.annotation.IntRange(from = 1, to = Long.MAX_VALUE)count: Int) {
|
||||
mVisibleItemCount = (itemCount - 1).coerceAtMost(1.coerceAtLeast(count))
|
||||
mAnimation?.setVisibleCount(mVisibleItemCount)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取recyclerView 静止时候可见的itemView 个数.
|
||||
* @return 静止时候可见的itemView 个数,默认为3.
|
||||
*/
|
||||
fun getVisibleItemCount(): Int {
|
||||
return mVisibleItemCount
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 item 偏移值,即第 i 个 item 相对于 第 i-1个 item 在水平方向的偏移值,默认是40px.
|
||||
* @param offset 每个 item 相对于前一个的偏移值.
|
||||
*/
|
||||
fun setItemOffset(offset: Int) {
|
||||
mLayout?.setItemOffset(offset)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取每个 item 相对于前一个的水平偏移值.
|
||||
* @return 每个 item 相对于前一个的水平偏移值.
|
||||
*/
|
||||
fun getItemOffset(): Int {
|
||||
return if (mLayout == null) {
|
||||
0
|
||||
} else {
|
||||
mLayout!!.getItemOffset()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置item 移动动画.
|
||||
* @param animation item 移动动画.
|
||||
*/
|
||||
fun setAnimation(animation: StackAnimation) {
|
||||
mAnimation = animation
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 item 移动动画.
|
||||
* @return item 移动动画.
|
||||
*/
|
||||
fun getAnimation(): StackAnimation? {
|
||||
return mAnimation
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取StackLayoutManager 的滚动方向.
|
||||
* @return StackLayoutManager 的滚动方向.
|
||||
*/
|
||||
fun getScrollOrientation(): ScrollOrientation {
|
||||
return mScrollOrientation
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回第一个可见 itemView 的位置.
|
||||
* @return 返回第一个可见 itemView 的位置.
|
||||
*/
|
||||
fun getFirstVisibleItemPosition(): Int {
|
||||
if (width == 0 || height == 0) {
|
||||
return 0
|
||||
}
|
||||
return when(mScrollOrientation) {
|
||||
ScrollOrientation.RIGHT_TO_LEFT -> floor((mScrollOffset * 1.0 / width)).toInt()
|
||||
ScrollOrientation.LEFT_TO_RIGHT -> itemCount - 1 - ceil((mScrollOffset * 1.0 / width)).toInt()
|
||||
ScrollOrientation.BOTTOM_TO_TOP -> floor((mScrollOffset * 1.0 / height)).toInt()
|
||||
ScrollOrientation.TOP_TO_BOTTOM -> itemCount - 1 - ceil((mScrollOffset * 1.0 / height)).toInt()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 item 位置改变时触发的回调
|
||||
*/
|
||||
fun setItemChangedListener(listener: ItemChangedListener) {
|
||||
itemChangedListener = listener
|
||||
}
|
||||
|
||||
// constructor(scrollOrientation: ScrollOrientation) : this(scrollOrientation, 3, DefaultAnimation::class.java, DefaultLayout::class.java)
|
||||
//
|
||||
// constructor(scrollOrientation: ScrollOrientation, visibleCount: Int) : this(scrollOrientation, visibleCount, DefaultAnimation::class.java, DefaultLayout::class.java)
|
||||
|
||||
// constructor() : this(ScrollOrientation.RIGHT_TO_LEFT)
|
||||
|
||||
init {
|
||||
mScrollOffset = when(mScrollOrientation) {
|
||||
ScrollOrientation.RIGHT_TO_LEFT, ScrollOrientation.BOTTOM_TO_TOP -> 0
|
||||
else -> Int.MAX_VALUE
|
||||
}
|
||||
|
||||
if (StackAnimation::class.java.isAssignableFrom(animation)) {
|
||||
try {
|
||||
val cla = animation.getDeclaredConstructor(ScrollOrientation::class.java, Int::class.javaPrimitiveType)
|
||||
mAnimation = cla.newInstance(scrollOrientation, visibleCount) as StackAnimation
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
if (StackLayout::class.java.isAssignableFrom(layout)) {
|
||||
try {
|
||||
val cla = layout.getDeclaredConstructor(ScrollOrientation::class.java, Int::class.javaPrimitiveType, Int::class.javaPrimitiveType)
|
||||
mLayout = cla.newInstance(scrollOrientation, visibleCount, 30) as StackLayout
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun generateDefaultLayoutParams(): LayoutParams {
|
||||
return LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT)
|
||||
}
|
||||
|
||||
override fun onAttachedToWindow(view: RecyclerView) {
|
||||
super.onAttachedToWindow(view)
|
||||
mOnFlingListener = object : OnFlingListener() {
|
||||
override fun onFling(velocityX: Int, velocityY: Int): Boolean {
|
||||
if (mPagerMode) {
|
||||
when(mScrollOrientation) {
|
||||
ScrollOrientation.RIGHT_TO_LEFT, ScrollOrientation.LEFT_TO_RIGHT -> {
|
||||
mFlingOrientation = when {
|
||||
velocityX > mPagerFlingVelocity -> FlingOrientation.RIGHT_TO_LEFT
|
||||
velocityX < -mPagerFlingVelocity -> FlingOrientation.LEFT_TO_RIGHT
|
||||
else -> FlingOrientation.NONE
|
||||
}
|
||||
if (mScrollOffset in 1 until width * (itemCount - 1)) { //边界不需要滚动
|
||||
mFixScrolling = true
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
mFlingOrientation = when {
|
||||
velocityY > mPagerFlingVelocity -> FlingOrientation.BOTTOM_TO_TOP
|
||||
velocityY < -mPagerFlingVelocity -> FlingOrientation.TOP_TO_BOTTOM
|
||||
else -> FlingOrientation.NONE
|
||||
}
|
||||
if (mScrollOffset in 1 until width * (itemCount - 1)) { //边界不需要滚动
|
||||
mFixScrolling = true
|
||||
}
|
||||
}
|
||||
}
|
||||
calculateAndScrollToTarget(view)
|
||||
}
|
||||
else {
|
||||
Utils.log("$velocityX $velocityY")
|
||||
}
|
||||
return mPagerMode
|
||||
}
|
||||
}
|
||||
view.onFlingListener = mOnFlingListener
|
||||
|
||||
mOnScrollListener = object : OnScrollListener() {
|
||||
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
|
||||
if (newState == SCROLL_STATE_IDLE) {
|
||||
isFlinging = false
|
||||
if (!mFixScrolling) {
|
||||
mFixScrolling = true
|
||||
calculateAndScrollToTarget(view)
|
||||
} else {
|
||||
//表示此次 IDLE 是由修正位置结束触发的
|
||||
mFixScrolling = false
|
||||
}
|
||||
} else if (newState == SCROLL_STATE_DRAGGING) {
|
||||
mFixScrolling = false
|
||||
isFlinging = false
|
||||
} else if (newState == SCROLL_STATE_SETTLING) {
|
||||
isFlinging = true
|
||||
}
|
||||
}
|
||||
}
|
||||
view.addOnScrollListener(mOnScrollListener)
|
||||
}
|
||||
|
||||
override fun onDetachedFromWindow(view: RecyclerView?, recycler: RecyclerView.Recycler?) {
|
||||
super.onDetachedFromWindow(view, recycler)
|
||||
if (view?.onFlingListener == mOnFlingListener) {
|
||||
view.onFlingListener = null
|
||||
}
|
||||
view?.removeOnScrollListener(mOnScrollListener)
|
||||
}
|
||||
|
||||
override fun canScrollHorizontally(): Boolean {
|
||||
if (itemCount == 0) {
|
||||
return false
|
||||
}
|
||||
return when (mScrollOrientation) {
|
||||
ScrollOrientation.LEFT_TO_RIGHT, ScrollOrientation.RIGHT_TO_LEFT -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
override fun canScrollVertically(): Boolean {
|
||||
if (itemCount == 0) {
|
||||
return false
|
||||
}
|
||||
return when (mScrollOrientation) {
|
||||
ScrollOrientation.TOP_TO_BOTTOM, ScrollOrientation.BOTTOM_TO_TOP -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
override fun onLayoutChildren(recycler: RecyclerView.Recycler, state: State) {
|
||||
|
||||
mLayout?.requestLayout()
|
||||
|
||||
removeAndRecycleAllViews(recycler)
|
||||
|
||||
if (itemCount > 0) {
|
||||
mScrollOffset = getValidOffset(mScrollOffset)
|
||||
loadItemView(recycler)
|
||||
}
|
||||
}
|
||||
|
||||
override fun scrollHorizontallyBy(dx: Int, recycler: RecyclerView.Recycler, state: State): Int {
|
||||
return handleScrollBy(dx, recycler)
|
||||
}
|
||||
|
||||
override fun scrollVerticallyBy(dy: Int, recycler: RecyclerView.Recycler, state: State?): Int {
|
||||
return handleScrollBy(dy, recycler)
|
||||
}
|
||||
|
||||
override fun scrollToPosition(position: Int) {
|
||||
if (position < 0 || position >= itemCount) {
|
||||
throw ArrayIndexOutOfBoundsException("$position is out of bound [0..$itemCount-1]")
|
||||
}
|
||||
if (position < 0) {
|
||||
throw ArrayIndexOutOfBoundsException("$position is out of bound [0..$itemCount-1]")
|
||||
}
|
||||
mScrollOffset = getPositionOffset(position)
|
||||
requestLayout()
|
||||
}
|
||||
|
||||
override fun smoothScrollToPosition(recyclerView: RecyclerView, state: State?, position: Int) {
|
||||
if (position < 0 || position >= itemCount) {
|
||||
throw ArrayIndexOutOfBoundsException("$position is out of bound [0..$itemCount-1]")
|
||||
}
|
||||
if (position < 0) {
|
||||
throw ArrayIndexOutOfBoundsException("$position is out of bound [0..$itemCount-1]")
|
||||
}
|
||||
mFixScrolling = true
|
||||
scrollToCenter(position, recyclerView, true)
|
||||
}
|
||||
|
||||
private fun updatePositionRecordAndNotify(position: Int) {
|
||||
if (itemChangedListener == null) {
|
||||
return
|
||||
}
|
||||
if (position != itemPosition) {
|
||||
isItemPositionChanged = true
|
||||
itemPosition = position
|
||||
itemChangedListener?.onItemChanged(itemPosition)
|
||||
} else {
|
||||
isItemPositionChanged = false
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleScrollBy(offset: Int, recycler: RecyclerView.Recycler): Int {
|
||||
//期望值,不得超过最大最小值,所以期望值不一定等于实际值
|
||||
val expectOffset = mScrollOffset + offset
|
||||
//实际值
|
||||
mScrollOffset = getValidOffset(expectOffset)
|
||||
|
||||
//实际偏移,超过最大最小值之后的偏移都应该是0,该值作为返回值,否则在极限位置进行滚动的时候不会出现弹性阴影
|
||||
val exactMove = mScrollOffset - expectOffset + offset
|
||||
|
||||
if (exactMove == 0) {
|
||||
//itemViews 位置都不会改变,直接 return
|
||||
return 0
|
||||
}
|
||||
|
||||
detachAndScrapAttachedViews(recycler)
|
||||
|
||||
loadItemView(recycler)
|
||||
|
||||
return offset
|
||||
}
|
||||
|
||||
private fun loadItemView(recycler: RecyclerView.Recycler) {
|
||||
val firstVisiblePosition = getFirstVisibleItemPosition()
|
||||
val lastVisiblePosition = getLastVisibleItemPosition()
|
||||
|
||||
//位移百分比
|
||||
val movePercent = getFirstVisibleItemMovePercent()
|
||||
|
||||
for (i in lastVisiblePosition downTo firstVisiblePosition) {
|
||||
val view = recycler.getViewForPosition(i)
|
||||
view.layoutParams = view.layoutParams.apply { width = DisplayUtils.getScreenWidth() - 50F.dip2px() }
|
||||
//添加到recycleView 中
|
||||
addView(view)
|
||||
//测量
|
||||
measureChild(view, 0, 0)
|
||||
//布局
|
||||
mLayout?.doLayout(this, mScrollOffset, movePercent, view, i - firstVisiblePosition)
|
||||
//做动画
|
||||
mAnimation?.doAnimation(movePercent, view, i - firstVisiblePosition)
|
||||
}
|
||||
|
||||
//尝试更新当前item的位置并通知外界
|
||||
updatePositionRecordAndNotify(firstVisiblePosition)
|
||||
|
||||
//重用
|
||||
if (firstVisiblePosition - 1 >= 0) {
|
||||
val view = recycler.getViewForPosition(firstVisiblePosition - 1)
|
||||
resetViewAnimateProperty(view)
|
||||
removeAndRecycleView(view, recycler)
|
||||
}
|
||||
if (lastVisiblePosition + 1 < itemCount) {
|
||||
val view = recycler.getViewForPosition(lastVisiblePosition + 1)
|
||||
resetViewAnimateProperty(view)
|
||||
removeAndRecycleView(view, recycler)
|
||||
}
|
||||
}
|
||||
|
||||
private fun resetViewAnimateProperty(view: View) {
|
||||
view.rotationY = 0f
|
||||
view.rotationX = 0f
|
||||
view.scaleX = 1f
|
||||
view.scaleY = 1f
|
||||
view.alpha = 1f
|
||||
}
|
||||
|
||||
private fun calculateAndScrollToTarget(view: RecyclerView) {
|
||||
val targetPosition = calculateCenterPosition(getFirstVisibleItemPosition())
|
||||
scrollToCenter(targetPosition, view, true)
|
||||
}
|
||||
|
||||
private fun scrollToCenter(targetPosition: Int, recyclerView: RecyclerView, animation: Boolean) {
|
||||
val targetOffset = getPositionOffset(targetPosition)
|
||||
when(mScrollOrientation) {
|
||||
ScrollOrientation.LEFT_TO_RIGHT, ScrollOrientation.RIGHT_TO_LEFT -> {
|
||||
if (animation) {
|
||||
recyclerView.smoothScrollBy(targetOffset - mScrollOffset, 0)
|
||||
} else {
|
||||
recyclerView.scrollBy(targetOffset - mScrollOffset, 0)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
if (animation) {
|
||||
recyclerView.smoothScrollBy(0, targetOffset - mScrollOffset)
|
||||
} else {
|
||||
recyclerView.scrollBy(0, targetOffset - mScrollOffset)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getValidOffset(expectOffset: Int): Int {
|
||||
val offset = (width * (itemCount - 1)).coerceAtMost(expectOffset).coerceAtLeast(0)
|
||||
return when(mScrollOrientation) {
|
||||
ScrollOrientation.RIGHT_TO_LEFT, ScrollOrientation.LEFT_TO_RIGHT -> offset
|
||||
else -> (height * (itemCount - 1)).coerceAtMost(expectOffset).coerceAtLeast(0)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getPositionOffset(position: Int): Int {
|
||||
return when(mScrollOrientation) {
|
||||
ScrollOrientation.RIGHT_TO_LEFT -> position * width
|
||||
ScrollOrientation.LEFT_TO_RIGHT -> (itemCount - 1 - position) * width
|
||||
ScrollOrientation.BOTTOM_TO_TOP -> position * height
|
||||
ScrollOrientation.TOP_TO_BOTTOM -> (itemCount - 1 - position) * height
|
||||
}
|
||||
}
|
||||
|
||||
private fun getLastVisibleItemPosition(): Int {
|
||||
val firstVisiblePosition = getFirstVisibleItemPosition()
|
||||
return if (firstVisiblePosition + mVisibleItemCount > itemCount - 1) {
|
||||
itemCount - 1
|
||||
} else {
|
||||
firstVisiblePosition + mVisibleItemCount
|
||||
}
|
||||
}
|
||||
|
||||
private fun getFirstVisibleItemMovePercent(): Float {
|
||||
if (width == 0 || height == 0) {
|
||||
return 0f
|
||||
}
|
||||
return when (mScrollOrientation) {
|
||||
ScrollOrientation.RIGHT_TO_LEFT -> (mScrollOffset % width) * 1.0f / width
|
||||
ScrollOrientation.LEFT_TO_RIGHT -> {
|
||||
val targetPercent = 1 - (mScrollOffset % width) * 1.0f / width
|
||||
return if (targetPercent == 1f) {
|
||||
0f
|
||||
} else {
|
||||
targetPercent
|
||||
}
|
||||
}
|
||||
ScrollOrientation.BOTTOM_TO_TOP -> (mScrollOffset % height) * 1.0f / height
|
||||
ScrollOrientation.TOP_TO_BOTTOM -> {
|
||||
val targetPercent = 1 - (mScrollOffset % height) * 1.0f / height
|
||||
return if (targetPercent == 1f) {
|
||||
0f
|
||||
} else {
|
||||
targetPercent
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun calculateCenterPosition(position: Int): Int {
|
||||
//当是 Fling 触发的时候
|
||||
val triggerOrientation = mFlingOrientation
|
||||
mFlingOrientation = FlingOrientation.NONE
|
||||
when(mScrollOrientation) {
|
||||
ScrollOrientation.RIGHT_TO_LEFT -> {
|
||||
if (triggerOrientation == FlingOrientation.RIGHT_TO_LEFT) {
|
||||
return position + 1
|
||||
} else if (triggerOrientation == FlingOrientation.LEFT_TO_RIGHT) {
|
||||
return position
|
||||
}
|
||||
}
|
||||
ScrollOrientation.LEFT_TO_RIGHT -> {
|
||||
if (triggerOrientation == FlingOrientation.LEFT_TO_RIGHT) {
|
||||
return position + 1
|
||||
} else if (triggerOrientation == FlingOrientation.RIGHT_TO_LEFT) {
|
||||
return position
|
||||
}
|
||||
}
|
||||
ScrollOrientation.BOTTOM_TO_TOP -> {
|
||||
if (triggerOrientation == FlingOrientation.BOTTOM_TO_TOP) {
|
||||
return position + 1
|
||||
} else if (triggerOrientation == FlingOrientation.TOP_TO_BOTTOM) {
|
||||
return position
|
||||
}
|
||||
}
|
||||
ScrollOrientation.TOP_TO_BOTTOM -> {
|
||||
if (triggerOrientation == FlingOrientation.TOP_TO_BOTTOM) {
|
||||
return position + 1
|
||||
} else if (triggerOrientation == FlingOrientation.BOTTOM_TO_TOP) {
|
||||
return position
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//当不是 fling 触发的时候
|
||||
val percent = getFirstVisibleItemMovePercent()
|
||||
//向左移动超过50% position(firstVisibleItemPosition)++
|
||||
//否 position不变
|
||||
return if (percent < 0.5) {
|
||||
position
|
||||
} else {
|
||||
position + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,51 @@
|
||||
package com.gh.gamecenter.gamecollection.square
|
||||
|
||||
import android.content.Context
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.gh.gamecenter.databinding.GameCollectionAmwayContentItemBinding
|
||||
import com.gh.gamecenter.entity.AmwayCommentEntity
|
||||
import com.lightgame.adapter.BaseRecyclerAdapter
|
||||
|
||||
class GameCollectionAmwayAdapter(context: Context) :
|
||||
BaseRecyclerAdapter<RecyclerView.ViewHolder>(context) {
|
||||
|
||||
private var mAmwayList = ArrayList<AmwayCommentEntity>()
|
||||
|
||||
fun setAmwayList(amwayList: ArrayList<AmwayCommentEntity>) {
|
||||
mAmwayList = amwayList
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
|
||||
GameCollectionAmwayContentItemViewHolder(GameCollectionAmwayContentItemBinding.inflate(mLayoutInflater, parent, false))
|
||||
|
||||
override fun getItemCount(): Int = if (getRealCount() > 1) getRealCount() + INCREASE_COUNT else getRealCount()
|
||||
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||
if (holder is GameCollectionAmwayContentItemViewHolder) {
|
||||
holder.binding.entity = mAmwayList[getRealPosition(position)]
|
||||
}
|
||||
}
|
||||
|
||||
class GameCollectionAmwayContentItemViewHolder(var binding: GameCollectionAmwayContentItemBinding) :
|
||||
RecyclerView.ViewHolder(binding.root)
|
||||
|
||||
fun getRealCount(): Int = mAmwayList.size
|
||||
|
||||
fun getRealPosition(position: Int) = when (position) {
|
||||
0 -> {
|
||||
getRealCount() - 1
|
||||
}
|
||||
getRealCount() + 1 -> {
|
||||
0
|
||||
}
|
||||
else -> {
|
||||
position - 1
|
||||
}
|
||||
}
|
||||
|
||||
companion object{
|
||||
const val INCREASE_COUNT = 2
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,111 @@
|
||||
package com.gh.gamecenter.gamecollection.square
|
||||
|
||||
import android.animation.Animator
|
||||
import android.animation.TimeInterpolator
|
||||
import android.animation.ValueAnimator
|
||||
import android.view.animation.AccelerateDecelerateInterpolator
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
import com.gh.common.util.DirectUtils
|
||||
import com.gh.gamecenter.databinding.GameCollectionSquareAmwayItemBinding
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
class GameCollectionAmwayViewHolder(var binding: GameCollectionSquareAmwayItemBinding) : RecyclerView.ViewHolder(binding.root) {
|
||||
val mAdapter = GameCollectionAmwayAdapter(binding.root.context).apply { registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
|
||||
override fun onChanged() {
|
||||
if (itemCount <= 1) {
|
||||
stop()
|
||||
} else {
|
||||
start()
|
||||
}
|
||||
}
|
||||
}) }
|
||||
val mLoopTask = object : Runnable {
|
||||
private val reference: WeakReference<ViewPager2> = WeakReference(binding.amwayVp)
|
||||
override fun run() {
|
||||
val vp: ViewPager2? = reference.get()
|
||||
if (vp != null) {
|
||||
val count = mAdapter.itemCount
|
||||
if (count == 0) return
|
||||
val next = (vp.currentItem + 1) % count
|
||||
vp.setCurrentItem(next, 1000)
|
||||
vp.postDelayed(this, GameCollectionSquareAdapter.AMWAY_LOOP_TIME)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun bindAmway() {
|
||||
binding.amwayVp.run {
|
||||
if (adapter is GameCollectionAmwayAdapter) return
|
||||
isUserInputEnabled = false
|
||||
adapter = mAdapter
|
||||
orientation = ViewPager2.ORIENTATION_VERTICAL
|
||||
registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
|
||||
private var mTempPosition = GameCollectionSquareAdapter.INVALID_VALUE
|
||||
private var isScrolled = false
|
||||
|
||||
override fun onPageSelected(position: Int) {
|
||||
if (isScrolled) {
|
||||
mTempPosition = position
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPageScrollStateChanged(state: Int) {
|
||||
//手势滑动中,代码执行滑动中
|
||||
if (state == ViewPager2.SCROLL_STATE_DRAGGING || state == ViewPager2.SCROLL_STATE_SETTLING) {
|
||||
isScrolled = true
|
||||
} else if (state == ViewPager2.SCROLL_STATE_IDLE) {
|
||||
//滑动闲置或滑动结束
|
||||
isScrolled = false
|
||||
if (mTempPosition != GameCollectionSquareAdapter.INVALID_VALUE) {
|
||||
if (mTempPosition == 0) {
|
||||
setCurrentItem(mAdapter.getRealCount(), false)
|
||||
} else if (mTempPosition == mAdapter.itemCount - 1) {
|
||||
setCurrentItem(1, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
setCurrentItem(1, false)
|
||||
postDelayed(mLoopTask, GameCollectionSquareAdapter.AMWAY_LOOP_TIME)
|
||||
}
|
||||
binding.root.setOnClickListener { DirectUtils.directToAmway(binding.root.context) }
|
||||
}
|
||||
|
||||
fun start() {
|
||||
stop()
|
||||
binding.amwayVp.postDelayed(mLoopTask, GameCollectionSquareAdapter.AMWAY_LOOP_TIME)
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
binding.amwayVp.removeCallbacks(mLoopTask)
|
||||
}
|
||||
|
||||
fun ViewPager2.setCurrentItem(
|
||||
item: Int,
|
||||
duration: Long,
|
||||
interpolator: TimeInterpolator = AccelerateDecelerateInterpolator(),
|
||||
pagePxHeight: Int = height
|
||||
) {
|
||||
val pxToDrag: Int = pagePxHeight * (item - currentItem)
|
||||
val animator = ValueAnimator.ofInt(0, pxToDrag)
|
||||
var previousValue = 0
|
||||
animator.addUpdateListener { valueAnimator ->
|
||||
val currentValue = valueAnimator.animatedValue as Int
|
||||
val currentPxToDrag = (currentValue - previousValue).toFloat()
|
||||
fakeDragBy(-currentPxToDrag)
|
||||
previousValue = currentValue
|
||||
}
|
||||
animator.addListener(object : Animator.AnimatorListener {
|
||||
override fun onAnimationStart(animation: Animator?) { beginFakeDrag() }
|
||||
override fun onAnimationEnd(animation: Animator?) { endFakeDrag() }
|
||||
override fun onAnimationCancel(animation: Animator?) { }
|
||||
override fun onAnimationRepeat(animation: Animator?) { }
|
||||
})
|
||||
animator.interpolator = interpolator
|
||||
animator.duration = duration
|
||||
animator.start()
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
package com.gh.gamecenter.gamecollection.square
|
||||
|
||||
import android.os.Bundle
|
||||
import com.gh.base.BaseActivity
|
||||
import com.gh.common.util.DisplayUtils
|
||||
import com.gh.gamecenter.R
|
||||
|
||||
class GameCollectionSquareActivity : BaseActivity() {
|
||||
override fun getLayoutId(): Int {
|
||||
return R.layout.activity_amway
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
DisplayUtils.transparentStatusBar(this)
|
||||
|
||||
val containerFragment = supportFragmentManager.findFragmentByTag(
|
||||
GameCollectionSquareFragment::class.java.simpleName)
|
||||
?: GameCollectionSquareFragment().with(intent.extras)
|
||||
supportFragmentManager.beginTransaction().replace(R.id.placeholder, containerFragment, GameCollectionSquareFragment::class.java.simpleName).commitAllowingStateLoss()
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,128 @@
|
||||
package com.gh.gamecenter.gamecollection.square
|
||||
|
||||
import android.content.Context
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.gh.common.constant.ItemViewType
|
||||
import com.gh.common.exposure.ExposureEvent
|
||||
import com.gh.common.exposure.ExposureSource
|
||||
import com.gh.common.exposure.IExposable
|
||||
import com.gh.common.util.DisplayUtils
|
||||
import com.gh.common.util.toDrawable
|
||||
import com.gh.gamecenter.R
|
||||
import com.gh.gamecenter.adapter.viewholder.FooterViewHolder
|
||||
import com.gh.gamecenter.baselist.ListAdapter
|
||||
import com.gh.gamecenter.databinding.GameCollectionSquareAmwayItemBinding
|
||||
import com.gh.gamecenter.databinding.GameCollectionSquareItemBinding
|
||||
import com.gh.gamecenter.entity.GamesCollectionEntity
|
||||
|
||||
class GameCollectionSquareAdapter(
|
||||
context: Context,
|
||||
private val isHome: Boolean = false,
|
||||
private val mViewModel: GameCollectionSquareViewModel,
|
||||
private var basicExposureSource: List<ExposureSource>
|
||||
) :
|
||||
ListAdapter<GamesCollectionEntity>(context), IExposable {
|
||||
|
||||
override fun getItemViewType(position: Int): Int {
|
||||
return if (isHome && position == 0) ItemViewType.ITEM_HEADER else if (position == itemCount - 1) ItemViewType.ITEM_FOOTER else ItemViewType.ITEM_BODY
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
return when (viewType) {
|
||||
ItemViewType.ITEM_HEADER -> {
|
||||
GameCollectionAmwayViewHolder(
|
||||
GameCollectionSquareAmwayItemBinding.bind(
|
||||
mLayoutInflater.inflate(
|
||||
R.layout.game_collection_square_amway_item,
|
||||
parent,
|
||||
false
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
ItemViewType.ITEM_BODY -> {
|
||||
GameCollectionSquareItemViewHolder(
|
||||
GameCollectionSquareItemBinding.bind(
|
||||
mLayoutInflater.inflate(
|
||||
R.layout.game_collection_square_item,
|
||||
parent,
|
||||
false
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
ItemViewType.ITEM_FOOTER -> FooterViewHolder(
|
||||
mLayoutInflater.inflate(
|
||||
R.layout.refresh_footerview,
|
||||
parent,
|
||||
false
|
||||
)
|
||||
)
|
||||
|
||||
else -> GameCollectionSquareItemViewHolder(
|
||||
GameCollectionSquareItemBinding.bind(
|
||||
mLayoutInflater.inflate(
|
||||
R.layout.game_collection_square_item,
|
||||
parent,
|
||||
false
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||
when (holder) {
|
||||
is GameCollectionAmwayViewHolder -> {
|
||||
holder.bindAmway()
|
||||
}
|
||||
is GameCollectionSquareItemViewHolder -> {
|
||||
val realPosition = if (isHome) position - 1 else position
|
||||
holder.bindGameCollection(mEntityList[realPosition])
|
||||
}
|
||||
is FooterViewHolder -> {
|
||||
holder.initItemPadding()
|
||||
holder.initFooterViewHolder(mViewModel, mIsLoading, mIsNetworkError, mIsOver)
|
||||
holder.hint.setTextColor(ContextCompat.getColor(mContext, R.color.text_B3B3B3))
|
||||
val lp = holder.itemView.layoutParams as RecyclerView.LayoutParams
|
||||
lp.height = DisplayUtils.dip2px(48F)
|
||||
lp.width = ViewGroup.LayoutParams.MATCH_PARENT
|
||||
holder.itemView.layoutParams = lp
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount() = if (mEntityList.isNullOrEmpty()) 0 else if (isHome) mEntityList.size + 2 else mEntityList.size + 1
|
||||
|
||||
class GameCollectionSquareItemViewHolder(var binding: GameCollectionSquareItemBinding) :
|
||||
RecyclerView.ViewHolder(binding.root) {
|
||||
fun bindGameCollection(gamesCollectionEntity: GamesCollectionEntity) {
|
||||
binding.run {
|
||||
entity = gamesCollectionEntity
|
||||
stampIv.setImageDrawable(if (gamesCollectionEntity.stamp == "offical") R.drawable.ic_official.toDrawable() else R.drawable.ic_chosen.toDrawable())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val INVALID_VALUE = -1
|
||||
|
||||
// 安利墙卡片轮播时间
|
||||
const val AMWAY_LOOP_TIME = 5000L
|
||||
}
|
||||
|
||||
override fun getEventByPosition(pos: Int): ExposureEvent? {
|
||||
// return mEntityList[pos].exposureEvent
|
||||
return null
|
||||
}
|
||||
|
||||
override fun getEventListByPosition(pos: Int): List<ExposureEvent>? {
|
||||
// return mEntityList[pos].exposureEventList
|
||||
return null
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,313 @@
|
||||
package com.gh.gamecenter.gamecollection.square
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.gh.common.TimeElapsedHelper
|
||||
import com.gh.common.exposure.ExposureListener
|
||||
import com.gh.common.exposure.ExposureSource
|
||||
import com.gh.common.util.*
|
||||
import com.gh.common.view.VerticalItemDecoration
|
||||
import com.gh.download.DownloadManager
|
||||
import com.gh.gamecenter.R
|
||||
import com.gh.gamecenter.baselist.LazyListFragment
|
||||
import com.gh.gamecenter.baselist.ListAdapter
|
||||
import com.gh.gamecenter.databinding.FragmentGameCollectionSquareAlBinding
|
||||
import com.gh.gamecenter.databinding.FragmentGameCollectionSquareBinding
|
||||
import com.gh.gamecenter.entity.GamesCollectionEntity
|
||||
import com.gh.gamecenter.eventbus.EBDownloadStatus
|
||||
import com.gh.gamecenter.eventbus.EBPackage
|
||||
import com.gh.gamecenter.eventbus.EBReuse
|
||||
import com.gh.gamecenter.gamecollection.publish.GameCollectionEditActivity
|
||||
import com.gh.gamecenter.gamecollection.tag.GameCollectionTagSelectActivity
|
||||
import com.gh.gamecenter.personal.PersonalFragment
|
||||
import com.google.android.material.appbar.AppBarLayout
|
||||
import com.lightgame.download.DataWatcher
|
||||
import com.lightgame.download.DownloadEntity
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
import kotlin.math.abs
|
||||
|
||||
class GameCollectionSquareFragment : LazyListFragment<GamesCollectionEntity, GameCollectionSquareViewModel>() {
|
||||
private lateinit var mViewModel: GameCollectionSquareViewModel
|
||||
private val mElapsedHelper by lazy { TimeElapsedHelper() }
|
||||
private lateinit var mExposureListener: ExposureListener
|
||||
|
||||
private lateinit var mDefaultBinding: FragmentGameCollectionSquareBinding
|
||||
private lateinit var mAlternativeBinding: FragmentGameCollectionSquareAlBinding
|
||||
|
||||
private var mAdapter: GameCollectionSquareAdapter? = null
|
||||
|
||||
private var mUseAlternativeLayout = false
|
||||
|
||||
private val dataWatcher = object : DataWatcher() {
|
||||
override fun onDataChanged(downloadEntity: DownloadEntity) {
|
||||
// mAdapter?.notifyItemByDownload(downloadEntity)
|
||||
|
||||
// if (downloadEntity.meta[XapkInstaller.XAPK_UNZIP_STATUS] == XapkUnzipStatus.FAILURE.name) {
|
||||
// showUnzipFailureDialog(downloadEntity)
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
mUseAlternativeLayout = arguments?.getBoolean(EntranceUtils.KEY_IS_HOME, false) ?: false
|
||||
super.onCreate(savedInstanceState)
|
||||
}
|
||||
|
||||
override fun getLayoutId() = R.layout.fragment_stub
|
||||
|
||||
override fun getRealLayoutId(): Int {
|
||||
return if (mUseAlternativeLayout) R.layout.fragment_game_collection_square_al else R.layout.fragment_game_collection_square
|
||||
}
|
||||
|
||||
override fun onFragmentFirstVisible() {
|
||||
super.onFragmentFirstVisible()
|
||||
mViewModel.initData()
|
||||
mViewModel.entrance = mEntrance
|
||||
}
|
||||
|
||||
override fun onRealLayoutInflated(inflatedView: View) {
|
||||
super.onRealLayoutInflated(inflatedView)
|
||||
if (mUseAlternativeLayout) {
|
||||
mAlternativeBinding = FragmentGameCollectionSquareAlBinding.bind(inflatedView)
|
||||
} else {
|
||||
mDefaultBinding = FragmentGameCollectionSquareBinding.bind(inflatedView)
|
||||
}
|
||||
}
|
||||
|
||||
override fun initRealView() {
|
||||
super.initRealView()
|
||||
|
||||
mExposureListener = ExposureListener(this, mAdapter!!)
|
||||
mListRv.addOnScrollListener(mExposureListener)
|
||||
|
||||
if (!mUseAlternativeLayout) {
|
||||
initDefaultLayout()
|
||||
} else {
|
||||
initAlternativeLayout()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
if (requestCode == REQUEST_SELECT_TAG) {
|
||||
val tagName = data?.getStringExtra("tagName")
|
||||
mDefaultBinding.tagTv.text = tagName ?: "标签筛选"
|
||||
}
|
||||
}
|
||||
|
||||
override fun provideListViewModel(): GameCollectionSquareViewModel {
|
||||
mViewModel = viewModelProvider()
|
||||
return mViewModel
|
||||
}
|
||||
|
||||
override fun getItemDecoration() = VerticalItemDecoration(context, 16F, false, R.color.white)
|
||||
|
||||
override fun provideListAdapter(): ListAdapter<*> {
|
||||
if (mAdapter == null) {
|
||||
val basicExposureSource = arrayListOf<ExposureSource>().apply {
|
||||
arguments?.getParcelable<ExposureSource>(EntranceUtils.KEY_EXPOSURE_SOURCE)?.let {
|
||||
add(it)
|
||||
}
|
||||
add(ExposureSource("游戏单广场", ""))
|
||||
}
|
||||
mAdapter = GameCollectionSquareAdapter(requireContext(), mUseAlternativeLayout, mViewModel, basicExposureSource)
|
||||
}
|
||||
return mAdapter!!
|
||||
}
|
||||
|
||||
override fun isAutomaticLoad() = false
|
||||
|
||||
private fun initDefaultLayout() {
|
||||
// toolbar 消费 fitsSystemWindows 避免在 collapsingToolbar 下面出现多出来的 padding
|
||||
// [https://stackoverflow.com/questions/48137666/viewgroup-inside-collapsingtoolbarlayout-show-extra-bottom-padding-when-set-fits]
|
||||
ViewCompat.setOnApplyWindowInsetsListener(mDefaultBinding.appbar) { _, insets ->
|
||||
(mDefaultBinding.toolbar.layoutParams as ViewGroup.MarginLayoutParams).topMargin = insets.systemWindowInsetTop
|
||||
insets.consumeSystemWindowInsets()
|
||||
}
|
||||
|
||||
val collapsingTrigger = 66F.dip2px() + DisplayUtils.getStatusBarHeight(context?.resources)
|
||||
|
||||
mDefaultBinding.toolbar.setNavigationOnClickListener { requireActivity().finish() }
|
||||
|
||||
mDefaultBinding.collapsingToolbar.scrimVisibleHeightTrigger = collapsingTrigger
|
||||
mDefaultBinding.collapsingToolbar.scrimShownAction = {
|
||||
DisplayUtils.setLightStatusBar(requireActivity(), it)
|
||||
if (it) {
|
||||
mDefaultBinding.titleTv.alpha = 1F
|
||||
mDefaultBinding.titleTv.visibility = View.VISIBLE
|
||||
mDefaultBinding.titleTv.setTextColor(R.color.black.toColor())
|
||||
mDefaultBinding.toolbar.navigationIcon = R.drawable.ic_bar_back.toDrawable()
|
||||
} else {
|
||||
mDefaultBinding.titleTv.visibility = View.GONE
|
||||
mDefaultBinding.toolbar.navigationIcon = R.drawable.ic_bar_back_light.toDrawable()
|
||||
}
|
||||
}
|
||||
|
||||
mDefaultBinding.titleTv.setOnClickListener {
|
||||
if (ClickUtils.isFastDoubleClick(mDefaultBinding.titleTv.id, 300)) {
|
||||
scrollToTop()
|
||||
}
|
||||
}
|
||||
|
||||
mDefaultBinding.appbar.addOnOffsetChangedListener(AppBarLayout.OnOffsetChangedListener { _, verticalOffset ->
|
||||
val absOffset = abs(verticalOffset)
|
||||
val invisibleOffset = DisplayUtils.dip2px(30F)
|
||||
|
||||
if (absOffset <= invisibleOffset) {
|
||||
mDefaultBinding.titleTv.alpha = 1 - (absOffset.toFloat() / invisibleOffset)
|
||||
} else {
|
||||
mDefaultBinding.titleTv.alpha = 0F
|
||||
}
|
||||
|
||||
mListRefresh?.isEnabled = absOffset <= 2
|
||||
})
|
||||
|
||||
mDefaultBinding.orderRg.setOnCheckedChangeListener { _, checkedId ->
|
||||
if (checkedId == R.id.hotRb) {
|
||||
// 热门排序
|
||||
// TODO 热门排序
|
||||
} else {
|
||||
// 最新排序
|
||||
// TODO 最新排序
|
||||
}
|
||||
}
|
||||
|
||||
mDefaultBinding.fab.setOnClickListener {
|
||||
// 创建游戏单
|
||||
ifLogin(mEntrance) {
|
||||
showRegulationTestDialogIfNeeded {
|
||||
startActivity(GameCollectionEditActivity.getIntent(requireContext()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mDefaultBinding.tagFilter.setOnClickListener {
|
||||
startActivityForResult(GameCollectionTagSelectActivity.getIntent(requireContext(), true), REQUEST_SELECT_TAG)
|
||||
}
|
||||
|
||||
mListRefresh?.setProgressViewOffset(false, 0, 118F.dip2px() + DisplayUtils.getStatusBarHeight(requireContext().resources))
|
||||
// mSkeletonScreen = Skeleton.bind(mDefaultBinding?.skeletonPlaceholder).shimmer(false).load(R.layout.fragment_amway_skeleton).show()
|
||||
}
|
||||
|
||||
private fun initAlternativeLayout() {
|
||||
mAlternativeBinding.fab.setOnClickListener {
|
||||
// 创建游戏单
|
||||
ifLogin(mEntrance) {
|
||||
showRegulationTestDialogIfNeeded {
|
||||
startActivity(GameCollectionEditActivity.getIntent(requireContext()))
|
||||
}
|
||||
}
|
||||
}
|
||||
// mAlternativeBinding.listRv.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||
// override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||
// super.onScrolled(recyclerView, dx, dy)
|
||||
// val scrollY = recyclerView.computeVerticalScrollOffset()
|
||||
// }
|
||||
// })
|
||||
|
||||
// mSkeletonScreen = Skeleton.bind(mAlternativeBinding?.skeletonPlaceholder).shimmer(false).load(
|
||||
// R.layout.fragment_amway_skeleton_al).show()
|
||||
}
|
||||
|
||||
override fun onFragmentPause() {
|
||||
super.onPause()
|
||||
DownloadManager.getInstance(context).removeObserver(dataWatcher)
|
||||
|
||||
// mElapsedHelper.pauseCounting()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
// if (isEverPause) mAdapter?.notifyDataSetChanged()
|
||||
super.onResume()
|
||||
}
|
||||
|
||||
override fun onFragmentResume() {
|
||||
super.onFragmentResume()
|
||||
DownloadManager.getInstance(context).addObserver(dataWatcher)
|
||||
|
||||
// mElapsedHelper.resetCounting()
|
||||
// mElapsedHelper.resumeCounting()
|
||||
}
|
||||
|
||||
override fun onLoadEmpty() {
|
||||
super.onLoadEmpty()
|
||||
if (!mUseAlternativeLayout) mDefaultBinding.tagFilterContainer.visibility = View.GONE
|
||||
}
|
||||
|
||||
override fun onLoadDone() {
|
||||
super.onLoadDone()
|
||||
if (!mUseAlternativeLayout) mDefaultBinding.tagFilterContainer.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
override fun onLoadError() {
|
||||
super.onLoadError()
|
||||
if (!mUseAlternativeLayout) mDefaultBinding.tagFilterContainer.visibility = View.GONE
|
||||
}
|
||||
|
||||
override fun onLoadRefresh() {
|
||||
super.onLoadRefresh()
|
||||
if (!mUseAlternativeLayout) mDefaultBinding.tagFilterContainer.visibility = View.GONE
|
||||
}
|
||||
|
||||
private fun scrollToTop() {
|
||||
val firstItemPosition = mLayoutManager.findFirstVisibleItemPosition()
|
||||
if (firstItemPosition >= 10) {
|
||||
mListRv.scrollToPosition(6)
|
||||
}
|
||||
mListRv.smoothScrollToPosition(0)
|
||||
mDefaultBinding.appbar.setExpanded(true)
|
||||
}
|
||||
|
||||
|
||||
//下载被删除事件
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onEventMainThread(status: EBDownloadStatus) {
|
||||
if ("delete" == status.status) {
|
||||
// mAdapter?.notifyItemAndRemoveDownload(status)
|
||||
}
|
||||
}
|
||||
|
||||
//安装、卸载事件
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onEventMainThread(busFour: EBPackage) {
|
||||
// val data = mAdapter?.getGameEntityByPackage(busFour.packageName) ?: arrayListOf()
|
||||
// for (gameAndPosition in data) {
|
||||
// mAdapter?.notifyChildItem(gameAndPosition.position)
|
||||
// }
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onEventMainThread(reuse: EBReuse) {
|
||||
if ("Refresh" == reuse.type) {
|
||||
// mAdapter?.notifyDataSetChanged()
|
||||
} else if (reuse.type == PersonalFragment.LOGIN_TAG) { // 登入
|
||||
scrollToTop()
|
||||
onRefresh()
|
||||
// mViewModel.initData(false)
|
||||
}
|
||||
}
|
||||
|
||||
fun showUnzipFailureDialog(downloadEntity: DownloadEntity) {
|
||||
// val data = mAdapter?.getGameEntityByPackage(downloadEntity.packageName) ?: return
|
||||
// for (gameAndPosition in data) {
|
||||
// val targetView = mLayoutManager.findViewByPosition(gameAndPosition.position)
|
||||
// if (targetView != null) {
|
||||
// if (targetView is RecyclerView) {
|
||||
// // todo 如果时竖向专题该怎么判断?
|
||||
// } else {
|
||||
// DialogUtils.showUnzipFailureDialog(requireContext(), downloadEntity)
|
||||
// return
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val REQUEST_SELECT_TAG = 100
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
package com.gh.gamecenter.gamecollection.square
|
||||
|
||||
import android.app.Application
|
||||
import com.gh.gamecenter.baselist.ListViewModel
|
||||
import com.gh.gamecenter.entity.GamesCollectionEntity
|
||||
import com.gh.gamecenter.retrofit.RetrofitManager
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Single
|
||||
|
||||
class GameCollectionSquareViewModel(application: Application) :
|
||||
ListViewModel<GamesCollectionEntity, GamesCollectionEntity>(application) {
|
||||
|
||||
var entrance: String? = null
|
||||
|
||||
init {
|
||||
initData()
|
||||
}
|
||||
|
||||
fun initData() {
|
||||
|
||||
}
|
||||
|
||||
// override fun provideDataSingle(page: Int): Single<MutableList<GamesCollectionEntity>> = RetrofitManager.getInstance(getApplication())
|
||||
// .api.getGameCollectionSquareList(page)
|
||||
|
||||
override fun mergeResultLiveData() {
|
||||
mResultLiveData.addSource(mListLiveData) { mResultLiveData.postValue(it) }
|
||||
}
|
||||
|
||||
override fun provideDataObservable(page: Int): Observable<MutableList<GamesCollectionEntity>>? = null
|
||||
}
|
||||
@ -0,0 +1,144 @@
|
||||
package com.gh.gamecenter.home.gamecollection
|
||||
|
||||
import android.view.View
|
||||
import com.gh.common.util.DisplayUtils
|
||||
import com.gh.common.view.stacklayoutmanager.StackAnimation
|
||||
import com.gh.common.view.stacklayoutmanager.StackLayoutManager
|
||||
import com.gh.gamecenter.R
|
||||
import com.halo.assistant.HaloApp
|
||||
import com.lightgame.utils.Utils
|
||||
import kotlin.math.pow
|
||||
|
||||
class GameCollectionStackAnimation(
|
||||
scrollOrientation: StackLayoutManager.ScrollOrientation,
|
||||
visibleCount: Int
|
||||
) : StackAnimation(scrollOrientation, visibleCount) {
|
||||
private var mScale = 0.9F
|
||||
private var mOutScale = 1.0F
|
||||
private var mOutRotation = 0
|
||||
|
||||
/**
|
||||
* 设置 item 缩放比例.
|
||||
* @param scale 缩放比例
|
||||
*/
|
||||
fun setItemScaleRate(scale: Float) {
|
||||
mScale = scale
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取item缩放比例.
|
||||
* @return item缩放比例
|
||||
*/
|
||||
fun getItemScaleRate(): Float {
|
||||
return mScale
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 itemView 离开屏幕时候的缩放比例.
|
||||
* @param scale 缩放比例
|
||||
*/
|
||||
fun setOutScale(scale: Float) {
|
||||
mOutScale = scale
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 itemView 离开屏幕时候的缩放比例.
|
||||
* @return 缩放比例
|
||||
*/
|
||||
fun getOutScale(): Float {
|
||||
return mOutScale
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 itemView 离开屏幕时候的旋转角度.
|
||||
* @param rotation 旋转角度
|
||||
*/
|
||||
fun setOutRotation(rotation: Int) {
|
||||
mOutRotation = rotation
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 itemView 离开屏幕时候的旋转角度
|
||||
* @return 旋转角度
|
||||
*/
|
||||
fun getOutRotation(): Int {
|
||||
return mOutRotation
|
||||
}
|
||||
|
||||
override fun doAnimation(firstMovePercent: Float, itemView: View, position: Int) {
|
||||
val cover = itemView.findViewById<View>(R.id.cover)
|
||||
val content = itemView.findViewById<View>(R.id.content)
|
||||
val scale: Float
|
||||
var alpha = 1.0F
|
||||
val rotation: Float
|
||||
if (position == 0) {
|
||||
// 顶层item透明度变化
|
||||
cover.alpha = 0F
|
||||
content.alpha = 1F
|
||||
if (firstMovePercent > 0.5) alpha = 1F - 2 * (firstMovePercent - 0.5F)
|
||||
scale = 1 - ((1 - mOutScale) * firstMovePercent)
|
||||
rotation = mOutRotation * firstMovePercent
|
||||
} else {
|
||||
val minScale = (mScale.toDouble().pow(position.toDouble())).toFloat()
|
||||
val maxScale = (mScale.toDouble().pow((position - 1).toDouble())).toFloat()
|
||||
scale = minScale + (maxScale - minScale) * firstMovePercent
|
||||
when (position) {
|
||||
1 -> {
|
||||
cover.alpha = 0.3F - 0.3F * firstMovePercent
|
||||
content.alpha = firstMovePercent
|
||||
}
|
||||
2 -> cover.alpha = 0.6F - 0.3F * firstMovePercent
|
||||
3 -> {
|
||||
cover.alpha = 1F - 0.4F * firstMovePercent
|
||||
// 如果不改变会出现残留边框
|
||||
// alpha = if (firstMovePercent == 0F) 0F else 1F
|
||||
}
|
||||
}
|
||||
rotation = 0F
|
||||
}
|
||||
|
||||
setItemPivotXY(mScrollOrientation, itemView)
|
||||
rotationFirstVisibleItem(mScrollOrientation, itemView, rotation)
|
||||
itemView.scaleX = scale
|
||||
itemView.scaleY = scale
|
||||
itemView.alpha = alpha
|
||||
}
|
||||
|
||||
private fun setItemPivotXY(
|
||||
scrollOrientation: StackLayoutManager.ScrollOrientation,
|
||||
view: View
|
||||
) {
|
||||
when (scrollOrientation) {
|
||||
StackLayoutManager.ScrollOrientation.RIGHT_TO_LEFT -> {
|
||||
view.pivotX = view.measuredWidth.toFloat()
|
||||
view.pivotY = view.measuredHeight.toFloat() / 2
|
||||
}
|
||||
StackLayoutManager.ScrollOrientation.LEFT_TO_RIGHT -> {
|
||||
view.pivotX = 0F
|
||||
view.pivotY = view.measuredHeight.toFloat() / 2
|
||||
}
|
||||
StackLayoutManager.ScrollOrientation.BOTTOM_TO_TOP -> {
|
||||
view.pivotX = view.measuredWidth.toFloat() / 2
|
||||
view.pivotY = view.measuredHeight.toFloat()
|
||||
}
|
||||
StackLayoutManager.ScrollOrientation.TOP_TO_BOTTOM -> {
|
||||
view.pivotX = view.measuredWidth.toFloat() / 2
|
||||
view.pivotY = 0F
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun rotationFirstVisibleItem(
|
||||
scrollOrientation: StackLayoutManager.ScrollOrientation,
|
||||
view: View,
|
||||
rotation: Float
|
||||
) {
|
||||
when (scrollOrientation) {
|
||||
StackLayoutManager.ScrollOrientation.RIGHT_TO_LEFT -> view.rotationY = rotation
|
||||
StackLayoutManager.ScrollOrientation.LEFT_TO_RIGHT -> view.rotationY = -rotation
|
||||
StackLayoutManager.ScrollOrientation.BOTTOM_TO_TOP -> view.rotationX = -rotation
|
||||
StackLayoutManager.ScrollOrientation.TOP_TO_BOTTOM -> view.rotationX = rotation
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,110 @@
|
||||
package com.gh.gamecenter.home.gamecollection
|
||||
|
||||
import android.view.View
|
||||
import com.gh.common.util.dip2px
|
||||
import com.gh.common.view.stacklayoutmanager.StackLayout
|
||||
import com.gh.common.view.stacklayoutmanager.StackLayoutManager
|
||||
|
||||
class GameCollectionStackLayout(
|
||||
scrollOrientation: StackLayoutManager.ScrollOrientation,
|
||||
visibleCount: Int,
|
||||
perItemOffset: Int
|
||||
) : StackLayout(scrollOrientation, visibleCount, perItemOffset) {
|
||||
|
||||
private var mHasMeasureItemSize = false
|
||||
private var mWidthSpace = 0
|
||||
private var mHeightSpace = 0
|
||||
private var mStartMargin = 0
|
||||
|
||||
private var mWidth = 0
|
||||
private var mHeight = 0
|
||||
private var mScrollOffset = 0
|
||||
|
||||
override fun doLayout(
|
||||
stackLayoutManager: StackLayoutManager,
|
||||
scrollOffset: Int,
|
||||
firstMovePercent: Float,
|
||||
itemView: View,
|
||||
position: Int
|
||||
) {
|
||||
mWidth = stackLayoutManager.width
|
||||
mHeight = stackLayoutManager.height
|
||||
mScrollOffset = scrollOffset
|
||||
if (!mHasMeasureItemSize) {
|
||||
mWidthSpace =
|
||||
mWidth - stackLayoutManager.getDecoratedMeasuredWidth(itemView) - 18F.dip2px()
|
||||
mHeightSpace = mHeight - stackLayoutManager.getDecoratedMeasuredHeight(itemView)
|
||||
mStartMargin = 16F.dip2px()
|
||||
mHasMeasureItemSize = true
|
||||
}
|
||||
val left: Int
|
||||
val top: Int
|
||||
if (position == 0) {
|
||||
left = getFirstVisibleItemLeft()
|
||||
top = getFirstVisibleItemTop()
|
||||
} else {
|
||||
left = getAfterFirstVisibleItemLeft(position, firstMovePercent)
|
||||
top = getAfterFirstVisibleItemTop(position, firstMovePercent)
|
||||
}
|
||||
|
||||
val right = left + stackLayoutManager.getDecoratedMeasuredWidth(itemView)
|
||||
val bottom = top + stackLayoutManager.getDecoratedMeasuredHeight(itemView)
|
||||
|
||||
stackLayoutManager.layoutDecorated(itemView, left, top, right, bottom)
|
||||
}
|
||||
|
||||
override fun requestLayout() {
|
||||
mHasMeasureItemSize = false //表示尺寸可能发生了改变
|
||||
}
|
||||
|
||||
private fun getFirstVisibleItemLeft(): Int {
|
||||
return when (mScrollOrientation) {
|
||||
StackLayoutManager.ScrollOrientation.RIGHT_TO_LEFT -> mStartMargin - mScrollOffset % mWidth
|
||||
StackLayoutManager.ScrollOrientation.LEFT_TO_RIGHT -> {
|
||||
return if (mScrollOffset % mWidth == 0) {
|
||||
mStartMargin
|
||||
} else {
|
||||
mStartMargin + (mWidth - mScrollOffset % mWidth)
|
||||
}
|
||||
}
|
||||
else -> mWidthSpace / 2
|
||||
}
|
||||
}
|
||||
|
||||
private fun getFirstVisibleItemTop(): Int {
|
||||
return when (mScrollOrientation) {
|
||||
StackLayoutManager.ScrollOrientation.BOTTOM_TO_TOP -> mStartMargin - mScrollOffset % mHeight
|
||||
StackLayoutManager.ScrollOrientation.TOP_TO_BOTTOM -> {
|
||||
return if (mScrollOffset % mHeight == 0) {
|
||||
mStartMargin
|
||||
} else {
|
||||
mStartMargin + (mHeight - mScrollOffset % mHeight)
|
||||
}
|
||||
}
|
||||
else -> mHeightSpace / 2
|
||||
}
|
||||
}
|
||||
|
||||
private fun getAfterFirstVisibleItemLeft(visiblePosition: Int, movePercent: Float): Int {
|
||||
return when (mScrollOrientation) {
|
||||
StackLayoutManager.ScrollOrientation.RIGHT_TO_LEFT -> (mStartMargin + mPerItemOffset * (visiblePosition - movePercent)).toInt()
|
||||
StackLayoutManager.ScrollOrientation.LEFT_TO_RIGHT -> (mStartMargin - mPerItemOffset * (visiblePosition - movePercent)).toInt()
|
||||
else -> mWidthSpace / 2
|
||||
}
|
||||
}
|
||||
|
||||
private fun getAfterFirstVisibleItemTop(visiblePosition: Int, movePercent: Float): Int {
|
||||
return when (mScrollOrientation) {
|
||||
StackLayoutManager.ScrollOrientation.BOTTOM_TO_TOP -> (mStartMargin + mPerItemOffset * (visiblePosition - movePercent)).toInt()
|
||||
StackLayoutManager.ScrollOrientation.TOP_TO_BOTTOM -> (mStartMargin - mPerItemOffset * (visiblePosition - movePercent)).toInt()
|
||||
else -> mHeightSpace / 2
|
||||
}
|
||||
}
|
||||
|
||||
private fun getStartMargin(): Int {
|
||||
return when (mScrollOrientation) {
|
||||
StackLayoutManager.ScrollOrientation.RIGHT_TO_LEFT, StackLayoutManager.ScrollOrientation.LEFT_TO_RIGHT -> mWidthSpace / 2
|
||||
else -> mHeightSpace / 2
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,65 @@
|
||||
package com.gh.gamecenter.home.gamecollection
|
||||
|
||||
import android.content.Context
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.gh.gamecenter.R
|
||||
import com.gh.gamecenter.databinding.HomeGameCollectionCardItemBinding
|
||||
import com.gh.gamecenter.entity.GamesCollectionEntity
|
||||
import com.lightgame.adapter.BaseRecyclerAdapter
|
||||
|
||||
class HomeGameCollectionAdapter(context: Context) :
|
||||
BaseRecyclerAdapter<RecyclerView.ViewHolder>(context) {
|
||||
|
||||
private var mGameCollectionList = ArrayList<GamesCollectionEntity>()
|
||||
|
||||
fun setGameCollectionList(gameCollectionList: ArrayList<GamesCollectionEntity>) {
|
||||
mGameCollectionList = gameCollectionList
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = HomeGameCollectionCardViewHolder(
|
||||
HomeGameCollectionCardItemBinding.bind(
|
||||
mLayoutInflater.inflate(
|
||||
R.layout.home_game_collection_card_item,
|
||||
parent,
|
||||
false
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||
if (holder is HomeGameCollectionCardViewHolder) {
|
||||
var realPosition: Int
|
||||
when (position) {
|
||||
0 -> {
|
||||
realPosition = getRealCount() - 2
|
||||
}
|
||||
1 -> {
|
||||
realPosition = getRealCount() - 1
|
||||
}
|
||||
itemCount - 2 -> {
|
||||
realPosition = 0
|
||||
}
|
||||
itemCount - 1 -> {
|
||||
realPosition = 1
|
||||
}
|
||||
else -> {
|
||||
realPosition = position - 2
|
||||
}
|
||||
}
|
||||
holder.bindGameListCard(position, mGameCollectionList[realPosition])
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount() = getRealCount() + 4
|
||||
|
||||
fun getRealCount() = mGameCollectionList.size
|
||||
|
||||
class HomeGameCollectionCardViewHolder(val binding: HomeGameCollectionCardItemBinding) :
|
||||
RecyclerView.ViewHolder(binding.root) {
|
||||
fun bindGameListCard(itemPosition: Int, entity: GamesCollectionEntity) {
|
||||
binding.entity = entity
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,46 @@
|
||||
package com.gh.gamecenter.home.gamecollection
|
||||
|
||||
import com.gh.base.BaseRecyclerViewHolder
|
||||
import com.gh.common.util.dip2px
|
||||
import com.gh.common.view.stacklayoutmanager.StackLayoutManager
|
||||
import com.gh.gamecenter.databinding.HomeGameCollectionItemBinding
|
||||
import com.gh.gamecenter.entity.GamesCollectionEntity
|
||||
|
||||
class HomeGameCollectionViewHolder(val binding: HomeGameCollectionItemBinding) :
|
||||
BaseRecyclerViewHolder<Any>(binding.root) {
|
||||
fun bindGameCollectionList(gameCollectionList: ArrayList<GamesCollectionEntity>) {
|
||||
if (binding.recyclerView.adapter is HomeGameCollectionAdapter) {
|
||||
return
|
||||
}
|
||||
val adapter = HomeGameCollectionAdapter(binding.root.context)
|
||||
val manager = StackLayoutManager(
|
||||
StackLayoutManager.ScrollOrientation.RIGHT_TO_LEFT,
|
||||
3,
|
||||
GameCollectionStackAnimation::class.java,
|
||||
GameCollectionStackLayout::class.java
|
||||
)
|
||||
manager.setItemOffset(9F.dip2px())
|
||||
manager.setItemChangedListener(object : StackLayoutManager.ItemChangedListener {
|
||||
override fun onItemChanged(position: Int) {
|
||||
when (position) {
|
||||
0 -> binding.recyclerView.scrollToPosition(adapter.itemCount - 3)
|
||||
// 最后一个item跳转
|
||||
adapter.itemCount - 3 -> binding.recyclerView.scrollToPosition(1)
|
||||
}
|
||||
}
|
||||
})
|
||||
binding.recyclerView.layoutManager = manager
|
||||
binding.recyclerView.adapter = adapter
|
||||
binding.recyclerView.isNestedScrollingEnabled = false
|
||||
|
||||
adapter.setGameCollectionList(gameCollectionList)
|
||||
binding.recyclerView.post {
|
||||
// 定位到实际的第一个item
|
||||
manager.scrollToPosition(FIRST_ITEM_POSITION)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val FIRST_ITEM_POSITION = 2
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user