diff --git a/README.md b/README.md index daccc1d..cd5527b 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,20 @@ StickyTimeLine is timeline view for android. -## What's New in 0.0.20? :tada: -- change DotDrawable for each row of items[(#16)](https://github.com/sangcomz/StickyTimeLine/issues/16) -- add java example[(#9)](https://github.com/sangcomz/StickyTimeLine/issues/9) -- migrate to AndroidX +## What's New in 0.1.0? :tada: +- Add Horizontal Mode +- Fix DEMO app +- change attribute name + - `timeLineCircleColor` -> `timeLineDotColor` + - `timeLineCircleStrokeColor` -> `timeLineDotStrokeColor` + + ## Result Screen + + Feel free to send me a pull request with your app and I'll link you here: + + | Sample

Get it on Google Play

| AlleysMap

Get it on Google Play

| + |:---------------------------------:|:--------------------------------:| + | || ## How to Use @@ -19,7 +29,7 @@ StickyTimeLine is timeline view for android. dependencies { //StickyTimeLine v0.0.20 and above only supports projects that have been migrated to androidx. - compile 'com.github.sangcomz:StickyTimeLine:v0.0.20' + compile 'com.github.sangcomz:StickyTimeLine:v0.1.0' } ``` ### Usage @@ -189,28 +199,20 @@ public class JavaExampleActivity extends AppCompatActivity { #### attribute -| Method Name | Description | Default Value | -|:------------------------:|------------------------------------------------|:-------------:| -| sectionBackgroundColor | To change section section background color | #f9f9f9 | -| sectionTitleTextColor | To change section title color | #414fca | -| sectionSubTitleTextColor | To change section sub title color | #d16767 | -| timeLineColor | To change line color in timeline | #51ae45 | -| timeLineCircleColor | To change circle color in timeline | #51ae45 | -| timeLineCircleStrokeColor| To change circle stroke color in timeline | #f9f9f9 | -| sectionTitleTextSize | To change section title text size | 14sp | -| sectionSubTitleTextSize | To change section sub title text size | 12sp | -| timeLineWidth | To change line width in timeline | 4dp | -| isSticky | To change Sticky functionality in the Timeline | true | -| customDotDrawable | To change the circle to custom drawable | null | - -## Result Screen - -Feel free to send me a pull request with your app and I'll link you here: - -| Project Name | Result Screen | -|:---------:|---| -| Sample

Get it on Google Play

| | -| AlleysMap

Get it on Google Play

