11package io.github.kbiakov.codeview
22
33import android.content.Context
4+ import android.graphics.drawable.Drawable
5+ import android.graphics.drawable.GradientDrawable
46import android.support.v7.widget.LinearLayoutManager
57import android.support.v7.widget.RecyclerView
68import android.util.AttributeSet
@@ -10,6 +12,8 @@ import io.github.kbiakov.codeview.Thread.delayed
1012import io.github.kbiakov.codeview.adapters.AbstractCodeAdapter
1113import io.github.kbiakov.codeview.adapters.CodeWithNotesAdapter
1214import io.github.kbiakov.codeview.adapters.Options
15+ import io.github.kbiakov.codeview.highlight.ColorThemeData
16+ import io.github.kbiakov.codeview.highlight.color
1317
1418/* *
1519 * @class CodeView
@@ -20,59 +24,58 @@ import io.github.kbiakov.codeview.adapters.Options
2024 */
2125class CodeView (context : Context , attrs : AttributeSet ) : RelativeLayout(context, attrs) {
2226
23- private val vShadowRight: View
24- private val vShadowBottomLine: View
25- private val vShadowBottomContent: View
26-
2727 private val vCodeList: RecyclerView
28+ private val vShadows: Map <ShadowPosition , View >
2829
2930 /* *
3031 * Primary constructor.
3132 */
3233 init {
33- val isAnimateOnStart = visibility == VISIBLE && { ctx : Context , ats : AttributeSet ->
34- val a = ctx.theme.obtainStyledAttributes(ats, R .styleable. CodeView , 0 , 0 )
34+ inflate(context, R .layout.layout_code_view, this )
35+ checkStartAnimation(attrs )
3536
36- try {
37- a.getBoolean(R .styleable.CodeView_animateOnStart , true )
38- } finally {
39- a.recycle()
40- }
41- }(context, attrs)
37+ vCodeList = findViewById(R .id.rv_code_content) as RecyclerView
38+ vCodeList.layoutManager = LinearLayoutManager (context)
39+ vCodeList.isNestedScrollingEnabled = true
4240
43- alpha = if (isAnimateOnStart) 0f else Consts .ALPHA
41+ vShadows = mapOf (
42+ ShadowPosition .RightBorder to R .id.shadow_right_border,
43+ ShadowPosition .NumBottom to R .id.shadow_num_bottom,
44+ ShadowPosition .ContentBottom to R .id.shadow_content_bottom
45+ ).mapValues { findViewById(it.value) }
46+ }
4447
45- inflate(context, R .layout.layout_code_view, this )
48+ private fun checkStartAnimation (attrs : AttributeSet ) {
49+ if (visibility == VISIBLE && attrs.isAnimateOnStart(context)) {
50+ alpha = Const .Alpha .Invisible
4651
47- if (isAnimateOnStart)
4852 animate()
49- .setDuration(Consts .DELAY * 5 )
50- .alpha(Consts .ALPHA )
51-
52- // TODO: add shadow color customization
53- vShadowRight = findViewById(R .id.v_shadow_right)
54- vShadowBottomLine = findViewById(R .id.v_shadow_bottom_line)
55- vShadowBottomContent = findViewById(R .id.v_shadow_bottom_content)
53+ .setDuration(Const .DefaultDelay * 5 )
54+ .alpha(Const .Alpha .Initial )
55+ } else
56+ alpha = Const .Alpha .Initial
57+ }
5658
57- vCodeList = findViewById(R .id.rv_code_content) as RecyclerView
58- vCodeList.layoutManager = LinearLayoutManager (context)
59- vCodeList.isNestedScrollingEnabled = true
59+ private fun AbstractCodeAdapter <* >.checkHighlightAnimation (action : () -> Unit ) {
60+ if (options.animateOnHighlight) {
61+ animate()
62+ .setDuration(Const .DefaultDelay * 2 )
63+ .alpha(Const .Alpha .AlmostInvisible )
64+ delayed {
65+ animate().alpha(Const .Alpha .Visible )
66+ action()
67+ }
68+ } else action()
6069 }
6170
6271 /* *
6372 * Highlight code with defined programming language.
6473 * It holds the placeholder on view until code is not highlighted.
6574 */
6675 private fun highlight () {
67- getAdapter()?.highlight {
68-
69- animate()
70- .setDuration(Consts .DELAY * 2 )
71- .alpha(.1f )
72-
73- delayed {
74- animate().alpha(1f )
75- getAdapter()?.notifyDataSetChanged()
76+ getAdapter()?.apply {
77+ highlight {
78+ checkHighlightAnimation(this ::notifyDataSetChanged)
7679 }
7780 }
7881 }
@@ -81,14 +84,15 @@ class CodeView(context: Context, attrs: AttributeSet) : RelativeLayout(context,
8184 * Border shadows will shown if full listing presented.
8285 * It helps to see what part of code is scrolled & hidden.
8386 *
84- * @param isShadows Is shadows needed
87+ * @param isVisible Is shadows visible
8588 */
86- private fun setupShadows (isShadows : Boolean ) {
87- val visibility = if (isShadows) VISIBLE else GONE
88-
89- vShadowRight.visibility = visibility
90- vShadowBottomLine.visibility = visibility
91- vShadowBottomContent.visibility = visibility
89+ fun setupShadows (isVisible : Boolean ) {
90+ val visibility = if (isVisible) VISIBLE else GONE
91+ val theme = getOptionsOrDefault().theme
92+ vShadows.forEach { (pos, view) ->
93+ view.visibility = visibility
94+ view.setSafeBackground(pos.createShadow(theme))
95+ }
9296 }
9397
9498 // - Initialization
@@ -112,7 +116,6 @@ class CodeView(context: Context, attrs: AttributeSet) : RelativeLayout(context,
112116 */
113117 fun setAdapter (adapter : AbstractCodeAdapter <* >) {
114118 vCodeList.adapter = adapter
115- setupShadows(adapter.options.shadows)
116119 highlight()
117120 }
118121
@@ -121,7 +124,7 @@ class CodeView(context: Context, attrs: AttributeSet) : RelativeLayout(context,
121124 /* *
122125 * View options accessor.
123126 */
124- fun getOptions (): Options ? = getAdapter()?.options
127+ fun getOptions () = getAdapter()?.options
125128 fun getOptionsOrDefault () = getOptions() ? : Options (context)
126129
127130 /* *
@@ -130,10 +133,14 @@ class CodeView(context: Context, attrs: AttributeSet) : RelativeLayout(context,
130133 * @param options Options
131134 */
132135 fun updateOptions (options : Options ) {
133- if (getAdapter() == null )
134- setOptions(options)
135- else
136- getAdapter()!! .options = options
136+ getAdapter() ? : setOptions(options)
137+ getAdapter()?.options = options
138+ setupShadows(options.shadows)
139+ }
140+
141+ fun updateOptions (body : Options .() -> Unit ) {
142+ val options = getOptions() ? : getOptionsOrDefault()
143+ updateOptions(options.apply (body))
137144 }
138145
139146 // - Adapter
@@ -169,7 +176,7 @@ class CodeView(context: Context, attrs: AttributeSet) : RelativeLayout(context,
169176 */
170177 fun setCode (code : String ) {
171178 getAdapter() ? : prepare()
172- getAdapter()!! .updateCode(code)
179+ getAdapter()? .updateCode(code)
173180 }
174181
175182 /* *
@@ -185,7 +192,40 @@ class CodeView(context: Context, attrs: AttributeSet) : RelativeLayout(context,
185192 fun setCode (code : String , language : String ) {
186193 val options = getOptionsOrDefault()
187194 updateOptions(options.withLanguage(language))
188- getAdapter()!! .updateCode(code)
195+ getAdapter()?.updateCode(code)
196+ }
197+
198+ companion object {
199+
200+ private fun AttributeSet.isAnimateOnStart (context : Context ): Boolean {
201+ context.theme.obtainStyledAttributes(this , R .styleable.CodeView , 0 , 0 ).apply {
202+ val flag = getBoolean(R .styleable.CodeView_animateOnStart , false )
203+ recycle()
204+ return @isAnimateOnStart flag
205+ }
206+ return false
207+ }
208+
209+ private fun View.setSafeBackground (newBackground : Drawable ) {
210+ if (android.os.Build .VERSION .SDK_INT >= android.os.Build .VERSION_CODES .JELLY_BEAN ) {
211+ background = newBackground
212+ }
213+ }
214+ }
215+
216+ private enum class ShadowPosition {
217+ RightBorder ,
218+ NumBottom ,
219+ ContentBottom ;
220+
221+ fun createShadow (theme : ColorThemeData ) = when (this ) {
222+ RightBorder -> GradientDrawable .Orientation .LEFT_RIGHT to theme.bgContent
223+ NumBottom -> GradientDrawable .Orientation .TOP_BOTTOM to theme.bgNum
224+ ContentBottom -> GradientDrawable .Orientation .TOP_BOTTOM to theme.bgContent
225+ }.let {
226+ val colors = arrayOf(android.R .color.transparent, it.second)
227+ GradientDrawable (it.first, colors.map(Int ::color).toIntArray())
228+ }
189229 }
190230}
191231
0 commit comments