Design thinking cultivation: RecyclerView in decorator mode adds head and tail

Use a design pattern to cultivate high reuse and low coupling ideas

  • Preface
  • Decorators in Android
  • Code
    • Step 1: Create the decorator DecorateAdapter
    • Step 2: Process the binding relationship between the header, middle content, and tail
    • Step 3: Use of decorators
    • Step 4: Improve and directly encapsulate a View
  • Summarize

Foreword

A code with high reuse and low coupling will not make you feel comfortable the first time you implement the code.
But when you expand and meet similar needs later, he will say “it’s so delicious!” ! !

Recently, I wrote a requirement and borrowed the idea of decorator to extend the head and tail of RecyclerView.
It feels very good, I’ll take it out and talk about it quickly, hehe

ps: This article is just to help you. You can also use design patterns to optimize your code subtly in the process of realizing your needs.

Decorators in Android

First, one sentence summarizes the decorator: it is to extend the function of an object without inheritance to achieve the effect we want to achieve.
Let’s give two small examples without going into details, because we have already talked about the decorator pattern before.

  • InputStream decorator

InputStream decorator: In Android, we often need to read files or network streams. InputStream is an abstract class for reading byte streams, and Android provides many decorator classes to extend the functionality of InputStream.
For example, BufferedInputStream and DataInputStream are both decorator classes of InputStream, which add buffering and data type conversion functions.

FileInputStream fileInputStream = new FileInputStream("example.txt");
BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
DataInputStream dataInputStream = new DataInputStream(bufferedInputStream);
  • View decorator

UI components in Android all inherit from the View class, and Android provides decorator classes to extend the functions of View
For example, you can use LayoutInflater to add some additional decorations to the layout file

LayoutInflater inflater = LayoutInflater.from(context);
View originalView = inflater.inflate(R.layout.original_layout, parentLayout, false);
ViewDecorator viewDecorator = new ViewDecorator(originalView);
View decoratedView = viewDecorator.decorate();
parentLayout.addView(decoratedView);

Based on this idea, I thought of extending the head and tail of RecyclerView, using this decorator extension method to add the head and tail, while retaining the original attributes of the intermediate data layer.

Code implementation

Use DecorateAdapter to decorate RecyclerView.Adapter to extend HeadView and FooterView

We use contentAdapter to represent the intermediate content. Assuming it is a simple Item structure, we will not introduce the code.

Step 1: Create the decorator DecorateAdapter

Create the decorator DecorateAdapter and implement related methods and member variables for adding head and tail items
and use the contentAdapter as the original content list

class DecorateAdapter(
    val contentAdapter: RecyclerView.Adapter<RecyclerView.ViewHolder>
): RecyclerView.Adapter<RecyclerView.ViewHolder>(), IdecorateList {<!-- -->

    /**save head View*/
    private val headerList: MutableList<View> by lazy {<!-- -->
        mutableListOf()
    }

    /**save footer view*/
    private val footerList: MutableList<View> by lazy {<!-- -->
        mutableListOf()
    }

    /**normal method*/
    ...
    ...

    override fun getItemCount(): Int {<!-- -->
        //Remember to add the Item entry of contentAdapter
        return contentAdapter.itemCount + headerList.size + footerList.size
    }

    override fun addHeaderView(header: View) {<!-- -->
        if (!headerList.contains(header)) {<!-- -->
            headerList.add(header)
            refreshList()
        }
    }

    override fun removeHeaderView(header: View) {<!-- -->
        if (headerList.contains(header)) {<!-- -->
            headerList.remove(header)
            refreshList()
        }
    }

    override fun addFooterView(foot: View) {<!-- -->
        if (!footerList.contains(foot)) {<!-- -->
            footerList.add(foot)
            refreshList()
        }
    }

    override fun removeFooterView(foot: View) {<!-- -->
        if (footerList.contains(foot)) {<!-- -->
            footerList.remove(foot)
            refreshList()
        }
    }

    override fun refreshList() {<!-- -->
        notifyDataSetChanged()
    }
}

Step 2: Process the binding relationship between the header, middle content, and tail

  • Handling different bound views of the head, middle and tail: that is, createHeaderViewHolder processing
/**Create the ViewHolder of the header*/
private fun createHeaderViewHolder(view: View): RecyclerView.ViewHolder {<!-- -->
    return HeaderViewHolder(view)
}

/**Create the tail ViewHolder*/
private fun createFooterViewHolder(view: View): RecyclerView.ViewHolder {<!-- -->
    return FooterViewHolder(view)
}

override fun onCreateViewHolder(parent: ViewGroup, position: Int): RecyclerView.ViewHolder {<!-- -->

    /**Head style display*/
    if (headerList.isNotEmpty() & amp; & amp; position in 0 until headerList.size) {<!-- -->
        return createHeaderViewHolder(headerList[position])
    }

    /**Intermediate content display*/
    val startPosition = if (headerList.isNotEmpty()) headerList.size else 0
    val endPosition =
        if (headerList.isNotEmpty()) headerList.size + contentAdapter.itemCount else contentAdapter.itemCount
    if (position in startPosition until endPosition) {<!-- -->
        return contentAdapter.onCreateViewHolder(parent, position)
    }

    /**Tail style display*/
    return createFooterViewHolder(footerList[position - endPosition]) /**Note the value here*/
}
  • Processing of data binding at the head, middle, and tail: that is, the processing of onBindViewHolder
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {<!-- -->

    if (headerList.isNotEmpty() & amp; & amp; position in 0 until headerList.size) {<!-- -->
        return
    }
    /**Intermediate content data binding*/
    val startPosition = if (headerList.isNotEmpty()) headerList.size else 0
    val endPosition =
        if (headerList.isNotEmpty()) headerList.size + contentAdapter!!.itemCount else contentAdapter!!.itemCount
    if (position in startPosition until endPosition) {<!-- -->
        /**Note that when calculating, you need to subtract HeadView*/
        contentAdapter?.onBindViewHolder(holder, position - headerList.size)
    }
}

Step 3: Use of decorators

Wrap the ContentAdapter of the content area through DecorateAdapter, and then call the wrapped decorateAdapter to add HeadView

decorate = findViewById(R.id.rv_decorate)
val decorateAdapter = DecorateAdapter(ContentAdapter())
decorate.adapter = decorateAdapter
decorate.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)

decorateAdapter.addHeaderView(
    LayoutInflater.from(this).inflate(R.layout.layout_header, decorate, false)
)

Step 4: Improve and directly encapsulate a View

Seal the decorator inside
After all, when we call it, we still want to use it directly as a View and improve it.

class decorateRecyclerView : RecyclerView {<!-- -->

    private var DecorateAdapter: DecorateAdapter? = null

    @JvmOverloads
    constructor(context: Context, attributes: AttributeSet? = null) : super(context, attributes)

    override fun setAdapter(adapter: Adapter<ViewHolder>?) {<!-- -->
        DecorateAdapter = DecorateAdapter(adapter!!)
        super.setAdapter(DecorateAdapter)
    }

    fun addHeaderView(header: View) {<!-- -->
        DecorateAdapter?.addHeaderView(header)
    }

    fun removeHeaderView(header: View) {<!-- -->
        DecorateAdapter?.removeHeaderView(header)
    }

    fun addFooterView(foot: View) {<!-- -->
        DecorateAdapter?.addFooterView(foot)
    }

    fun removeFooterView(foot: View) {<!-- -->
        DecorateAdapter?.removeFooterView(foot)
    }
}

Call & amp; use

decorate = findViewById(R.id.rv_decorate)
decorate.adapter = ContentAdapter()
decorate.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)

decorate.addHeaderView(
    LayoutInflater.from(this).inflate(R.layout.layout_header, decorate, false)
)
decorate.addFooterView(
    LayoutInflater.from(this).inflate(R.layout.layout_footer, decorate, false)
)

Summary

In this article, I just want to talk about how to use design patterns subtly to change our code.
An example of extending the head and tail of RecyclerView is given.
If you don’t use design patterns, I think your head and tail processing logic needs to be redundant in ContentAdapter.
question:
1. So what are the definition limits of your ContentAdapter? Entire page of business? That would inevitably be too difficult to maintain.
2. What to do if you only need to take ContentAdapter? Paste and copy, create an extra class?

Through the decorator pattern we introduced above, we can solve these two problems: reuse and coupling.