diff --git a/codeview/build.gradle b/codeview/build.gradle index 57383ae..90783af 100644 --- a/codeview/build.gradle +++ b/codeview/build.gradle @@ -26,11 +26,15 @@ dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) testCompile 'junit:junit:4.12' - compile 'com.android.support:appcompat-v7:24.1.1' - compile 'com.android.support:recyclerview-v7:24.1.1' - compile 'com.github.twalcari:java-prettify:1.2.2' compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + compile group: 'org.jetbrains.kotlin', name: 'kotlin-reflect', version: '1.0.3' + + compile 'com.android.support:appcompat-v7:24.2.0' + compile 'com.android.support:recyclerview-v7:24.2.0' + compile 'com.github.twalcari:java-prettify:1.2.2' } repositories { mavenCentral() } +buildscript { +} diff --git a/codeview/src/main/java/io/github/kbiakov/codeview/CodeView.kt b/codeview/src/main/java/io/github/kbiakov/codeview/CodeView.kt index dbde06e..3887a91 100644 --- a/codeview/src/main/java/io/github/kbiakov/codeview/CodeView.kt +++ b/codeview/src/main/java/io/github/kbiakov/codeview/CodeView.kt @@ -3,7 +3,6 @@ package io.github.kbiakov.codeview import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.content.Context -import android.os.Handler import android.support.v7.widget.LinearLayoutManager import android.support.v7.widget.RecyclerView import android.util.AttributeSet @@ -11,9 +10,11 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewPropertyAnimator import android.widget.RelativeLayout +import io.github.kbiakov.codeview.adapters.AbstractCodeAdapter +import io.github.kbiakov.codeview.Thread.delayed +import io.github.kbiakov.codeview.adapters.CodeWithNotesAdapter import io.github.kbiakov.codeview.highlight.ColorTheme import io.github.kbiakov.codeview.highlight.ColorThemeData -import io.github.kbiakov.codeview.highlight.color import java.util.* /** @@ -55,7 +56,15 @@ class CodeView : RelativeLayout { * (and awaiting for build) or view was built & code is presented. */ private var state: ViewState + set(newState) { + if (newState == ViewState.PRESENTED) + hidePlaceholder() + field = newState + } + /** + * Default constructor. + */ constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater inflater.inflate(R.layout.layout_code_view, this, true) @@ -69,13 +78,13 @@ class CodeView : RelativeLayout { rvCodeContent.layoutManager = LinearLayoutManager(context) rvCodeContent.isNestedScrollingEnabled = true - tasks = LinkedList() - state = ViewState.BUILD + + tasks = LinkedList() } /** - * Code view states. + * Code view state to control build flow. */ enum class ViewState { BUILD, @@ -84,22 +93,28 @@ class CodeView : RelativeLayout { } /** - * Public getter for accessing view state. - * It may be useful if code view state is unknown. - * If code view was built it is not safe to use operations chaining. + * Public getters for checking view state. + * May be useful when code view state is unknown. + * If view was built it is unsafe to use operations chaining. + * + * @return Result of state check */ - fun getState() = state + fun isBuilding() = state == ViewState.BUILD + fun isPrepared() = state == ViewState.PREPARE + fun isPresented() = state == ViewState.PRESENTED /** * Accessor/mutator to reduce frequently used actions. */ - var adapter: CodeContentAdapter + var adapter: AbstractCodeAdapter<*> get() { - return rvCodeContent.adapter as CodeContentAdapter + return rvCodeContent.adapter as AbstractCodeAdapter<*> } set(adapter) { - rvCodeContent.adapter = adapter - state = ViewState.PRESENTED + delayed { // to prevent UI overhead & initialization inconsistency + rvCodeContent.adapter = adapter + state = ViewState.PRESENTED + } } // - Build processor @@ -116,7 +131,7 @@ class CodeView : RelativeLayout { ViewState.BUILD -> tasks.add(task) ViewState.PREPARE -> - Thread.delayed(task) + delayed(body = task) ViewState.PRESENTED -> task() } @@ -202,7 +217,7 @@ class CodeView : RelativeLayout { ViewState.BUILD -> build(content) ViewState.PREPARE -> - Thread.delayed { + delayed { update(content) } ViewState.PRESENTED -> @@ -224,8 +239,8 @@ class CodeView : RelativeLayout { measurePlaceholder(linesCount) state = ViewState.PREPARE - Thread.delayed { - rvCodeContent.adapter = CodeContentAdapter(context, content) + delayed { + rvCodeContent.adapter = CodeWithNotesAdapter(context, content) processBuildTasks() setupShadows() hidePlaceholder() @@ -264,13 +279,13 @@ class CodeView : RelativeLayout { val lineHeight = dpToPx(context, 24) val topPadding = dpToPx(context, 8) - // double padding (top & bottom) for big view, one is enough for small + // double padding (top & bottom), one is enough for single line view val padding = (if (linesCount > 1) 2 else 1) * topPadding val height = linesCount * lineHeight + padding - vPlaceholder.layoutParams = RelativeLayout.LayoutParams( - RelativeLayout.LayoutParams.MATCH_PARENT, height) + vPlaceholder.layoutParams = LayoutParams( + LayoutParams.MATCH_PARENT, height) vPlaceholder.visibility = View.VISIBLE } @@ -296,16 +311,9 @@ class CodeView : RelativeLayout { * Provides listener to code line clicks. */ interface OnCodeLineClickListener { - fun onCodeLineClicked(n: Int) + fun onCodeLineClicked(n: Int, line: String) } -/** - * Extension for delayed block call. - * - * @param body Operation body - */ -fun Thread.delayed(body: () -> Unit) = Handler().postDelayed(body, 150) - /** * More readable form for animation listener (hi, iOS & Cocoa Touch!). * diff --git a/codeview/src/main/java/io/github/kbiakov/codeview/Utils.kt b/codeview/src/main/java/io/github/kbiakov/codeview/Utils.kt index ade5e3b..bfff99b 100644 --- a/codeview/src/main/java/io/github/kbiakov/codeview/Utils.kt +++ b/codeview/src/main/java/io/github/kbiakov/codeview/Utils.kt @@ -43,7 +43,7 @@ fun extractLines(source: String) = listOf(*source.split("\n").toTypedArray()) * @param content Source * @return Spanned HTML string */ -@Suppress("DEPRECATION") +@Suppress("deprecation") fun html(content: String): Spanned = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) Html.fromHtml(content, Html.FROM_HTML_MODE_LEGACY) @@ -69,6 +69,14 @@ object Thread { Handler(Looper.getMainLooper()).post(body) } + /** + * Delayed block call. + * + * @param body Operation body + * @param delayMs Delay in m + */ + fun delayed(delayMs: Long = 150, body: () -> Unit) = Handler().postDelayed(body, delayMs) + // - Extensions for block manipulations fun (() -> Unit).async(isAsync: Boolean = true) { diff --git a/codeview/src/main/java/io/github/kbiakov/codeview/CodeContentAdapter.kt b/codeview/src/main/java/io/github/kbiakov/codeview/adapters/AbstractCodeAdapter.kt similarity index 67% rename from codeview/src/main/java/io/github/kbiakov/codeview/CodeContentAdapter.kt rename to codeview/src/main/java/io/github/kbiakov/codeview/adapters/AbstractCodeAdapter.kt index 8a89324..34bbe04 100644 --- a/codeview/src/main/java/io/github/kbiakov/codeview/CodeContentAdapter.kt +++ b/codeview/src/main/java/io/github/kbiakov/codeview/adapters/AbstractCodeAdapter.kt @@ -1,26 +1,29 @@ -package io.github.kbiakov.codeview +package io.github.kbiakov.codeview.adapters import android.content.Context import android.support.v7.widget.RecyclerView import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.LinearLayout import android.widget.TextView +import io.github.kbiakov.codeview.* import io.github.kbiakov.codeview.classifier.CodeProcessor import io.github.kbiakov.codeview.highlight.* import io.github.kbiakov.codeview.Thread.async import io.github.kbiakov.codeview.Thread.ui import io.github.kbiakov.codeview.classifier.CodeClassifier +import io.github.kbiakov.codeview.OnCodeLineClickListener import java.util.* /** - * @class CodeContentAdapter + * @class AbstractCodeAdapter * - * Adapter for code view. + * Basic adapter for code view. * * @author Kirill Biakov */ -class CodeContentAdapter : RecyclerView.Adapter { +abstract class AbstractCodeAdapter : RecyclerView.Adapter { private val mContext: Context private var mContent: String @@ -38,15 +41,22 @@ class CodeContentAdapter : RecyclerView.Adapter { notifyDataSetChanged() } + internal var footerEntities: HashMap> + set(footerEntities) { + field = footerEntities + notifyDataSetChanged() + } + init { mLines = ArrayList() mDroppedLines = null isFullShowing = true colorTheme = ColorTheme.SOLARIZED_LIGHT.with() + footerEntities = HashMap() } /** - * Adapter constructor + * Adapter constructor. * * @param content Context * @param content Code content @@ -81,7 +91,7 @@ class CodeContentAdapter : RecyclerView.Adapter { * @param shortcutNote Note will shown below code for listing shortcut */ private fun initCodeContent(isShowFull: Boolean, - shortcutNote: String = mContext.getString(R.string.show_all)) { + shortcutNote: String = showAllBottomNote()) { var lines: MutableList = ArrayList(extractLines(mContent)) isFullShowing = isShowFull || lines.size <= mMaxLines // limit is not reached @@ -93,7 +103,7 @@ class CodeContentAdapter : RecyclerView.Adapter { mLines = lines } - // - User interaction interface + // - Adapter interface /** * Update code with new content. @@ -106,6 +116,18 @@ class CodeContentAdapter : RecyclerView.Adapter { notifyDataSetChanged() } + /** + * Add footer entity for code line. + * + * @param num Line number + * @param entity Footer entity + */ + fun addFooterEntity(num: Int, entity: T) { + val notes = footerEntities[num] ?: ArrayList() + footerEntities.put(num, notes + entity) + notifyDataSetChanged() + } + /** * Highlight code content. * @@ -134,7 +156,16 @@ class CodeContentAdapter : RecyclerView.Adapter { } } - // - Helpers + /** + * Mapper from entity to footer view. + * + * @param context Context + * @param entity Entity to init view + * @return Footer view + */ + abstract fun createFooter(context: Context, entity: T): View + + // - Helpers (for accessors) private fun updateContent(codeLines: List, onUpdated: () -> Unit) { ui { @@ -153,6 +184,8 @@ class CodeContentAdapter : RecyclerView.Adapter { updateContent(lines, onReady) } + private fun showAllBottomNote() = mContext.getString(R.string.show_all) + private fun monoTypeface() = MonoFontCache.getInstance(mContext).typeface // - View holder @@ -178,59 +211,88 @@ class CodeContentAdapter : RecyclerView.Adapter { holder.mItem = codeLine holder.itemView.setOnClickListener { - codeListener?.onCodeLineClicked(position) + codeListener?.onCodeLineClicked(position, codeLine) } - holder.tvLineContent.text = html(codeLine) + setupLine(position, codeLine, holder) + displayLineFooter(position, holder) + addExtraPadding(position, holder) + } + + override fun getItemCount() = mLines.size + + // - Helpers (for view holder) + + private fun setupLine(position: Int, line: String, holder: ViewHolder) { + holder.tvLineContent.text = html(line) + holder.tvLineContent.setTextColor(colorTheme.noteColor.color()) if (!isFullShowing && position == MAX_SHORTCUT_LINES) { holder.tvLineNum.textSize = 10f holder.tvLineNum.text = mContext.getString(R.string.dots) - holder.tvLineContent.setTextColor(colorTheme.noteColor.color()) } else { holder.tvLineNum.textSize = 12f holder.tvLineNum.text = "${position + 1}" } - - addExtraPadding(position, holder.itemView, holder.tvLineNum, holder.tvLineContent) } - override fun getItemCount() = mLines.size + private fun displayLineFooter(position: Int, holder: ViewHolder) { + val entityList = footerEntities[position] + + holder.llLineFooter.removeAllViews() + + entityList?.let { + holder.llLineFooter.visibility = if (it.isNotEmpty()) View.VISIBLE else View.GONE + + var isFirst = true + + it.forEach { entity -> + val footerView = createFooter(mContext, entity) + val dp8 = dpToPx(mContext, 8) + footerView.setPadding(dpToPx(mContext, 46), if (isFirst) dp8 else 0, dp8, dp8) - private fun addExtraPadding(position: Int, itemView: View, - tvLineNum: TextView, tvLineContent: TextView) { + holder.llLineFooter.addView(footerView) + + isFirst = false + } + } + } + + private fun addExtraPadding(position: Int, holder: ViewHolder) { val dp8 = dpToPx(mContext, 8) val isFirst = position == 0 val isLast = position == itemCount - 1 if (isFirst || isLast) { - itemView.layoutParams.height = dp8 * 4 - val topPadding = if (isFirst) dp8 else 0 val bottomPadding = if (isLast) dp8 else 0 - tvLineNum.setPadding(0, topPadding, 0, bottomPadding) - tvLineContent.setPadding(0, topPadding, 0, bottomPadding) + holder.tvLineNum.setPadding(0, topPadding, 0, bottomPadding) + holder.tvLineContent.setPadding(0, topPadding, 0, bottomPadding) } else { - itemView.layoutParams.height = dp8 * 3 - - tvLineNum.setPadding(0, 0, 0, 0) - tvLineContent.setPadding(0, 0, 0, 0) + holder.tvLineNum.setPadding(0, 0, 0, 0) + holder.tvLineContent.setPadding(0, 0, 0, 0) } } companion object { - private const val MAX_SHORTCUT_LINES = 6 + internal const val MAX_SHORTCUT_LINES = 6 } + /** + * View holder for code adapter. + * Stores all views related to code line layout. + */ class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { var tvLineNum: TextView var tvLineContent: TextView + var llLineFooter: LinearLayout var mItem: String? = null init { tvLineNum = itemView.findViewById(R.id.tv_line_num) as TextView tvLineContent = itemView.findViewById(R.id.tv_line_content) as TextView + llLineFooter = itemView.findViewById(R.id.ll_line_footer) as LinearLayout } override fun toString() = "${super.toString()} '$mItem'" diff --git a/codeview/src/main/java/io/github/kbiakov/codeview/adapters/CodeWithNotesAdapter.kt b/codeview/src/main/java/io/github/kbiakov/codeview/adapters/CodeWithNotesAdapter.kt new file mode 100644 index 0000000..a53d7fb --- /dev/null +++ b/codeview/src/main/java/io/github/kbiakov/codeview/adapters/CodeWithNotesAdapter.kt @@ -0,0 +1,31 @@ +package io.github.kbiakov.codeview.adapters + +import android.content.Context +import io.github.kbiakov.codeview.highlight.color +import io.github.kbiakov.codeview.views.LineNoteView + +/** + * @class CodeWithNotesAdapter + * + * Default code content adapter. + * + * @author Kirill Biakov + */ +class CodeWithNotesAdapter : AbstractCodeAdapter { + /** + * Default constructor. + */ + constructor(context: Context, content: String) : super(context, content) + + /** + * Create footer view. + * + * @param context Context + * @param entity Note content + */ + override fun createFooter(context: Context, entity: String) = + LineNoteView.create(context, + text = entity, + bgColor = colorTheme.bgNum.color(), + textColor = colorTheme.noteColor.color()) +} diff --git a/codeview/src/main/java/io/github/kbiakov/codeview/highlight/CodeHighlighter.kt b/codeview/src/main/java/io/github/kbiakov/codeview/highlight/CodeHighlighter.kt index ca8fcc9..54af59d 100644 --- a/codeview/src/main/java/io/github/kbiakov/codeview/highlight/CodeHighlighter.kt +++ b/codeview/src/main/java/io/github/kbiakov/codeview/highlight/CodeHighlighter.kt @@ -116,6 +116,24 @@ enum class ColorTheme( bgNum = 0xEEE8D5, noteColor = 0x657B83), + MONOKAI( + syntaxColors = SyntaxColors( + type = 0xA7E22E, + keyword = 0xFA2772, + literal = 0x66D9EE, + comment = 0x76715E, + string = 0xE6DB74, + punctuation = 0xC1C1C1, + plain = 0xF8F8F0, + tag = 0xF92672, + declaration = 0xFA2772, + attrName = 0xA6E22E, + attrValue = 0xE6DB74), + numColor = 0x48483E, + bgContent = 0x272822, + bgNum = 0x272822, + noteColor = 0xCFD0C2), + DEFAULT( numColor = 0x99A8B7, bgContent = 0xE9EDF4, diff --git a/codeview/src/main/java/io/github/kbiakov/codeview/highlight/MonoFontCache.java b/codeview/src/main/java/io/github/kbiakov/codeview/highlight/MonoFontCache.java index fc49790..50cd2d4 100644 --- a/codeview/src/main/java/io/github/kbiakov/codeview/highlight/MonoFontCache.java +++ b/codeview/src/main/java/io/github/kbiakov/codeview/highlight/MonoFontCache.java @@ -3,12 +3,14 @@ import android.content.Context; import android.graphics.Typeface; +import io.github.kbiakov.codeview.adapters.AbstractCodeAdapter; + /** * @class MonoFontCache * * Simple font cache. * - * @see io.github.kbiakov.codeview.CodeContentAdapter + * @see AbstractCodeAdapter * @author Kirill Biakov */ public class MonoFontCache { diff --git a/codeview/src/main/java/io/github/kbiakov/codeview/BidirectionalScrollView.kt b/codeview/src/main/java/io/github/kbiakov/codeview/views/BidirectionalScrollView.kt similarity index 95% rename from codeview/src/main/java/io/github/kbiakov/codeview/BidirectionalScrollView.kt rename to codeview/src/main/java/io/github/kbiakov/codeview/views/BidirectionalScrollView.kt index 373b0a6..6eedfb1 100644 --- a/codeview/src/main/java/io/github/kbiakov/codeview/BidirectionalScrollView.kt +++ b/codeview/src/main/java/io/github/kbiakov/codeview/views/BidirectionalScrollView.kt @@ -1,12 +1,12 @@ -package io.github.kbiakov.codeview +package io.github.kbiakov.codeview.views import android.content.Context import android.util.AttributeSet import android.view.MotionEvent import android.view.View import android.view.View.MeasureSpec.makeMeasureSpec -import android.view.ViewGroup import android.widget.HorizontalScrollView +import io.github.kbiakov.codeview.dpToPx /** * @class BidirectionalScrollView @@ -77,7 +77,7 @@ class BidirectionalScrollView : HorizontalScrollView { override fun measureChildWithMargins(child: View, parentWidthMeasureSpec: Int, widthUsed: Int, parentHeightMeasureSpec: Int, heightUsed: Int) { - val params = child.layoutParams as ViewGroup.MarginLayoutParams + val params = child.layoutParams as MarginLayoutParams val childWidthMeasureSpec = makeMeasureSpec( params.leftMargin + params.rightMargin, MeasureSpec.UNSPECIFIED) diff --git a/codeview/src/main/java/io/github/kbiakov/codeview/views/LineNoteView.kt b/codeview/src/main/java/io/github/kbiakov/codeview/views/LineNoteView.kt new file mode 100644 index 0000000..609d294 --- /dev/null +++ b/codeview/src/main/java/io/github/kbiakov/codeview/views/LineNoteView.kt @@ -0,0 +1,35 @@ +package io.github.kbiakov.codeview.views + +import android.content.Context +import android.widget.TextView + +/** + * @class LineNoteView + * + * Note view for code line. Default footer view. + * + * @author Kirill Biakov + */ +class LineNoteView(context: Context?) : TextView(context) { + + companion object Factory { + /** + * Simple factory method to create note view. + * + * @param context Context + * @param text Note text + * @param bgColor Background color + * @param textColor Text Color + * @return Created line note view + */ + fun create(context: Context, text: String, bgColor: Int, textColor: Int): LineNoteView { + val noteView = LineNoteView(context) + noteView.textSize = 12f + noteView.text = text + noteView.setTextColor(textColor) + noteView.setBackgroundColor(bgColor) + + return noteView + } + } +} diff --git a/codeview/src/main/res/layout/item_code_line.xml b/codeview/src/main/res/layout/item_code_line.xml index 9dc6622..0185388 100644 --- a/codeview/src/main/res/layout/item_code_line.xml +++ b/codeview/src/main/res/layout/item_code_line.xml @@ -7,7 +7,7 @@ + + diff --git a/codeview/src/main/res/layout/layout_code_view.xml b/codeview/src/main/res/layout/layout_code_view.xml index ee66263..7407af2 100644 --- a/codeview/src/main/res/layout/layout_code_view.xml +++ b/codeview/src/main/res/layout/layout_code_view.xml @@ -5,7 +5,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content"> - - + { + + public CustomAdapter(@NotNull Context context, @NotNull String content) { + super(context, content, true, 10, context.getString(R.string.show_all), null); + } + + @NotNull + @Override + public View createFooter(@NotNull Context context, CustomModel entity) { + View footerView = LayoutInflater.from(context).inflate(R.layout.custom_footer, null); + ((TextView) footerView.findViewById(R.id.tv_footer_title)).setText(entity.firstName); + ((TextView) footerView.findViewById(R.id.tv_footer_description)).setText(entity.lastName); + return footerView; + } + + public static class CustomModel { + private String firstName; + private String lastName; + + public CustomModel(String firstName, String lastName) { + this.firstName = firstName; + this.lastName = lastName; + } + + public String getFirstName() { + return firstName; + } + + public String getLastName() { + return lastName; + } + } +} diff --git a/example/src/main/java/io/github/kbiakov/codeviewexample/ListingsActivity.java b/example/src/main/java/io/github/kbiakov/codeviewexample/ListingsActivity.java index 036c3fa..548eaa9 100644 --- a/example/src/main/java/io/github/kbiakov/codeviewexample/ListingsActivity.java +++ b/example/src/main/java/io/github/kbiakov/codeviewexample/ListingsActivity.java @@ -6,8 +6,12 @@ import android.support.v7.app.AppCompatActivity; import android.util.Log; -import io.github.kbiakov.codeview.CodeView; +import org.jetbrains.annotations.NotNull; + +import io.github.kbiakov.codeview.adapters.CodeWithNotesAdapter; import io.github.kbiakov.codeview.highlight.ColorTheme; +import io.github.kbiakov.codeview.CodeView; +import io.github.kbiakov.codeview.OnCodeLineClickListener; public class ListingsActivity extends AppCompatActivity { @@ -16,19 +20,49 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_listings); - //int myColor = ContextCompat.getColor(this, R.color.code_content_background); + int myColor = ContextCompat.getColor(this, R.color.code_num); CodeView codeView = (CodeView) findViewById(R.id.code_view); - // use chaining to build view + /** + * 1: Default adapter with chaining build flow + */ + + // use chaining to build view with default adapter codeView.highlightCode("js") - .setColorTheme(ColorTheme.SOLARIZED_LIGHT) + .setColorTheme(ColorTheme.DEFAULT.withBgContent(myColor)) .setCodeContent(getString(R.string.listing_js)); + /** + * 2: Updating built view + */ + // do not use chaining for built view - // (you can, but follow it should be performed sequentially) + // (you can, but it should be performed sequentially) codeView.setCodeContent(getString(R.string.listing_java)); - codeView.setColorTheme(ColorTheme.DEFAULT); + codeView.setColorTheme(ColorTheme.SOLARIZED_LIGHT); codeView.highlightCode("java"); + codeView.setCodeListener(new OnCodeLineClickListener() { + @Override + public void onCodeLineClicked(int n, @NotNull String line) { + Log.i("ListingsActivity", "On " + (n + 1) + " line clicked"); + } + }); + + /** + * 3: Custom adapter with footer views + */ + + final CustomAdapter adapter = new CustomAdapter(this, getString(R.string.listing_py)); + + codeView.setAdapter(adapter); + codeView.setColorTheme(ColorTheme.MONOKAI); + codeView.highlightCode("python"); + codeView.setCodeListener(new OnCodeLineClickListener() { + @Override + public void onCodeLineClicked(int n, @NotNull String line) { + adapter.addFooterEntity(n, new CustomAdapter.CustomModel("Line " + (n + 1), line)); + } + }); } } diff --git a/example/src/main/res/layout/custom_footer.xml b/example/src/main/res/layout/custom_footer.xml new file mode 100644 index 0000000..1ead22a --- /dev/null +++ b/example/src/main/res/layout/custom_footer.xml @@ -0,0 +1,22 @@ + + + + + + + +