| | +| Method Name | Description | Default Value | +|:------------------------:|-------------------------------------------------------|:-------------:| +| sectionBackgroundColor | To change section section background color | #f9f9f9 | +| sectionTitleTextColor | To change section title color | #414fca | +| sectionSubTitleTextColor | To change section sub title color | #d16767 | +| timeLineColor | To change line color in timeline | #51ae45 | +| timeLineDotColor | To change dot color in timeline | #51ae45 | +| timeLineCircleStrokeColor| To change dot stroke color in timeline | #f9f9f9 | +| sectionTitleTextSize | To change section title text size | 14sp | +| sectionSubTitleTextSize | To change section sub title text size | 12sp | +| timeLineWidth | To change line width in timeline | 4dp | +| isSticky | To change Sticky functionality in the Timeline | true | +| customDotDrawable | To change the circle to custom drawable | null | +|sectionBackgroundColorMode| To change section background area(for horizontal mode)| MODE_FULL | # Contribute We welcome any contributions. diff --git a/app/build.gradle b/app/build.gradle index 6d49d8c..19f76f9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -44,15 +44,15 @@ android { dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation "androidx.appcompat:appcompat:$support_version" + implementation "androidx.appcompat:appcompat:1.2.0" implementation "androidx.constraintlayout:constraintlayout:$constraint_version" - implementation "androidx.recyclerview:recyclerview:$support_version" + implementation "androidx.recyclerview:recyclerview:1.1.0" implementation 'androidx.cardview:cardview:1.0.0' implementation project(':stickytimelineview') - implementation "androidx.appcompat:appcompat:$support_version" - implementation 'androidx.constraintlayout:constraintlayout:1.1.3' - testImplementation 'junit:junit:4.12' - androidTestImplementation 'androidx.test:runner:1.1.2-alpha02' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0-alpha02' + implementation "androidx.appcompat:appcompat:1.2.0" + implementation 'androidx.constraintlayout:constraintlayout:2.0.1' + testImplementation 'junit:junit:4.13' + androidTestImplementation 'androidx.test:runner:1.3.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' } diff --git a/app/src/main/java/xyz/sangcomz/stickytimeline/JavaExampleActivity.java b/app/src/main/java/xyz/sangcomz/stickytimeline/JavaExampleActivity.java index e933a10..8ccddf9 100644 --- a/app/src/main/java/xyz/sangcomz/stickytimeline/JavaExampleActivity.java +++ b/app/src/main/java/xyz/sangcomz/stickytimeline/JavaExampleActivity.java @@ -3,16 +3,17 @@ import android.graphics.drawable.Drawable; import android.os.Bundle; -import org.jetbrains.annotations.Nullable; - -import java.util.List; - import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.content.res.AppCompatResources; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; -import xyz.sangcomz.stickytimelineview.RecyclerSectionItemDecoration; + +import org.jetbrains.annotations.Nullable; + +import java.util.List; + import xyz.sangcomz.stickytimelineview.TimeLineRecyclerView; +import xyz.sangcomz.stickytimelineview.callback.SectionCallback; import xyz.sangcomz.stickytimelineview.model.SectionInfo; public class JavaExampleActivity extends AppCompatActivity { @@ -25,7 +26,7 @@ protected void onCreate(Bundle savedInstanceState) { setContentView(R.layout.activity_main); initDrawable(); - TimeLineRecyclerView recyclerView = findViewById(R.id.recycler_view); + TimeLineRecyclerView recyclerView = findViewById(R.id.vertical_recycler_view); recyclerView.setLayoutManager(new LinearLayoutManager(this, RecyclerView.VERTICAL, @@ -35,11 +36,11 @@ protected void onCreate(Bundle savedInstanceState) { recyclerView.addItemDecoration(getSectionCallback(singerList)); - recyclerView.setAdapter(new SingerAdapter(getLayoutInflater(), singerList, R.layout.recycler_row)); + recyclerView.setAdapter(new SingerAdapter(getLayoutInflater(), singerList, R.layout.recycler_vertical_row)); } - private RecyclerSectionItemDecoration.SectionCallback getSectionCallback(final List singerList) { - return new RecyclerSectionItemDecoration.SectionCallback() { + private SectionCallback getSectionCallback(final List singerList) { + return new SectionCallback() { @Nullable @Override diff --git a/app/src/main/java/xyz/sangcomz/stickytimeline/MainActivity.kt b/app/src/main/java/xyz/sangcomz/stickytimeline/MainActivity.kt index 752b35f..7eb36e6 100644 --- a/app/src/main/java/xyz/sangcomz/stickytimeline/MainActivity.kt +++ b/app/src/main/java/xyz/sangcomz/stickytimeline/MainActivity.kt @@ -6,8 +6,9 @@ import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.content.res.AppCompatResources import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView -import xyz.sangcomz.stickytimelineview.RecyclerSectionItemDecoration -import xyz.sangcomz.stickytimelineview.TimeLineRecyclerView +import kotlinx.android.synthetic.main.activity_main.* +import xyz.sangcomz.stickytimelineview.callback.SectionCallback +import xyz.sangcomz.stickytimelineview.decoration.VerticalSectionItemDecoration import xyz.sangcomz.stickytimelineview.model.SectionInfo class MainActivity : AppCompatActivity() { @@ -28,32 +29,62 @@ class MainActivity : AppCompatActivity() { AppCompatResources.getDrawable(this@MainActivity, R.drawable.ic_solo) } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) + initVerticalRecyclerView() + initHorizontalRecyclerView() + } - val recyclerView: TimeLineRecyclerView = findViewById(R.id.recycler_view) + private fun initVerticalRecyclerView() { + val singerList = getSingerList() + vertical_recycler_view.adapter = SingerAdapter( + layoutInflater, + singerList, + R.layout.recycler_vertical_row + ) //Currently only LinearLayoutManager is supported. - recyclerView.layoutManager = LinearLayoutManager( + vertical_recycler_view.layoutManager = LinearLayoutManager( this, RecyclerView.VERTICAL, false ) - //Get data - val singerList = getSingerList() - + vertical_recycler_view.addItemDecoration(getSectionCallback(singerList)) + } - //Add RecyclerSectionItemDecoration.SectionCallback - recyclerView.addItemDecoration(getSectionCallback(singerList)) + private fun initHorizontalRecyclerView() { + val singerList = getSingerList() + horizontal_recycler_view.adapter = SingerAdapter( + layoutInflater, + singerList, + R.layout.recycler_horizontal_row + ) - //Set Adapter - recyclerView.adapter = SingerAdapter( + horizontal_recycler_view2.adapter = SingerAdapter( layoutInflater, singerList, - R.layout.recycler_row + R.layout.recycler_horizontal_row + ) + + + //Currently only LinearLayoutManager is supported. + horizontal_recycler_view.layoutManager = LinearLayoutManager( + this, + RecyclerView.HORIZONTAL, + false + ) + + horizontal_recycler_view2.layoutManager = LinearLayoutManager( + this, + RecyclerView.HORIZONTAL, + false ) + + horizontal_recycler_view.addItemDecoration(getSectionCallback(singerList)) + horizontal_recycler_view2.addItemDecoration(getSectionCallback(singerList)) } //Get data method @@ -61,8 +92,8 @@ class MainActivity : AppCompatActivity() { //Get SectionCallback method - private fun getSectionCallback(singerList: List): RecyclerSectionItemDecoration.SectionCallback { - return object : RecyclerSectionItemDecoration.SectionCallback { + private fun getSectionCallback(singerList: List): SectionCallback { + return object : SectionCallback { //In your data, implement a method to determine if this is a section. override fun isSection(position: Int): Boolean = singerList[position].debuted != singerList[position - 1].debuted diff --git a/app/src/main/java/xyz/sangcomz/stickytimeline/SingerRepo.kt b/app/src/main/java/xyz/sangcomz/stickytimeline/SingerRepo.kt index cbc3386..77dd86e 100755 --- a/app/src/main/java/xyz/sangcomz/stickytimeline/SingerRepo.kt +++ b/app/src/main/java/xyz/sangcomz/stickytimeline/SingerRepo.kt @@ -11,23 +11,23 @@ class SingerRepo { val singerList: List get() { val singerList = ArrayList() - singerList.add(Singer("Solo", "1995.04", "Lim Chang Jung")) + singerList.add(Singer("Solo", "1995.04", "Lim ChangJung")) singerList.add(Singer("FIN.K.L", "1998.05", "Lee Jin")) - singerList.add(Singer("FIN.K.L", "1998.05", "Sung Yu Ri")) - singerList.add(Singer("FIN.K.L", "1998.05", "Oak Joo Hyun")) - singerList.add(Singer("FIN.K.L", "1998.05", "Lee Hyo Ri")) + singerList.add(Singer("FIN.K.L", "1998.05", "Sung YuRi")) + singerList.add(Singer("FIN.K.L", "1998.05", "Oak JooHyun")) + singerList.add(Singer("FIN.K.L", "1998.05", "Lee HyoRi")) - singerList.add(Singer("Solo", "1999.04", "Kim Bumsoo")) + singerList.add(Singer("Solo", "1999.04", "Kim BumSoo")) - singerList.add(Singer("Solo", "1999.11", "Park Hyo Shin")) - singerList.add(Singer("Solo", "1999.11", "Lee Soo Young")) - singerList.add(Singer("Solo", "2000.11", "Sung Si Kyung")) + singerList.add(Singer("Solo", "1999.11", "Park HyoShin")) + singerList.add(Singer("Solo", "1999.11", "Lee SooYoung")) + singerList.add(Singer("Solo", "2000.11", "Sung SiKyung")) singerList.add(Singer("Buzz", "2003.10", "Kim Yeah")) - singerList.add(Singer("Buzz", "2003.10", "Yun Woo Hyun")) - singerList.add(Singer("Buzz", "2003.10", "Sin Jun Ki")) - singerList.add(Singer("Buzz", "2003.10", "Min Kyung Hoon")) + singerList.add(Singer("Buzz", "2003.10", "Yun WooHyun")) + singerList.add(Singer("Buzz", "2003.10", "Sin JunKi")) + singerList.add(Singer("Buzz", "2003.10", "Min KyungHoon")) singerList.add(Singer("Solo", "2006.06", "Yunha")) @@ -42,17 +42,17 @@ class SingerRepo { singerList.add(Singer("Wanna One", "2017.08", "Kang Daniel")) singerList.add(Singer("Wanna One", "2017.08", "Lai Kuan Lin")) - singerList.add(Singer("Wanna One", "2017.08", "Ong Seong Wu")) - singerList.add(Singer("Wanna One", "2017.08", "Ha Sung Woon")) - singerList.add(Singer("Wanna One", "2017.08", "Yoon Ji Sung")) - singerList.add(Singer("Wanna One", "2017.08", "Park Woo Jin")) - singerList.add(Singer("Wanna One", "2017.08", "Lee Dae Hwi")) - singerList.add(Singer("Wanna One", "2017.08", "Kim Jae Hwan")) - singerList.add(Singer("Wanna One", "2017.08", "Bae Jin Young")) - singerList.add(Singer("Wanna One", "2017.08", "Hwang Min Hyun")) - singerList.add(Singer("Wanna One", "2017.08", "Park Ji Hoon")) - - singerList.add(Singer("Solo", "2017.11", "Woo Won Jae")) + singerList.add(Singer("Wanna One", "2017.08", "Ong SeongWu")) + singerList.add(Singer("Wanna One", "2017.08", "Ha SungWoon")) + singerList.add(Singer("Wanna One", "2017.08", "Yoon JiSung")) + singerList.add(Singer("Wanna One", "2017.08", "Park WooJin")) + singerList.add(Singer("Wanna One", "2017.08", "Lee DaeHwi")) + singerList.add(Singer("Wanna One", "2017.08", "Kim JaeHwan")) + singerList.add(Singer("Wanna One", "2017.08", "Bae JinYoung")) + singerList.add(Singer("Wanna One", "2017.08", "Hwang MinHyun")) + singerList.add(Singer("Wanna One", "2017.08", "Park JiHoon")) + + singerList.add(Singer("Solo", "2017.11", "Woo WonJae")) return singerList } diff --git a/app/src/main/res/drawable/ic_solo.xml b/app/src/main/res/drawable/ic_solo.xml index cb10b5f..0341836 100644 --- a/app/src/main/res/drawable/ic_solo.xml +++ b/app/src/main/res/drawable/ic_solo.xml @@ -1,7 +1,14 @@ - - + - + android:strokeWidth="10" + android:strokeColor="#F9F9F9" /> + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 475f2c1..7c7dd4f 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -6,9 +6,92 @@ android:layout_height="match_parent" tools:context="xyz.sangcomz.stickytimeline.MainActivity"> + + + + + + + + + android:background="#3F51B5" + android:paddingStart="8dp" + android:paddingTop="16dp" + android:paddingEnd="8dp" + android:paddingBottom="16dp" + android:text="HORIZONTAL" + android:textColor="#ffffff" + android:textSize="24dp" + android:textStyle="bold" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/guideline" /> + + + + + diff --git a/app/src/main/res/layout/recycler_horizontal_row.xml b/app/src/main/res/layout/recycler_horizontal_row.xml new file mode 100755 index 0000000..45b8372 --- /dev/null +++ b/app/src/main/res/layout/recycler_horizontal_row.xml @@ -0,0 +1,14 @@ + + + + + + diff --git a/app/src/main/res/layout/recycler_row.xml b/app/src/main/res/layout/recycler_vertical_row.xml similarity index 100% rename from app/src/main/res/layout/recycler_row.xml rename to app/src/main/res/layout/recycler_vertical_row.xml diff --git a/build.gradle b/build.gradle index cf3d4bc..dab7543 100644 --- a/build.gradle +++ b/build.gradle @@ -3,9 +3,8 @@ buildscript { ext { - support_version = '1.1.0-alpha03' - kotlin_version = '1.3.21' - constraint_version = '1.1.3' + kotlin_version = '1.3.72' + constraint_version = '2.0.1' } repositories { @@ -13,7 +12,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.3.2' + classpath 'com.android.tools.build:gradle:4.0.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1' // NOTE: Do not place your application dependencies here; they belong diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 5493714..2f36f39 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Sun Mar 10 16:44:37 KST 2019 +#Mon Aug 10 23:14:19 KST 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip diff --git a/pic/sample_result.gif b/pic/sample_result.gif index 53829f1..5b6d1d7 100644 Binary files a/pic/sample_result.gif and b/pic/sample_result.gif differ diff --git a/settings.gradle b/settings.gradle index 1e64947..a021b50 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,7 +1,7 @@ include ':app', ':stickytimelineview' -gradle.ext.set('versionCode', 6) -gradle.ext.set('versionName', '0.0.20') +gradle.ext.set('versionCode', 8) +gradle.ext.set('versionName', '0.1.0') gradle.ext.set('minSdk', 16) gradle.ext.set('targetSdk', 28) diff --git a/stickytimelineview/build.gradle b/stickytimelineview/build.gradle index 1c07a08..769e61b 100644 --- a/stickytimelineview/build.gradle +++ b/stickytimelineview/build.gradle @@ -31,12 +31,12 @@ android { dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation 'androidx.appcompat:appcompat:1.1.0-alpha03' - compile 'androidx.recyclerview:recyclerview:1.1.0-alpha03' + implementation 'androidx.appcompat:appcompat:1.2.0' + compile 'androidx.recyclerview:recyclerview:1.1.0' compile "androidx.constraintlayout:constraintlayout:$constraint_version" - testImplementation 'junit:junit:4.12' - androidTestImplementation 'androidx.test:runner:1.1.2-alpha02' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0-alpha02' + testImplementation 'junit:junit:4.13' + androidTestImplementation 'androidx.test:runner:1.3.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' } diff --git a/stickytimelineview/src/main/java/xyz/sangcomz/stickytimelineview/TimeLineRecyclerView.kt b/stickytimelineview/src/main/java/xyz/sangcomz/stickytimelineview/TimeLineRecyclerView.kt index a359774..e3b22c5 100644 --- a/stickytimelineview/src/main/java/xyz/sangcomz/stickytimelineview/TimeLineRecyclerView.kt +++ b/stickytimelineview/src/main/java/xyz/sangcomz/stickytimelineview/TimeLineRecyclerView.kt @@ -4,6 +4,9 @@ import android.content.Context import android.util.AttributeSet import androidx.core.content.ContextCompat import androidx.recyclerview.widget.RecyclerView +import xyz.sangcomz.stickytimelineview.callback.SectionCallback +import xyz.sangcomz.stickytimelineview.decoration.HorizontalSectionItemDecoration +import xyz.sangcomz.stickytimelineview.decoration.VerticalSectionItemDecoration import xyz.sangcomz.stickytimelineview.model.RecyclerViewAttr @@ -27,50 +30,110 @@ class TimeLineRecyclerView(context: Context, attrs: AttributeSet?) : RecyclerVie private var recyclerViewAttr: RecyclerViewAttr? = null + companion object { + private const val MODE_VERTICAL = 0x00 + private const val MODE_HORIZONTAL = 0x01 + + const val MODE_FULL = 0x00 + const val MODE_TO_TIME_LINE = 0x01 + const val MODE_TO_DOT = 0x02 + } + init { attrs?.let { - val a = context?.theme?.obtainStyledAttributes( - attrs, - R.styleable.TimeLineRecyclerView, - 0, 0) + val a = context.theme?.obtainStyledAttributes( + attrs, + R.styleable.TimeLineRecyclerView, + 0, 0 + ) a?.let { recyclerViewAttr = - RecyclerViewAttr(it.getColor(R.styleable.TimeLineRecyclerView_sectionBackgroundColor, - ContextCompat.getColor(context, R.color.colorDefaultBackground)), - it.getColor(R.styleable.TimeLineRecyclerView_sectionTitleTextColor, - ContextCompat.getColor(context, R.color.colorDefaultTitle)), - it.getColor(R.styleable.TimeLineRecyclerView_sectionSubTitleTextColor, - ContextCompat.getColor(context, R.color.colorDefaultSubTitle)), - it.getColor(R.styleable.TimeLineRecyclerView_timeLineColor, - ContextCompat.getColor(context, R.color.colorDefaultTitle)), - it.getColor(R.styleable.TimeLineRecyclerView_timeLineCircleColor, - ContextCompat.getColor(context, R.color.colorDefaultTitle)), - it.getColor(R.styleable.TimeLineRecyclerView_timeLineCircleStrokeColor, - ContextCompat.getColor(context, R.color.colorDefaultStroke)), - it.getDimension(R.styleable.TimeLineRecyclerView_sectionTitleTextSize, - context.resources.getDimension(R.dimen.title_text_size)), - it.getDimension(R.styleable.TimeLineRecyclerView_sectionSubTitleTextSize, - context.resources.getDimension(R.dimen.sub_title_text_size)), - it.getDimension(R.styleable.TimeLineRecyclerView_timeLineWidth, - context.resources.getDimension(R.dimen.line_width)), - it.getBoolean(R.styleable.TimeLineRecyclerView_isSticky, true), - it.getDrawable(R.styleable.TimeLineRecyclerView_customDotDrawable)) + RecyclerViewAttr( + it.getColor( + R.styleable.TimeLineRecyclerView_sectionBackgroundColor, + ContextCompat.getColor(context, R.color.colorDefaultBackground) + ), + it.getColor( + R.styleable.TimeLineRecyclerView_sectionTitleTextColor, + ContextCompat.getColor(context, R.color.colorDefaultTitle) + ), + it.getColor( + R.styleable.TimeLineRecyclerView_sectionSubTitleTextColor, + ContextCompat.getColor(context, R.color.colorDefaultSubTitle) + ), + it.getColor( + R.styleable.TimeLineRecyclerView_timeLineColor, + ContextCompat.getColor(context, R.color.colorDefaultTitle) + ), + it.getColor( + R.styleable.TimeLineRecyclerView_timeLineDotColor, + ContextCompat.getColor(context, R.color.colorDefaultTitle) + ), + it.getColor( + R.styleable.TimeLineRecyclerView_timeLineDotStrokeColor, + ContextCompat.getColor(context, R.color.colorDefaultStroke) + ), + it.getDimension( + R.styleable.TimeLineRecyclerView_sectionTitleTextSize, + context.resources.getDimension(R.dimen.title_text_size) + ), + it.getDimension( + R.styleable.TimeLineRecyclerView_sectionSubTitleTextSize, + context.resources.getDimension(R.dimen.sub_title_text_size) + ), + it.getDimension( + R.styleable.TimeLineRecyclerView_timeLineWidth, + context.resources.getDimension(R.dimen.line_width) + ), + it.getBoolean(R.styleable.TimeLineRecyclerView_isSticky, true), + it.getDrawable(R.styleable.TimeLineRecyclerView_customDotDrawable), + it.getInt(R.styleable.TimeLineRecyclerView_timeLineMode, MODE_VERTICAL), + it.getInt( + R.styleable.TimeLineRecyclerView_sectionBackgroundColorMode, + MODE_FULL + ) + ) } + a?.recycle() } } /** - * Add RecyclerSectionItemDecoration for Sticky TimeLineView + * Add VerticalSectionItemDecoration for Sticky TimeLineView * * @param callback SectionCallback + * if you'd like to know more mode , look at res/values/attrs.xml */ - fun addItemDecoration(callback: RecyclerSectionItemDecoration.SectionCallback) { + fun addItemDecoration(callback: SectionCallback) { recyclerViewAttr?.let { - this.addItemDecoration(RecyclerSectionItemDecoration(context, - callback, - it)) + val decoration: ItemDecoration = + when (it.timeLineMode) { + MODE_VERTICAL -> { + VerticalSectionItemDecoration( + context, + callback, + it + ) + } + MODE_HORIZONTAL -> { + HorizontalSectionItemDecoration( + context, + callback, + it + ) + } + else -> { + VerticalSectionItemDecoration( + context, + callback, + it + ) + } + } + + this.addItemDecoration(decoration) } } } \ No newline at end of file diff --git a/stickytimelineview/src/main/java/xyz/sangcomz/stickytimelineview/callback/SectionCallback.kt b/stickytimelineview/src/main/java/xyz/sangcomz/stickytimelineview/callback/SectionCallback.kt new file mode 100644 index 0000000..a170ebf --- /dev/null +++ b/stickytimelineview/src/main/java/xyz/sangcomz/stickytimelineview/callback/SectionCallback.kt @@ -0,0 +1,15 @@ +package xyz.sangcomz.stickytimelineview.callback + +import xyz.sangcomz.stickytimelineview.model.SectionInfo + +interface SectionCallback { + /** + * To check if section is + */ + fun isSection(position: Int): Boolean + + /** + * Functions that return a section header in a section + */ + fun getSectionHeader(position: Int): SectionInfo? +} \ No newline at end of file diff --git a/stickytimelineview/src/main/java/xyz/sangcomz/stickytimelineview/decoration/HorizontalSectionItemDecoration.kt b/stickytimelineview/src/main/java/xyz/sangcomz/stickytimelineview/decoration/HorizontalSectionItemDecoration.kt new file mode 100644 index 0000000..9740bcd --- /dev/null +++ b/stickytimelineview/src/main/java/xyz/sangcomz/stickytimelineview/decoration/HorizontalSectionItemDecoration.kt @@ -0,0 +1,275 @@ +package xyz.sangcomz.stickytimelineview.decoration + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.Rect +import android.graphics.Typeface +import android.graphics.drawable.Drawable +import android.graphics.drawable.GradientDrawable +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import xyz.sangcomz.stickytimelineview.TimeLineRecyclerView.Companion.MODE_TO_DOT +import xyz.sangcomz.stickytimelineview.TimeLineRecyclerView.Companion.MODE_TO_TIME_LINE +import xyz.sangcomz.stickytimelineview.callback.SectionCallback +import xyz.sangcomz.stickytimelineview.ext.DP +import xyz.sangcomz.stickytimelineview.model.RecyclerViewAttr +import xyz.sangcomz.stickytimelineview.model.SectionInfo + +class HorizontalSectionItemDecoration( + context: Context, + private val sectionCallback: SectionCallback, + private val recyclerViewAttr: RecyclerViewAttr +) : RecyclerView.ItemDecoration() { + + private var defaultOffset: Int = 8.DP(context).toInt() + + private val headerSectionBackgroundPaint = Paint().apply { + isAntiAlias = true + color = recyclerViewAttr.sectionBackgroundColor + } + + private val linePaint = Paint().apply { + isAntiAlias = true + color = recyclerViewAttr.sectionLineColor + strokeWidth = recyclerViewAttr.sectionLineWidth + } + + private val headerTitlePaint = Paint().apply { + isAntiAlias = true + textSize = recyclerViewAttr.sectionTitleTextSize + color = recyclerViewAttr.sectionTitleTextColor + typeface = Typeface.create(FONT_FAMILY, Typeface.BOLD) + } + + private val headerSubTitlePaint = Paint().apply { + isAntiAlias = true + textSize = recyclerViewAttr.sectionSubTitleTextSize + color = recyclerViewAttr.sectionSubTitleTextColor + typeface = Typeface.create(FONT_FAMILY, Typeface.NORMAL) + } + + override fun getItemOffsets( + outRect: Rect, + view: View, + parent: RecyclerView, + state: RecyclerView.State + ) { + super.getItemOffsets( + outRect, + view, + parent, + state + ) + outRect.top = getTopSpace().toInt() + + val leftMargin = defaultOffset + val rightMargin = defaultOffset + + outRect.bottom = defaultOffset / 2 + outRect.left = leftMargin + outRect.right = rightMargin + } + + + override fun onDrawOver(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) { + super.onDrawOver(canvas, parent, state) + + drawBackground(canvas, parent) + drawLine(canvas, parent) + + var previousHeader: SectionInfo? = null + + if (recyclerViewAttr.isSticky) { + val topChild = parent.getChildAt(0) + val topHeaderSectionInfo = getCurrentTopSectionInfo(parent) ?: return + + val currentHeaderWidth = getCurrentHeaderViewWidth(parent) + val nextHeaderView = getNextHeaderView(parent) + + val isContact = + nextHeaderView?.left in 0..currentHeaderWidth.toInt() + defaultOffset * 2 + val defaultLeftOffset = -(topChild.left).toFloat() + defaultOffset + + previousHeader = topHeaderSectionInfo + + if (isContact) { + val offset = currentHeaderWidth - ((nextHeaderView?.left ?: 0) - defaultOffset * 2) + + drawHeader( + canvas, + topChild, + topHeaderSectionInfo, + defaultLeftOffset - offset + ) + } else { + drawHeader( + canvas, + topChild, + topHeaderSectionInfo, + defaultLeftOffset + ) + } + } + + for (i in 0 until parent.childCount) { + val child = parent.getChildAt(i) + val position = parent.getChildAdapterPosition(child) + + sectionCallback.getSectionHeader(position)?.let { sectionInfo -> + if (previousHeader?.title != sectionInfo.title) { + if (getIsSection(position)) { + drawHeader(canvas, child, sectionInfo) + } + previousHeader = sectionInfo + } + } + } + } + + /** + * Draw a line in the timeline. + */ + private fun drawLine(canvas: Canvas, parent: RecyclerView) { + val yValue = getTopSpace() - (defaultOffset * 2) - (defaultOffset / 4) + + canvas.drawLines(floatArrayOf(0f, yValue, parent.width.toFloat(), yValue), linePaint) + } + + private fun drawBackground(canvas: Canvas, parent: RecyclerView) { + var bottom = getTopSpace() - defaultOffset + + when (recyclerViewAttr.sectionBackgroundColorMode) { + MODE_TO_DOT -> { + bottom -= (defaultOffset * 2 + defaultOffset / 2) + } + MODE_TO_TIME_LINE -> { + bottom -= if (recyclerViewAttr.sectionLineWidth > defaultOffset * 2 + defaultOffset / 2) { + recyclerViewAttr.sectionLineWidth / 2 + } else { + (defaultOffset + defaultOffset / 4) - (recyclerViewAttr.sectionLineWidth / 2) + } + } + } + + val rect = Rect(parent.left, 0, parent.width, bottom.toInt()) + + canvas.drawRect(rect, headerSectionBackgroundPaint) + } + + /** + * Returns the oval dotDrawable of the timeline. + */ + private fun getOvalDrawable(): Drawable { + val strokeWidth = defaultOffset / 2 + val roundRadius = defaultOffset * 2 + val strokeColor = recyclerViewAttr.sectionDotStrokeColor + val fillColor = recyclerViewAttr.sectionDotColor + + val gd = GradientDrawable() + gd.setColor(fillColor) + gd.cornerRadius = roundRadius.toFloat() + gd.setStroke(strokeWidth, strokeColor) + + return gd + } + + /** + * Draw a header + */ + private fun drawHeader(c: Canvas, child: View, sectionInfo: SectionInfo, offset: Float = 0f) { + c.save() + c.translate((child.left).toFloat() + offset, 0f) + drawDotDrawable(c, sectionInfo) + drawHeaderTitle(c, sectionInfo) + drawHeaderSubTitle(c, sectionInfo) + c.restore() + } + + private fun drawDotDrawable(canvas: Canvas, sectionInfo: SectionInfo) { + val dotDrawable = + sectionInfo.dotDrawable ?: recyclerViewAttr.customDotDrawable ?: getOvalDrawable() + canvas.save() + canvas.translate( + 0f, + recyclerViewAttr.sectionTitleTextSize + recyclerViewAttr.sectionSubTitleTextSize + defaultOffset + ) + dotDrawable.draw(canvas) + canvas.restore() + } + + private fun drawHeaderTitle(canvas: Canvas, sectionInfo: SectionInfo) { + val subTitleHeight = + if (sectionInfo.subTitle.isNullOrEmpty()) recyclerViewAttr.sectionSubTitleTextSize else 0f + canvas.drawText( + sectionInfo.title, + 0f, + recyclerViewAttr.sectionTitleTextSize - subTitleHeight, headerTitlePaint + ) + } + + private fun drawHeaderSubTitle(canvas: Canvas, sectionInfo: SectionInfo) { + val subTitle = sectionInfo.subTitle ?: return + canvas.drawText( + subTitle, + 0f, + recyclerViewAttr.sectionTitleTextSize + recyclerViewAttr.sectionSubTitleTextSize, + headerSubTitlePaint + ) + } + + private fun getCurrentHeaderViewWidth(parent: RecyclerView): Float { + val prevHeaderSectionInfo = getCurrentTopSectionInfo(parent) ?: return 0f + + val titleWidth = headerTitlePaint.measureText(prevHeaderSectionInfo.title) + val subTitleWidth = headerSubTitlePaint.measureText(prevHeaderSectionInfo.subTitle ?: "") + return titleWidth.coerceAtLeast(subTitleWidth) + } + + private fun getIsSection(position: Int): Boolean = when (position) { + 0 -> { + true + } + -1 -> { + false + } + else -> { + sectionCallback.isSection(position) + } + } + + private fun getTopSpace(): Float { + return recyclerViewAttr.sectionTitleTextSize + + recyclerViewAttr.sectionSubTitleTextSize + + (defaultOffset * 4) + + (defaultOffset / 2) + } + + private fun getCurrentTopSectionInfo(parent: RecyclerView): SectionInfo? { + return parent.getChildAt(0) + ?.let { sectionCallback.getSectionHeader(parent.getChildAdapterPosition(it)) } + } + + private fun getNextHeaderView(parent: RecyclerView): View? { + val currentTopSectionInfo = getCurrentTopSectionInfo(parent) + + return (0 until parent.childCount) + .map { + parent.getChildAt(it) + } + .firstOrNull { + sectionCallback.getSectionHeader(parent.getChildAdapterPosition(it)) != currentTopSectionInfo + } + } + + private fun getDotRadius() = defaultOffset + defaultOffset / 4 + + + private fun isLineBiggerThenDot(): Boolean { + return recyclerViewAttr.sectionLineWidth > (getDotRadius() * 2) + } + + companion object { + private const val FONT_FAMILY = "sans-serif-light" + } +} \ No newline at end of file diff --git a/stickytimelineview/src/main/java/xyz/sangcomz/stickytimelineview/RecyclerSectionItemDecoration.kt b/stickytimelineview/src/main/java/xyz/sangcomz/stickytimelineview/decoration/VerticalSectionItemDecoration.kt similarity index 91% rename from stickytimelineview/src/main/java/xyz/sangcomz/stickytimelineview/RecyclerSectionItemDecoration.kt rename to stickytimelineview/src/main/java/xyz/sangcomz/stickytimelineview/decoration/VerticalSectionItemDecoration.kt index c71c407..b7354b7 100755 --- a/stickytimelineview/src/main/java/xyz/sangcomz/stickytimelineview/RecyclerSectionItemDecoration.kt +++ b/stickytimelineview/src/main/java/xyz/sangcomz/stickytimelineview/decoration/VerticalSectionItemDecoration.kt @@ -1,4 +1,4 @@ -package xyz.sangcomz.stickytimelineview +package xyz.sangcomz.stickytimelineview.decoration import android.content.Context import android.graphics.Canvas @@ -13,6 +13,8 @@ import android.view.ViewGroup import android.widget.TextView import androidx.appcompat.widget.AppCompatImageView import androidx.recyclerview.widget.RecyclerView +import xyz.sangcomz.stickytimelineview.R +import xyz.sangcomz.stickytimelineview.callback.SectionCallback import xyz.sangcomz.stickytimelineview.ext.DP import xyz.sangcomz.stickytimelineview.model.RecyclerViewAttr import xyz.sangcomz.stickytimelineview.model.SectionInfo @@ -27,7 +29,7 @@ import xyz.sangcomz.stickytimelineview.model.SectionInfo * I was inspired by his code. And I used some of his code in the library. * https://github.com/paetztm/recycler_view_headers */ -class RecyclerSectionItemDecoration( +class VerticalSectionItemDecoration( context: Context, private val sectionCallback: SectionCallback, private val recyclerViewAttr: RecyclerViewAttr @@ -141,7 +143,7 @@ class RecyclerSectionItemDecoration( private fun getHeaderView(parent: RecyclerView) { headerView = inflateHeaderView(parent) headerView?.let { headerView -> - headerBackground = headerView.findViewById(R.id.v_item_background) + headerBackground = headerView.findViewById(R.id.lin_item_background) headerTitle = headerView.findViewById(R.id.list_item_section_title) headerSubTitle = headerView.findViewById(R.id.list_item_section_sub_title) dot = headerView.findViewById(R.id.dot) @@ -207,9 +209,6 @@ class RecyclerSectionItemDecoration( ) } - /** - * - */ private fun getChildInContact(parent: RecyclerView, contactPoint: Int): View? = (0 until parent.childCount) .map { @@ -225,8 +224,8 @@ class RecyclerSectionItemDecoration( private fun getOvalDrawable(): Drawable { val strokeWidth = defaultOffset / 2 val roundRadius = defaultOffset * 2 - val strokeColor = recyclerViewAttr.sectionStrokeColor - val fillColor = recyclerViewAttr.sectionCircleColor + val strokeColor = recyclerViewAttr.sectionDotStrokeColor + val fillColor = recyclerViewAttr.sectionDotStrokeColor val gd = GradientDrawable() gd.setColor(fillColor) @@ -253,18 +252,9 @@ class RecyclerSectionItemDecoration( private fun drawHeader(c: Canvas, child: View, headerView: View) { c.save() if (recyclerViewAttr.isSticky) { - c.translate( - 0f, - Math.max( - 0, - child.top - headerView.height - ).toFloat() - ) + c.translate(0f, 0.coerceAtLeast(child.top - headerView.height).toFloat()) } else { - c.translate( - 0f, - (child.top - headerView.height).toFloat() - ) + c.translate(0f, (child.top - headerView.height).toFloat()) } headerView.draw(c) c.restore() @@ -332,22 +322,6 @@ class RecyclerSectionItemDecoration( } } - - - /** - * Section-specific callback interface - */ - interface SectionCallback { - /** - * To check if section is - */ - fun isSection(position: Int): Boolean - - /** - * Functions that return a section header in a section - */ - fun getSectionHeader(position: Int): SectionInfo? - } } diff --git a/stickytimelineview/src/main/java/xyz/sangcomz/stickytimelineview/model/RecyclerViewAttr.kt b/stickytimelineview/src/main/java/xyz/sangcomz/stickytimelineview/model/RecyclerViewAttr.kt index a8807ba..70441e2 100644 --- a/stickytimelineview/src/main/java/xyz/sangcomz/stickytimelineview/model/RecyclerViewAttr.kt +++ b/stickytimelineview/src/main/java/xyz/sangcomz/stickytimelineview/model/RecyclerViewAttr.kt @@ -5,15 +5,18 @@ import android.graphics.drawable.Drawable /** * Created by seokwon.jeong on 17/11/2017. */ -data class RecyclerViewAttr(val sectionBackgroundColor: Int, - val sectionTitleTextColor: Int, - val sectionSubTitleTextColor: Int, - val sectionLineColor: Int, - val sectionCircleColor: Int, - val sectionStrokeColor: Int, - val sectionTitleTextSize: Float, - val sectionSubTitleTextSize: Float, - val sectionLineWidth: Float, - val isSticky: Boolean, - val customDotDrawable: Drawable? +data class RecyclerViewAttr( + val sectionBackgroundColor: Int, + val sectionTitleTextColor: Int, + val sectionSubTitleTextColor: Int, + val sectionLineColor: Int, + val sectionDotColor: Int, + val sectionDotStrokeColor: Int, + val sectionTitleTextSize: Float, + val sectionSubTitleTextSize: Float, + val sectionLineWidth: Float, + val isSticky: Boolean, + val customDotDrawable: Drawable?, + val timeLineMode: Int, + val sectionBackgroundColorMode: Int ) \ No newline at end of file diff --git a/stickytimelineview/src/main/res/layout/recycler_section_header.xml b/stickytimelineview/src/main/res/layout/recycler_section_header.xml index 9cc40bd..5f674b6 100755 --- a/stickytimelineview/src/main/res/layout/recycler_section_header.xml +++ b/stickytimelineview/src/main/res/layout/recycler_section_header.xml @@ -1,7 +1,6 @@ @@ -10,18 +9,20 @@ android:id="@+id/dot" android:layout_width="@dimen/dot_size" android:layout_height="@dimen/dot_size" - android:layout_marginRight="2dp" android:layout_marginLeft="2dp" + android:layout_marginRight="2dp" app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toStartOf="@+id/v_item_background" + app:layout_constraintEnd_toStartOf="@+id/lin_item_background" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> - + app:layout_constraintStart_toStartOf="@+id/lin_item_background" + app:layout_constraintTop_toTopOf="@+id/dot" /> + app:layout_constraintTop_toBottomOf="@+id/list_item_section_title" /> diff --git a/stickytimelineview/src/main/res/values/attrs.xml b/stickytimelineview/src/main/res/values/attrs.xml index ca4c5cc..c1aaa32 100644 --- a/stickytimelineview/src/main/res/values/attrs.xml +++ b/stickytimelineview/src/main/res/values/attrs.xml @@ -1,18 +1,30 @@ + + + + + + + + + + + + + + + + - - - - - - + + \ No newline at end of file