Created
April 21, 2020 06:00
-
-
Save boybeak/1b387258bf11c7a3c50ae8380bf9136b to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.github.boybeak.design.widget | |
import android.content.Context | |
import android.util.Log | |
import android.util.Size | |
import androidx.recyclerview.widget.OrientationHelper | |
import androidx.recyclerview.widget.RecyclerView | |
import com.github.boybeak.design.ext.isNulls | |
import kotlin.math.ceil | |
import kotlin.math.min | |
class GalleryLayoutManager(context: Context, /*private val orientation: Int, */private val gap: Int | |
) : RecyclerView.LayoutManager() { | |
companion object { | |
private val TAG = GalleryLayoutManager::class.java.simpleName | |
} | |
private val sizes = arrayOfNulls<Size>(4) | |
private var groupWidth: Int = 0 | |
private var maxScrollX = 0 | |
private var groupSizeWithWidth = 0 | |
private val orientationHelper = OrientationHelper.createHorizontalHelper(this) | |
init { | |
/*if (orientation != RecyclerView.HORIZONTAL && orientation != RecyclerView.VERTICAL) { | |
throw IllegalArgumentException("Illegal orientation($orientation), " + | |
"must be RecyclerView.HORIZONTAL or RecyclerView.VERTICAL") | |
}*/ | |
} | |
override fun generateDefaultLayoutParams(): RecyclerView.LayoutParams { | |
return RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.MATCH_PARENT) | |
} | |
override fun onLayoutChildren(recycler: RecyclerView.Recycler, state: RecyclerView.State) { | |
if (itemCount == 0) { | |
detachAndScrapAttachedViews(recycler) | |
return | |
} | |
if (childCount == 0 && state.isPreLayout) { | |
return | |
} | |
if (sizes.isNulls()) { | |
val h0 = height- 2 * gap | |
sizes[0] = Size(h0, h0) | |
val h1 = (height - 3 * gap) / 2 | |
sizes[1] = Size(h0, h1) | |
val h2 = (h0 - gap) / 2 | |
sizes[2] = Size(h2, h2) | |
sizes[3] = Size(h2, h2) | |
groupWidth = sizes[0]!!.width + sizes[1]!!.width + gap * 2 | |
groupSizeWithWidth = getItemCountWithinWidth() | |
} | |
val groupCountTotally = itemCount / 4 | |
val remainCount = itemCount % 4 | |
val distance = groupCountTotally * groupWidth + | |
when (remainCount) { | |
0 -> { | |
gap | |
} | |
1 -> { | |
sizes[0]!!.width + gap * 2 | |
} | |
else -> { | |
groupWidth + gap | |
} | |
} | |
maxScrollX = min(0, width - distance) | |
detachAndScrapAttachedViews(recycler) | |
for (p in 0 until itemCount) { | |
val m = p % sizes.size | |
val offsetX = getAbsoluteOffsetXForPosition(p) + scrollX | |
val offsetY = when(m) { | |
0,1 -> gap | |
2,3 -> gap * 2 + sizes[1]!!.height | |
else -> 0 | |
} | |
val s = sizes[m]!! | |
val l = offsetX | |
val r = l + s.width | |
val t = offsetY | |
val b = t + s.height | |
if (r < 0) { | |
continue | |
} | |
if (l > width) { | |
break | |
} | |
val child = recycler.getViewForPosition(p) | |
child.layoutParams.apply { | |
width = s.width | |
height = s.height | |
} | |
measureChildWithMargins(child, s.width, s.height) | |
child.requestLayout() | |
addView(child) | |
layoutDecoratedWithMargins(child, l, t, r, b) | |
} | |
} | |
override fun canScrollHorizontally(): Boolean { | |
return true | |
} | |
private var scrollX = 0 | |
override fun scrollHorizontallyBy( | |
dx: Int, | |
recycler: RecyclerView.Recycler, | |
state: RecyclerView.State | |
): Int { | |
// <- positive | |
// -> negative | |
var predictSX = scrollX - dx | |
if (dx > 0 && predictSX < maxScrollX && scrollX >= maxScrollX) { | |
predictSX = maxScrollX | |
} else if (dx < 0 && predictSX > 0 && scrollX <= 0) { | |
predictSX = 0 | |
} | |
val newDX = scrollX - predictSX | |
scrollX = predictSX | |
recycleViews(newDX, recycler) | |
fill(dx, recycler) | |
offsetChildrenHorizontal(-newDX) | |
return newDX | |
} | |
private fun fill(dx: Int, recycler: RecyclerView.Recycler) { | |
if (dx > 0) { | |
val lastVisibleView = getChildAt(childCount - 1) ?: return | |
val position = getPosition(lastVisibleView) | |
if (position >= itemCount - 1) { | |
return | |
} | |
val nextPosition = position + 1 | |
val m = nextPosition % sizes.size | |
val s = sizes[m]!! | |
val nextView = recycler.getViewForPosition(nextPosition) | |
nextView.layoutParams.apply { | |
width = s.width | |
height = s.height | |
} | |
addView(nextView) | |
if (position == 9) { | |
Log.v(TAG, "dx=$dx fill addViewFor nextPosition=$nextPosition") | |
} | |
measureChild(nextView, s.width, s.height) | |
nextView.requestLayout() | |
val viewWidth = getDecoratedMeasuredWidth(nextView) | |
val viewHeight = getDecoratedMeasuredHeight(nextView) | |
val offsetX = when(m) { | |
0, 1, 3 -> lastVisibleView.right + gap | |
else -> lastVisibleView.left | |
} | |
val offsetY = when(m) { | |
0, 1 -> gap | |
else -> sizes[1]!!.height + 2 * gap | |
} | |
layoutDecorated(nextView, offsetX, offsetY, offsetX + viewWidth, offsetY + viewHeight) | |
} else if (dx < 0) { | |
val firstVisibleView = getChildAt(0) ?: return | |
val position = getPosition(firstVisibleView) | |
if (position <= 0) { | |
return | |
} | |
val prePosition = position - 1 | |
val m = prePosition % sizes.size | |
val s = sizes[m]!! | |
val preView = recycler.getViewForPosition(prePosition) | |
preView.layoutParams.apply { | |
width = s.width | |
height = s.height | |
} | |
addView(preView) | |
measureChild(preView, s.width, s.height) | |
preView.requestLayout() | |
val viewWidth = getDecoratedMeasuredWidth(preView) | |
val viewHeight = getDecoratedMeasuredHeight(preView) | |
val offsetX = when(m) { | |
0 -> firstVisibleView.left - gap - s.width | |
1 -> firstVisibleView.left | |
2 -> firstVisibleView.left - gap - s.width | |
else -> firstVisibleView.left - gap - s.width | |
} | |
val offsetY = when(m) { | |
0, 1 -> gap | |
else -> sizes[1]!!.height + 2 * gap | |
} | |
layoutDecorated(preView, offsetX, offsetY, offsetX + viewWidth, offsetY + viewHeight) | |
} | |
} | |
private fun recycleViews(dx: Int, recycler: RecyclerView.Recycler) { | |
for (i in 0 until itemCount) { | |
val childView = getChildAt(i) ?: return | |
val position = getPosition(childView) | |
//左滑 | |
if (dx > 0) { | |
//移除并回收 原点 左侧的子View | |
if (childView.right - dx < 0) { | |
removeAndRecycleViewAt(i, recycler) | |
} | |
} else { //右滑 | |
//移除并回收 右侧即RecyclerView宽度之以外的子View | |
if (childView.left - dx > width) { | |
removeAndRecycleViewAt(i, recycler) | |
} | |
} | |
} | |
} | |
private fun getItemCountWithinWidth(): Int { | |
val groupCount = ceil(width * 1.0 / groupWidth).toInt() | |
return groupCount * sizes.size | |
} | |
private fun getAbsoluteOffsetXForPosition(position: Int): Int { | |
val g = position / sizes.size | |
val m = position % sizes.size | |
return groupWidth * g + when(m) { | |
0 -> gap | |
1 -> gap * 2 + sizes[0]!!.width | |
2 -> gap * 2 + sizes[0]!!.width | |
3 -> gap * 3 + sizes[0]!!.width + sizes[2]!!.width | |
else -> 0 | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="utf-8"?> | |
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android" | |
xmlns:app="http://schemas.android.com/apk/res-auto" | |
android:layout_width="match_parent" | |
android:layout_height="match_parent" | |
style="@style/CardStyle" | |
app:cardCornerRadius="@dimen/radius_large"> | |
<androidx.appcompat.widget.AppCompatImageView | |
android:id="@+id/image" | |
android:layout_width="match_parent" | |
android:layout_height="match_parent" | |
android:background="#BBDEFB" | |
/> | |
<androidx.appcompat.widget.AppCompatTextView | |
android:id="@+id/number" | |
android:layout_width="match_parent" | |
android:layout_height="match_parent" | |
android:gravity="center" | |
android:background="@drawable/fg_thumb" | |
android:textAppearance="@style/CaptionAppearanceBold.Inverse"/> | |
</androidx.cardview.widget.CardView> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.github.boybeak.donkey.adapter.holder | |
import android.util.Log | |
import android.view.View | |
import android.widget.ImageView | |
import android.widget.TextView | |
import androidx.recyclerview.widget.StaggeredGridLayoutManager | |
import com.github.boybeak.adapter.AbsHolder | |
import com.github.boybeak.adapter.AnyAdapter | |
import com.github.boybeak.donkey.R | |
import com.github.boybeak.donkey.adapter.item.MediaImageItem | |
import com.github.boybeak.core.ext.thumb | |
import com.github.boybeak.donkey.databinding.ItemMediaImageBinding | |
import kotlinx.android.synthetic.main.item_media_image.view.* | |
import org.jetbrains.anko.dip | |
import java.io.File | |
class MediaImageHolder(v: View) : AbsHolder<MediaImageItem>(v) { | |
companion object { | |
private val TAG = MediaImageHolder::class.java.simpleName | |
} | |
private val binding = ItemMediaImageBinding.bind(v) | |
override fun onBind(item: MediaImageItem, position: Int, absAdapter: AnyAdapter) { | |
binding.image.thumb(File(item.source().content), true) | |
binding.number.text = position.toString() | |
Log.v(TAG, "onBind position=$position itemView=(${itemView.width}, ${itemView.height}) img=(${binding.image.width}, ${binding.image.height})") | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment