Coming soon - Get a detailed view of why an account is flagged as spam!
view details

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.

0
RecyclerView with Custom View child not working properly
Post Body

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:

RecyclerView error

Author
Account Strength
90%
Account Age
7 years
Verified Email
Yes
Verified Flair
No
Total Karma
3,195
Link Karma
2,282
Comment Karma
761
Profile updated: 5 days ago
Posts updated: 1 day ago
Android Engineer

Subreddit

Post Details

We try to extract some basic information from the post title. This is not always successful or accurate, please use your best judgement and compare these values to the post title and body for confirmation.
Posted
5 years ago