Можно ли разместить ItemDecoration на равных интервалах (независимо от предметов)? - PullRequest
0 голосов
/ 24 февраля 2020

Я пытаюсь создать свой собственный DayView / TimelineView / AgendaView, который имеет много имен, но по сути - что-то вроде этого:

DayView example 1 DayView example 2 DayView example 3

Я не смог найти ни одной библиотеки, которая работает + поддерживает API версии 21 + и позволяет мне выполнять необходимые настройки (например, пользовательское форматирование времени, макет элемента события, и др c). Поэтому я решил сделать свой. Мне любопытно, возможно ли создать это, используя простой ItemDecoration как время, где события будут тем, что фактически заполняет RecyclerView.

1 Ответ

0 голосов
/ 26 февраля 2020

Я создал нечто подобное, используя ItemDecoration надеюсь, это поможет.

DayView recyclerview with itemDecoration

в вашем Activity/Fragment вызове setAdapter()

@RequiresApi(Build.VERSION_CODES.O)
private fun setAdapter() {
    val data = arrayListOf<EventPOJO>(
        EventPOJO(
            "Entry 1 \n Lorem Ipsum is simply dummy text of the printing and typesetting industry. ",
            "12:00 AM"
        ),
        EventPOJO("Entry 2 ", "01:00 AM")
    )
    val mAdapter = Adapter<EventPOJO>(data)
    recyclerDay.run {
        adapter = mAdapter
        layoutManager = LinearLayoutManager(context)
        addItemDecoration(ScheduleTimeHeadersDecoration(data))
    }
}

Ваш обычный RecyclerView.Adapter

class Adapter<T>(
private val eventList: ArrayList<T>
) : RecyclerView.Adapter<Adapter.EventListViewHolder>() {


override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EventListViewHolder {
    val layoutInflater = LayoutInflater.from(parent.context)

    val view: View = layoutInflater.inflate(R.layout.item_day, parent, false)
    return EventListViewHolder(view)
}

override fun getItemCount() = eventList.size

override fun onBindViewHolder(holder: EventListViewHolder, position: Int) {
    holder.bind(eventList[position])
}

class EventListViewHolder(var view: View) :
    RecyclerView.ViewHolder(view) {
    private var name: TextView? = null

    init {

        name = view.findViewById(R.id.name)
        name?.creatRandomColor()
    }

    fun <T> bind(bindObj: T) {
        if (bindObj is EventPOJO) {
            name?.text = bindObj.name
        }
    }

    private fun TextView.creatRandomColor() {

        val rnd = Random()
        val color = Color.argb(
            255,
            rnd.nextInt(256),
            rnd.nextInt(256),
            rnd.nextInt(256)
        )
        this.setBackgroundColor(color)

    }
}}

Объект данных

data class EventPOJO(
val name: String,
val time: String

)

ваш item_day. xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 
    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="wrap_content">

<TextView
        android:id="@+id/name"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_margin="5dp"
        android:minHeight="50dp"
        android:padding="5dp"
        android:text="Entry 1"
        android:textSize="20sp"
        android:textStyle="bold"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="@+id/guideline"
        app:layout_constraintTop_toTopOf="parent" />

     <android.support.constraint.Guideline
        android:id="@+id/guideline"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_begin="100dp" /> 
</android.support.constraint.ConstraintLayout>

и, наконец, ваш itemDecoration

/**
* A [RecyclerView.ItemDecoration] which draws sticky headers for a given list of 
sessions.
*/
@RequiresApi(Build.VERSION_CODES.O)
class ScheduleTimeHeadersDecoration(
sessions: ArrayList<EventPOJO>
) : RecyclerView.ItemDecoration() {

private val paint: TextPaint = TextPaint(ANTI_ALIAS_FLAG).apply {
    color = Color.BLUE
    textSize = 50f
}
private val width: Int = 350

// Get the sessions index:start time and create header layouts for each
@RequiresApi(Build.VERSION_CODES.O)
private val timeSlots: Map<Int, StaticLayout> =
    sessions
        .mapIndexed { index, session ->
            index to session.time
        }.map {
            it.first to createHeader(it.second)
        }.toMap()


override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {

    val parentPadding = parent.paddingTop

    var earliestPosition = Int.MAX_VALUE
    var previousHeaderPosition = -1
    var previousHasHeader = false
    var earliestChild: View? = null
    for (i in parent.childCount - 1 downTo 0) {
        val child = parent.getChildAt(i)


        if (child.y > parent.height || (child.y + child.height) < 0) {
            // Can't see this child
            continue
        }

        val position = parent.getChildAdapterPosition(child)
        if (position < 0) {
            continue
        }
        if (position < earliestPosition) {
            earliestPosition = position
            earliestChild = child
        }

        val header = timeSlots[position]
        if (header != null) {
            drawHeader(c, child, parentPadding, header, child.alpha, previousHasHeader)
            previousHeaderPosition = position
            previousHasHeader = true
        } else {
            previousHasHeader = false
        }
    }

    if (earliestChild != null && earliestPosition != previousHeaderPosition) {
        // This child needs a sicky header
        findHeaderBeforePosition(earliestPosition)?.let { stickyHeader ->
            previousHasHeader = previousHeaderPosition - earliestPosition == 1
            drawHeader(c, earliestChild, parentPadding, stickyHeader, 1f, previousHasHeader)
        }
    }


}

private fun findHeaderBeforePosition(position: Int): StaticLayout? {
    for (headerPos in timeSlots.keys.reversed()) {
        if (headerPos < position) {
            return timeSlots[headerPos]
        }
    }
    return null
}

private fun drawHeader(
    canvas: Canvas,
    child: View,
    parentPadding: Int,
    header: StaticLayout,
    headerAlpha: Float,
    previousHasHeader: Boolean
) {
    val childTop = child.y.toInt()
    val childBottom = childTop + child.height
    var top = (childTop).coerceAtLeast(parentPadding)
    if (previousHasHeader) {
        top = top.coerceAtMost(childBottom - header.height - 25)
    }
    paint.alpha = (headerAlpha * 255).toInt()
    canvas.withTranslation(y = top.toFloat()) {
        header.draw(canvas)
    }
}

/**
 * Create a header layout for the given [startTime].
 */
private fun createHeader(startTime: String): StaticLayout {
    val text = SpannableStringBuilder().apply {

        append(startTime.split(" ")[0])

        append(System.lineSeparator())

        append(startTime.split(" ")[1])

    }
    return newStaticLayout(text, paint, width, ALIGN_CENTER, 1f, 0f, false)
}

fun newStaticLayout(
    source: CharSequence,
    paint: TextPaint,
    width: Int,
    alignment: Layout.Alignment,
    spacingmult: Float,
    spacingadd: Float,
    includepad: Boolean
): StaticLayout {
    return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        StaticLayout.Builder.obtain(source, 0, source.length, paint, width).apply {
            setAlignment(alignment)
            setLineSpacing(spacingadd, spacingmult)
            setIncludePad(includepad)
        }.build()
    } else {
        @Suppress("DEPRECATION")
        StaticLayout(source, paint, width, alignment, spacingmult, spacingadd, includepad)
    }
}

private inline fun Canvas.withTranslation(
    x: Float = 0.0f,
    y: Float = 0.0f,
    block: Canvas.() -> Unit
) {
    val checkpoint = save()
    translate(x, y)
    try {
        block()
    } finally {
        restoreToCount(checkpoint)
    }
}

}

ссылка Google I / O 2019 Android Приложение

...