diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..07fccd2 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,38 @@ +apply plugin: 'com.android.application' + +apply plugin: 'kotlin-android' + +apply plugin: 'kotlin-android-extensions' + + + +android { + compileSdkVersion 29 + buildToolsVersion "29.0.1" + defaultConfig { + applicationId "com.github.islamkhsh.cardslider_example" + minSdkVersion 17 + targetSdkVersion 29 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation 'androidx.appcompat:appcompat:1.0.2' + implementation 'androidx.core:core-ktx:1.0.2' + testImplementation 'junit:junit:4.12' + androidTestImplementation 'androidx.test:runner:1.2.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' + + implementation project(path: ':cardslider') +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..35ece30 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/github/islamkhsh/cardslider_example/MainActivity.kt b/app/src/main/java/com/github/islamkhsh/cardslider_example/MainActivity.kt new file mode 100644 index 0000000..9f41601 --- /dev/null +++ b/app/src/main/java/com/github/islamkhsh/cardslider_example/MainActivity.kt @@ -0,0 +1,38 @@ +package com.github.islamkhsh.cardslider_example + +import androidx.appcompat.app.AppCompatActivity +import android.os.Bundle +import kotlinx.android.synthetic.main.activity_main.* + + +class MainActivity : AppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + + val movies = arrayListOf() + + movies.add( + Movie(R.drawable.harry_potter,"Harry Potter", + "An orphaned boy enrolls in a school of wizardry, where he learns the truth about himself, his family and the terrible evil that haunts the magical world." + )) + + movies.add( + Movie(R.drawable.lord_of_rings,"The Lord of the Rings", + "A meek Hobbit from the Shire and eight companions set out on a journey to destroy the powerful One Ring and save Middle-earth from the Dark Lord Sauron." + )) + + movies.add( + Movie(R.drawable.the_matrix,"The Matrix", + "A computer hacker learns from mysterious rebels about the true nature of his reality and his role in the war against its controllers." + )) + + movies.add( + Movie(R.drawable.avengers,"Avengers Assemble", + "Earth's mightiest heroes must come together and learn to fight as a team if they are going to stop the mischievous Loki and his alien army from enslaving humanity." + )) + + viewPager.adapter = MovieAdapter(movies) + } +} diff --git a/app/src/main/java/com/github/islamkhsh/cardslider_example/Movie.kt b/app/src/main/java/com/github/islamkhsh/cardslider_example/Movie.kt new file mode 100644 index 0000000..73b1e37 --- /dev/null +++ b/app/src/main/java/com/github/islamkhsh/cardslider_example/Movie.kt @@ -0,0 +1,7 @@ +package com.github.islamkhsh.cardslider_example + +data class Movie ( + val poster : Int, + val title : String, + val overview : String +) \ No newline at end of file diff --git a/app/src/main/java/com/github/islamkhsh/cardslider_example/MovieAdapter.kt b/app/src/main/java/com/github/islamkhsh/cardslider_example/MovieAdapter.kt new file mode 100644 index 0000000..9d53645 --- /dev/null +++ b/app/src/main/java/com/github/islamkhsh/cardslider_example/MovieAdapter.kt @@ -0,0 +1,20 @@ +package com.github.islamkhsh.cardslider_example + +import android.view.View +import android.widget.TextView +import com.github.islamkhsh.CardSliderAdapter +import kotlinx.android.synthetic.main.item_card_content.view.* + +class MovieAdapter(items : ArrayList) : CardSliderAdapter(items) { + + override fun bindView(position: Int, itemContentView: View, item: Movie?) { + + item?.run { + itemContentView.movie_poster.setImageResource(poster) + itemContentView.movie_title.text = title + itemContentView.movie_overview.text = overview + } + } + + override fun getItemContentLayout(position: Int) = R.layout.item_card_content +} \ No newline at end of file diff --git a/app/src/main/res/drawable-v24/ic_bookmark_24dp.xml b/app/src/main/res/drawable-v24/ic_bookmark_24dp.xml new file mode 100755 index 0000000..c1f2c13 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_bookmark_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..6348baa --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable/avengers.jpg b/app/src/main/res/drawable/avengers.jpg new file mode 100644 index 0000000..111eed8 Binary files /dev/null and b/app/src/main/res/drawable/avengers.jpg differ diff --git a/app/src/main/res/drawable/harry_potter.jpg b/app/src/main/res/drawable/harry_potter.jpg new file mode 100644 index 0000000..ce77a21 Binary files /dev/null and b/app/src/main/res/drawable/harry_potter.jpg differ diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..a0ad202 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/lord_of_rings.jpg b/app/src/main/res/drawable/lord_of_rings.jpg new file mode 100644 index 0000000..8e505b5 Binary files /dev/null and b/app/src/main/res/drawable/lord_of_rings.jpg differ diff --git a/app/src/main/res/drawable/the_matrix.jpg b/app/src/main/res/drawable/the_matrix.jpg new file mode 100644 index 0000000..0bf5b9b Binary files /dev/null and b/app/src/main/res/drawable/the_matrix.jpg differ diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..a33234c --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_card_content.xml b/app/src/main/res/layout/item_card_content.xml new file mode 100755 index 0000000..37fc5d7 --- /dev/null +++ b/app/src/main/res/layout/item_card_content.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..bbd3e02 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..bbd3e02 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..898f3ed Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..dffca36 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..64ba76f Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..dae5e08 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..e5ed465 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..14ed0af Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..b0907ca Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..d8ae031 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..2c18de9 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..beed3cd Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..69b2233 --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,6 @@ + + + #008577 + #00574B + #D81B60 + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..db7b9d8 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + CardSlider + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cab82d7 --- /dev/null +++ b/app/src/main/res/values/styles.xml @@ -0,0 +1,15 @@ + + + + + + + + diff --git a/cardslider/.gitignore b/cardslider/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/cardslider/.gitignore @@ -0,0 +1 @@ +/build diff --git a/cardslider/build.gradle b/cardslider/build.gradle new file mode 100644 index 0000000..bee1d34 --- /dev/null +++ b/cardslider/build.gradle @@ -0,0 +1,41 @@ +apply plugin: 'com.android.library' + +apply plugin: 'kotlin-android' + +apply plugin: 'kotlin-android-extensions' + +android { + compileSdkVersion 29 + buildToolsVersion "29.0.1" + + + defaultConfig { + minSdkVersion 17 + targetSdkVersion 29 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } +} + + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation 'androidx.core:core-ktx:1.0.2' + + implementation 'androidx.viewpager:viewpager:1.0.0' + implementation 'androidx.cardview:cardview:1.0.0' + + api 'com.duolingo.open:rtl-viewpager:2.0.0' +} diff --git a/cardslider/proguard-rules.pro b/cardslider/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/cardslider/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/cardslider/publish.gradle b/cardslider/publish.gradle new file mode 100644 index 0000000..ea9aa54 --- /dev/null +++ b/cardslider/publish.gradle @@ -0,0 +1,50 @@ +apply plugin: 'maven-publish' + +apply plugin: 'com.jfrog.bintray' + +version '1.0' +group 'com.github.islamkhsh' + + +publishing { + publications { + Production(MavenPublication) { + artifact("$buildDir/outputs/aar/cardslider-release.aar") + groupId this.group + artifactId 'cardslider' + version this.version + + pom.withXml { + def dependenciesNode = asNode().appendNode('dependencies') + + configurations.implementation.allDependencies.each { + + if (it.name != 'unspecified') { + def dependencyNode = dependenciesNode.appendNode('dependency') + dependencyNode.appendNode('groupId', it.group) + dependencyNode.appendNode('artifactId', it.name) + dependencyNode.appendNode('version', it.version) + } + } + } + } + } +} + +bintray { + user = project.hasProperty('user') ?: System.getenv('BINTRAY_USER') + key = project.hasProperty('apiKey') ?: System.getenv('BINTRAY_API_KEY') + publications = ['Production'] + configurations = ['archives'] + + publish = true + pkg { + repo = 'Card-Slider' + name = 'com.github.islamkhsh.cardslider' + version { + name = this.version + released = new Date() + vcsTag = this.version + } + } +} diff --git a/cardslider/src/main/AndroidManifest.xml b/cardslider/src/main/AndroidManifest.xml new file mode 100644 index 0000000..9cb9c7f --- /dev/null +++ b/cardslider/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + diff --git a/cardslider/src/main/java/com/github/islamkhsh/CardSliderAdapter.kt b/cardslider/src/main/java/com/github/islamkhsh/CardSliderAdapter.kt new file mode 100644 index 0000000..1df6df1 --- /dev/null +++ b/cardslider/src/main/java/com/github/islamkhsh/CardSliderAdapter.kt @@ -0,0 +1,86 @@ +package com.github.islamkhsh + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout.LayoutParams +import androidx.annotation.CallSuper +import androidx.annotation.LayoutRes +import androidx.cardview.widget.CardView +import androidx.viewpager.widget.PagerAdapter + +abstract class CardSliderAdapter(private val items: ArrayList) : PagerAdapter() { + + private val cards = arrayOfNulls(count) + + private lateinit var cardSliderViewPager : CardSliderViewPager + + internal fun setViewPager(cardSliderViewPager : CardSliderViewPager) { + this.cardSliderViewPager = cardSliderViewPager + } + + override fun isViewFromObject(view: View, `object`: Any) = view === `object` + + override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) { + container.removeView(`object` as View) + cards[position] = null + } + + @CallSuper + override fun instantiateItem(container: ViewGroup, position: Int): Any { + + // create card view as a root to item view + val cardView = CardView(container.context) + val params = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT) + cardView.layoutParams = params + + // set card attrs + cardView.maxCardElevation = cardSliderViewPager.baseShadow + cardView.radius = cardSliderViewPager.cardCornerRadius + cardView.setCardBackgroundColor(cardSliderViewPager.cardBackgroundColor) + + // get item view + val cardContent = LayoutInflater.from(container.context) + .inflate(getItemContentLayout(position), cardView, false) + + bindView(position, cardContent, getItem(position)) + + cardView.addView(cardContent) + container.addView(cardView) + cards[position] = cardView + + return cardView + } + + /** + * @return Int The total number of pages + */ + override fun getCount() = items.size + + /** + * Call this method to get the item at specific position + * @param position Int the position of the item + * @return T? the nullable item of the passed position + */ + open fun getItem(position: Int): T? = items[position] + + /** + * Override it to bind the #item with the inflated view of the page layout #itemContentView + * @param position Int the current position + * @param itemContentView View the inflated view of #getItemContentLayout with #position + * @param item T? the current item #getItem with #position + */ + abstract fun bindView(position: Int, itemContentView: View, item: T?) + + /** + * Override it to provide the page layout for every position, + * - this layout will be added as a child of CardView. + * - this layout will be inflated and passed as a View instance to bindView() + * @param position Int the position of page + * @return Int layout resource of the page + */ + @LayoutRes + abstract fun getItemContentLayout(position: Int): Int + + +} \ No newline at end of file diff --git a/cardslider/src/main/java/com/github/islamkhsh/CardSliderIndicator.kt b/cardslider/src/main/java/com/github/islamkhsh/CardSliderIndicator.kt new file mode 100644 index 0000000..446ba2c --- /dev/null +++ b/cardslider/src/main/java/com/github/islamkhsh/CardSliderIndicator.kt @@ -0,0 +1,117 @@ +package com.github.islamkhsh + +import android.content.Context +import android.graphics.drawable.Drawable +import android.util.AttributeSet +import android.view.Gravity +import android.view.View +import android.widget.LinearLayout +import androidx.core.content.ContextCompat +import androidx.viewpager.widget.ViewPager +import kotlin.math.min + + +class CardSliderIndicator : LinearLayout { + + /** + * default indicator drawable, the background of the view if not selected + */ + var defaultIndicator: Drawable? = null + set(value) { + field = value ?: ContextCompat.getDrawable(context, R.drawable.default_dot) + } + + /** + * selected indicator drawable, the background of the view if selected + */ + var selectedIndicator: Drawable? = null + set(value) { + field = value ?: ContextCompat.getDrawable(context, R.drawable.selected_dot) + } + + /** + * space between one indicator and the next one + */ + var indicatorMargin = 0f + + constructor(context: Context) : super(context) { + initIndicatorGroup(null) + } + + constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) { + initIndicatorGroup(attrs) + } + + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { + initIndicatorGroup(attrs) + } + + + private fun initIndicatorGroup(attrs: AttributeSet?) { + + val typedArray = context.obtainStyledAttributes(attrs, R.styleable.CardSliderIndicator) + + defaultIndicator = typedArray.getDrawable(R.styleable.CardSliderIndicator_default_indicator) + selectedIndicator = typedArray.getDrawable(R.styleable.CardSliderIndicator_selected_indicator) + + indicatorMargin = typedArray.getDimension( + R.styleable.CardSliderIndicator_indicator_margin, + min(defaultIndicator!!.intrinsicWidth, selectedIndicator!!.intrinsicWidth).toFloat() + ) + + typedArray.recycle() + + orientation = HORIZONTAL + gravity = Gravity.CENTER_VERTICAL + } + + + internal fun setupWithViewCardSliderViewPager(viewPager: CardSliderViewPager) { + + viewPager.adapter?.run { + + // create indicators + for (i in 0 until count) { + + val indicator = View(context) + addView(indicator, i) + changeIndicatorState(i, defaultIndicator!!, count - 1) + } + + // set selected + changeIndicatorState(viewPager.currentItem, selectedIndicator!!) + + viewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener { + + override fun onPageScrollStateChanged(state: Int) {} + override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {} + + override fun onPageSelected(position: Int) { + + for (i in 0 until childCount) { + + if (i == position) + changeIndicatorState(i, selectedIndicator!!) + else + changeIndicatorState(i, defaultIndicator!!) + } + } + }) + } + + } + + private fun changeIndicatorState(position: Int, drawableState: Drawable, lastPosition: Int = childCount - 1) { + + getChildAt(position).apply { + + background = drawableState + + val parms = LayoutParams(drawableState.intrinsicWidth, drawableState.intrinsicHeight) + if (position < lastPosition) + parms.marginEnd = indicatorMargin.toInt() + + layoutParams = parms + } + } +} \ No newline at end of file diff --git a/cardslider/src/main/java/com/github/islamkhsh/CardSliderTransformer.kt b/cardslider/src/main/java/com/github/islamkhsh/CardSliderTransformer.kt new file mode 100755 index 0000000..2c92037 --- /dev/null +++ b/cardslider/src/main/java/com/github/islamkhsh/CardSliderTransformer.kt @@ -0,0 +1,49 @@ +package com.github.islamkhsh + +import android.content.Context +import android.graphics.Point +import android.view.View +import android.view.WindowManager +import androidx.cardview.widget.CardView +import androidx.viewpager.widget.ViewPager +import kotlin.math.absoluteValue + + +internal class CardSliderTransformer(private val viewPager: CardSliderViewPager) : ViewPager.PageTransformer { + + private val startOffset: Float + + init { + val windowManager = viewPager.context + .getSystemService(Context.WINDOW_SERVICE) as WindowManager + + val screen = Point() + windowManager.defaultDisplay.getSize(screen) + + val horizontalPadding = viewPager.paddingEnd + viewPager.paddingStart + startOffset = ((horizontalPadding / 2).toFloat() / (screen.x - horizontalPadding).toFloat()) + } + + override fun transformPage(page: View, position: Float) { + + val absPosition = (position - startOffset).absoluteValue + + if (absPosition >= 1) { + + (page as CardView).cardElevation = viewPager.minShadow + page.scaleY = viewPager.smallScaleFactor + + } else { + // This will be during transformation + (page as CardView).cardElevation = + scalingEquation(viewPager.minShadow, viewPager.baseShadow, absPosition) + + page.scaleY = scalingEquation(viewPager.smallScaleFactor,1f, absPosition) + } + } + + + private fun scalingEquation(minValue: Float, maxValue: Float, absPosition: Float) = + (minValue - maxValue) * absPosition + maxValue + +} diff --git a/cardslider/src/main/java/com/github/islamkhsh/CardSliderViewPager.kt b/cardslider/src/main/java/com/github/islamkhsh/CardSliderViewPager.kt new file mode 100644 index 0000000..cfe030f --- /dev/null +++ b/cardslider/src/main/java/com/github/islamkhsh/CardSliderViewPager.kt @@ -0,0 +1,171 @@ +package com.github.islamkhsh + +import android.content.Context +import android.graphics.Color +import android.util.AttributeSet +import android.view.View +import androidx.viewpager.widget.PagerAdapter +import com.duolingo.open.rtlviewpager.RtlViewPager +import kotlin.math.max + +class CardSliderViewPager : RtlViewPager { + + private var indicatorId = -1 + + /** + * The small scale factor, height of cards in right and left (previous and next cards) will be scaled to this factor + */ + var smallScaleFactor = 1f + + /** + * The card shadow in case of current card + */ + var baseShadow = 0.0f + set(value) { + field = value + setPageMargin() + adapter?.notifyDataSetChanged() + } + + /** + * The card shadow in case of previous and next cards + */ + var minShadow = baseShadow * smallScaleFactor + + /** + * Space between pages + */ + var sliderPageMargin = baseShadow + set(value) { + field = value + setPageMargin() + } + + /** + * The width of displayed part from previous and next cards + */ + var otherPagesWidth = 0f + set(value) { + field = value + setPagePadding() + } + + /** + * The background color of the CardView + */ + var cardBackgroundColor = Color.WHITE + set(value) { + field = value + adapter?.notifyDataSetChanged() + } + + /** + * The corner radius of the CardView + */ + var cardCornerRadius = 0f + set(value) { + field = value + adapter?.notifyDataSetChanged() + } + + + constructor(context: Context) : super(context) { + init(null) + } + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + init(attrs) + } + + + private fun init(attrs: AttributeSet?) { + + val typedArray = context.obtainStyledAttributes(attrs, R.styleable.CardSliderViewPager) + + smallScaleFactor = typedArray.getFloat(R.styleable.CardSliderViewPager_cardSlider_smallScaleFactor, 1f) + + baseShadow = typedArray.getDimension( + R.styleable.CardSliderViewPager_cardSlider_baseShadow, + context.resources.getDimension(R.dimen.baseCardElevation) + ) + minShadow = + typedArray.getDimension(R.styleable.CardSliderViewPager_cardSlider_minShadow, baseShadow * smallScaleFactor) + + cardBackgroundColor = + typedArray.getColor(R.styleable.CardSliderViewPager_cardSlider_cardBackgroundColor, Color.WHITE) + cardCornerRadius = typedArray.getDimension(R.styleable.CardSliderViewPager_cardSlider_cardCornerRadius, 0f) + + sliderPageMargin = + typedArray.getDimension(R.styleable.CardSliderViewPager_cardSlider_pageMargin, baseShadow) + + otherPagesWidth = + typedArray.getDimension(R.styleable.CardSliderViewPager_cardSlider_otherPagesWidth, 0f) + + indicatorId = typedArray.getResourceId(R.styleable.CardSliderViewPager_cardSlider_indicator, -1) + + typedArray.recycle() + + clipToPadding = false + overScrollMode = View.OVER_SCROLL_NEVER + offscreenPageLimit = 3 + } + + private fun setPageMargin() { + pageMargin = max(sliderPageMargin, baseShadow).toInt() + setPagePadding() + } + + private fun setPagePadding() { + setPadding( + otherPagesWidth.toInt() + pageMargin, max(paddingTop, baseShadow.toInt()), + otherPagesWidth.toInt() + pageMargin, max(paddingBottom, baseShadow.toInt()) + ) + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + + var heightMeasure = heightMeasureSpec + val mode = MeasureSpec.getMode(heightMeasureSpec) + + + if (mode == MeasureSpec.UNSPECIFIED || mode == MeasureSpec.AT_MOST) { + + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + var height = 0 + for (i in 0 until childCount) { + val child = getChildAt(i) + child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)) + val h = child.measuredHeight + if (h > height) height = h + } + heightMeasure = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY) + } + + super.onMeasure(widthMeasureSpec, heightMeasure + paddingTop + paddingBottom) + } + + + /** + * Set a CardSliderAdapter that will supply views for this pager. + * @param adapter PagerAdapter? instance of CardSliderAdapter + * @throws IllegalArgumentException if adapter passed isn't a CardSliderAdapter + */ + @Throws(IllegalArgumentException::class) + override fun setAdapter(adapter: PagerAdapter?) { + + if (adapter !is CardSliderAdapter<*>) + throw IllegalArgumentException("adapter must be CardSliderAdapter") + + adapter.setViewPager(this) + super.setAdapter(adapter) + + setPageTransformer(false, CardSliderTransformer(this)) + + if (indicatorId != -1) + rootView.findViewById(indicatorId)?.run { + setupWithViewCardSliderViewPager(this@CardSliderViewPager) + } + } + + +} \ No newline at end of file diff --git a/cardslider/src/main/res/drawable/default_dot.xml b/cardslider/src/main/res/drawable/default_dot.xml new file mode 100644 index 0000000..49edf9a --- /dev/null +++ b/cardslider/src/main/res/drawable/default_dot.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/cardslider/src/main/res/drawable/selected_dot.xml b/cardslider/src/main/res/drawable/selected_dot.xml new file mode 100644 index 0000000..7e09557 --- /dev/null +++ b/cardslider/src/main/res/drawable/selected_dot.xml @@ -0,0 +1,8 @@ + + + + + + + diff --git a/cardslider/src/main/res/values/attrs.xml b/cardslider/src/main/res/values/attrs.xml new file mode 100644 index 0000000..25d7d72 --- /dev/null +++ b/cardslider/src/main/res/values/attrs.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/cardslider/src/main/res/values/dimens.xml b/cardslider/src/main/res/values/dimens.xml new file mode 100644 index 0000000..1425ef6 --- /dev/null +++ b/cardslider/src/main/res/values/dimens.xml @@ -0,0 +1,4 @@ + + + 2dp + \ No newline at end of file