This post has been de-listed
It is no longer included in search results and normal feeds (front page, hot posts, subreddit posts, etc). It remains visible only via the author's post history.
I have created a custom view which serves as the root view of a RecyclerView item. I am trying to animate (handled by the custom view) each item of the RecyclerView, but it is only working for the first item in the RecyclerView. It screws up the second item and then doesn't work at all for the others. All the items animate their height properly, they just don't draw the grey ColorDrawable on top.
This is my custom view:
class GestureActionLayout(context: Context, attributeSet: AttributeSet): FrameLayout(context, attributeSet) {
private val initialMeasuredHeight: Int by lazy { measuredHeight }
// The actual content being displayed
private lateinit var gestureViewChild: View
// The overlay to provide gesture based actions
private var foregroundDrawable: ForegroundDrawable
private var foregroundDrawableHeightRatio = 1.0f
private var shouldForegroundBeDrawn = false
init {
val typedArray = context.obtainStyledAttributes(attributeSet, R.styleable.GestureActionLayout)
foregroundDrawableHeightRatio = typedArray.getFloat(R.styleable.GestureActionLayout_item_expand_ratio, 1.0f)
typedArray.recycle()
val positiveColour = ResourcesCompat.getColor(resources, R.color.colorTeal, null)
val negativeColour = ResourcesCompat.getColor(resources, R.color.colorRed, null)
foregroundDrawable = ForegroundDrawable(ColorDrawable(positiveColour), ColorDrawable(Color.DKGRAY), positiveColour, negativeColour)
foregroundDrawable.alpha = 0
foregroundDrawable.bounds = Rect(left, top, right, bottom)
foregroundDrawable.callback = this
}
override fun dispatchDraw(canvas: Canvas?) {
super.dispatchDraw(canvas)
if (shouldForegroundBeDrawn && canvas != null)
Log.e("DRAW", "FOREGROUND DRAW CALLED: $canvas")
foregroundDrawable.draw(canvas)
}
fun setGestureActions(gestureActions: GestureActions) {
}
/**
* Represents the drawable drawn on top of the content in the layout that
* provides gesture driven actions
*/
class ForegroundDrawable(foregroundColourDrawable: ColorDrawable,
grayFillerDrawable: ColorDrawable,
private val positiveColour: Int,
private val negativeColour: Int):
LayerDrawable(arrayOf(foregroundColourDrawable, grayFillerDrawable)) {
private lateinit var swipeTransitionAnimator: ObjectAnimator
private lateinit var changeColourAnimator: ValueAnimator
private var isPositive: Boolean = true
private val foregroundColourDrawable: ColorDrawable
get() = getDrawable(0) as ColorDrawable
private val greyFillerDrawable: ColorDrawable
get() = getDrawable(1) as ColorDrawable
fun animateSwipeGesture(alpha: Int) {
if (alpha == greyFillerDrawable.alpha)
return
if (swipeTransitionAnimator != null)
swipeTransitionAnimator.cancel()
swipeTransitionAnimator = ObjectAnimator.ofInt(
greyFillerDrawable,
"alpha",
greyFillerDrawable.alpha,
alpha
)
swipeTransitionAnimator.duration = 200
swipeTransitionAnimator.interpolator = FastOutSlowInInterpolator()
swipeTransitionAnimator.start()
}
fun animateColourChange(isPositive: Boolean) {
if(isPositive == this.isPositive)
return
this.isPositive = isPositive
if (changeColourAnimator != null)
changeColourAnimator.cancel()
changeColourAnimator = ValueAnimator.ofArgb(foregroundColourDrawable.color,
if (isPositive) positiveColour else negativeColour)
changeColourAnimator.addUpdateListener {
foregroundColourDrawable.color = it.animatedValue as Int
}
changeColourAnimator.duration = 200
changeColourAnimator.interpolator = FastOutSlowInInterpolator()
changeColourAnimator.start()
}
}
fun displayActions() {
Log.e("VALUES", "Top: $top Bottom: $bottom Right: $right Left: $left")
shouldForegroundBeDrawn = true
invalidate()
val viewHeightAnimator = ValueAnimator.ofFloat(initialMeasuredHeight.toFloat(), (initialMeasuredHeight * foregroundDrawableHeightRatio))
viewHeightAnimator.addUpdateListener {
val update = it.animatedValue as Float
val layoutParams = this.layoutParams
layoutParams.height = update.toInt()
this.layoutParams = layoutParams
foregroundDrawable.setBounds(left, top, right, bottom)
}
val actionsAlphaAnimator = ObjectAnimator.ofInt(
foregroundDrawable,
"alpha",
0,
255
)
val animatorSet = AnimatorSet()
animatorSet.duration = 300
animatorSet.interpolator = FastOutSlowInInterpolator()
animatorSet.playTogether(viewHeightAnimator, actionsAlphaAnimator)
animatorSet.start()
}
fun setSwipeTranslation(translationY: Float) {
if (!shouldForegroundBeDrawn)
return
// If user swipes down
if (translationY < 0) {
foregroundDrawable.animateColourChange(false)
} else {
foregroundDrawable.animateColourChange(true)
}
val translationYabs = Math.abs(translationY)
if (translationYabs > (height / 3)) {
foregroundDrawable.animateSwipeGesture(0)
} else {
foregroundDrawable.animateSwipeGesture(255)
}
}
fun hideActions() {
val viewHeightAnimator = ValueAnimator.ofFloat((initialMeasuredHeight * foregroundDrawableHeightRatio), initialMeasuredHeight.toFloat())
viewHeightAnimator.addUpdateListener {
val update = it.animatedValue as Float
val layoutParams = this.layoutParams
layoutParams.height = update.toInt()
this.layoutParams = layoutParams
foregroundDrawable.setBounds(left, top, right, bottom)
}
val actionsAlphaAnimator = ObjectAnimator.ofInt(
foregroundDrawable,
"alpha",
255,
0
)
val animatorSet = AnimatorSet()
animatorSet.duration = 300
animatorSet.interpolator = FastOutSlowInInterpolator()
animatorSet.playTogether(viewHeightAnimator, actionsAlphaAnimator)
animatorSet.doOnEnd {
// Completely remove foreground
shouldForegroundBeDrawn = false
invalidate()
}
animatorSet.start()
}
override fun invalidateDrawable(drawable: Drawable) {
if (drawable == foregroundDrawable)
invalidate()
else
super.invalidateDrawable(drawable)
}
}
This is my Adapter:
class SubmissionsAdapter(private val clickListener: SubmissionItemClickListener,
private val submissions: List<Submission>):
RecyclerView.Adapter<SubmissionsAdapter.SubmissionViewHolder>() {
init {
setHasStableIds(true)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SubmissionViewHolder {
val submissionLayout = LayoutInflater.from(parent.context).inflate(R.layout.list_submission_item, parent, false)
return SubmissionViewHolder(submissionLayout, clickListener)
}
override fun onBindViewHolder(holder: SubmissionViewHolder, position: Int) {
holder.submissionIndex = position
holder.submissionTitle.text = submissions[position].title
holder.submissionAuthor.text = submissions[position].author
holder.submissionSubreddit.text = submissions[position].subReddit
}
override fun getItemCount(): Int {
return submissions.size
}
open class SubmissionViewHolder(
itemView: View,
listener: SubmissionItemClickListener
): RecyclerView.ViewHolder(itemView) {
/*
Lazy evaluated views to avoid null reference exceptions
*/
val submissionTitle: TextView by lazy { itemView.findViewById<TextView>(R.id.submission_title) }
val submissionAuthor: TextView by lazy { itemView.findViewById<TextView>(R.id.submission_author) }
val submissionSubreddit: TextView by lazy { itemView.findViewById<TextView>(R.id.submission_subreddit) }
val gestureLayout: GestureActionLayout by lazy { itemView as GestureActionLayout }
var submissionIndex: Int by Delegates.notNull()
var isLongPressed = false
init {
itemView.setOnClickListener {
listener.onItemClick(submissionIndex)
}
gestureLayout.setOnLongClickListener {
isLongPressed = true
gestureLayout.displayActions()
true
}
gestureLayout.setOnTouchListener { view, motionEvent ->
Log.d("GESTURE", "Gesture instance: $gestureLayout")
if (isLongPressed) {
if (motionEvent.action == MotionEvent.ACTION_UP || motionEvent.action == MotionEvent.ACTION_CANCEL) {
gestureLayout.hideActions()
isLongPressed = false
} else {
view.onTouchEvent(motionEvent)
}
} else {
view.onTouchEvent(motionEvent)
}
true
}
}
}
}
Do excuse the messy code, I'm just playing with custom views right now. Below is a recording of what happens, I'd like all the items to animate like the first item in the RecyclerView:
Subreddit
Post Details
- Posted
- 5 years ago
- Reddit URL
- View post on reddit.com
- External URL
- reddit.com/r/androiddev/...