From 7714412b6cc296f6c702809e7361f0e910f59f09 Mon Sep 17 00:00:00 2001 From: anna Date: Tue, 14 Aug 2018 17:10:09 +0300 Subject: [PATCH] Add lost functionality and fix issue #4 --- .../yalantis/kolodaandroid/MainActivity.kt | 50 +++-- app/src/main/res/drawable/ic_reload.xml | 4 + app/src/main/res/layout/activity_main.xml | 21 +- app/src/main/res/menu/menu_reload.xml | 11 + .../java/com/yalantis/library/CardLayout.kt | 106 +++++++++ .../java/com/yalantis/library/CardOperator.kt | 143 +++++++----- .../main/java/com/yalantis/library/Koloda.kt | 208 +++++++++++++----- library/src/main/res/values/attrs.xml | 3 +- 8 files changed, 402 insertions(+), 144 deletions(-) create mode 100644 app/src/main/res/drawable/ic_reload.xml create mode 100644 app/src/main/res/menu/menu_reload.xml create mode 100644 library/src/main/java/com/yalantis/library/CardLayout.kt diff --git a/app/src/main/java/com/yalantis/kolodaandroid/MainActivity.kt b/app/src/main/java/com/yalantis/kolodaandroid/MainActivity.kt index 410e422..7f80d2b 100644 --- a/app/src/main/java/com/yalantis/kolodaandroid/MainActivity.kt +++ b/app/src/main/java/com/yalantis/kolodaandroid/MainActivity.kt @@ -2,11 +2,13 @@ package com.yalantis.kolodaandroid import android.support.v7.app.AppCompatActivity import android.os.Bundle -import android.widget.Toast import com.yalantis.library.KolodaListener import kotlinx.android.synthetic.main.activity_main.* import android.annotation.SuppressLint +import android.view.Menu +import android.view.MenuItem import android.view.ViewTreeObserver +import com.yalantis.kolodaandroid.R.id.actionReload class MainActivity : AppCompatActivity() { @@ -27,8 +29,6 @@ class MainActivity : AppCompatActivity() { @SuppressLint("NewApi") override fun onGlobalLayout() { //now we can retrieve the width and height - val width = activityMain.width - val height = activityMain.height //... //do whatever you want with them //... @@ -55,19 +55,19 @@ class MainActivity : AppCompatActivity() { internal var cardsSwiped = 0 override fun onNewTopCard(position: Int) { - Toast.makeText(this@MainActivity, "On new top card", Toast.LENGTH_LONG).show() + //todo realize your logic } override fun onCardSwipedLeft(position: Int) { - Toast.makeText(this@MainActivity, "On card swiped left", Toast.LENGTH_LONG).show() + //todo realize your logic } override fun onCardSwipedRight(position: Int) { - Toast.makeText(this@MainActivity, "On card swiped right", Toast.LENGTH_LONG).show() + //todo realize your logic } override fun onEmptyDeck() { - Toast.makeText(this@MainActivity, "On empty deck", Toast.LENGTH_LONG).show() + //todo realize your logic } } } @@ -77,18 +77,18 @@ class MainActivity : AppCompatActivity() { */ private fun fillData() { val data = arrayOf(R.drawable.cupcacke, - R.drawable.donut, - R.drawable.eclair, - R.drawable.froyo, - R.drawable.gingerbread, - R.drawable.honeycomb, - R.drawable.ice_cream_sandwich, - R.drawable.jelly_bean, - R.drawable.kitkat, - R.drawable.lollipop, - R.drawable.marshmallow, - R.drawable.nougat, - R.drawable.oreo) + R.drawable.donut, + R.drawable.eclair, + R.drawable.froyo, + R.drawable.gingerbread, + R.drawable.honeycomb, + R.drawable.ice_cream_sandwich, + R.drawable.jelly_bean, + R.drawable.kitkat, + R.drawable.lollipop, + R.drawable.marshmallow, + R.drawable.nougat, + R.drawable.oreo) adapter = KolodaSampleAdapter(this, data.toList()) koloda.adapter = adapter koloda.isNeedCircleLoading = true @@ -98,4 +98,16 @@ class MainActivity : AppCompatActivity() { dislike.setOnClickListener { koloda.onClickLeft() } like.setOnClickListener { koloda.onClickRight() } } + + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.menu_reload, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + actionReload -> { koloda.reloadPreviousCard() } + } + return super.onOptionsItemSelected(item) + } } diff --git a/app/src/main/res/drawable/ic_reload.xml b/app/src/main/res/drawable/ic_reload.xml new file mode 100644 index 0000000..1fb4ac7 --- /dev/null +++ b/app/src/main/res/drawable/ic_reload.xml @@ -0,0 +1,4 @@ + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 8827bd9..0ba1b85 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -2,24 +2,23 @@ + android:layout_height="match_parent" + android:background="@color/colorPrimary" + android:clipToPadding="false"> @@ -27,19 +26,19 @@ android:id="@+id/dislike" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:src="@drawable/ic_dislike" android:layout_weight="1" - android:background="@android:color/transparent"/> + android:background="@android:color/transparent" + android:src="@drawable/ic_dislike" /> + android:layout_weight="0.5" + android:background="@android:color/transparent" + android:src="@drawable/ic_like" /> diff --git a/app/src/main/res/menu/menu_reload.xml b/app/src/main/res/menu/menu_reload.xml new file mode 100644 index 0000000..0bc346b --- /dev/null +++ b/app/src/main/res/menu/menu_reload.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/library/src/main/java/com/yalantis/library/CardLayout.kt b/library/src/main/java/com/yalantis/library/CardLayout.kt new file mode 100644 index 0000000..d6f99e8 --- /dev/null +++ b/library/src/main/java/com/yalantis/library/CardLayout.kt @@ -0,0 +1,106 @@ +package com.yalantis.library + +import android.content.Context +import android.os.Build +import android.support.annotation.DrawableRes +import android.support.v4.content.ContextCompat +import android.util.AttributeSet +import android.view.View +import android.widget.FrameLayout +import android.widget.ImageView + +/** + * Created by anna on 1/2/18. + */ +class CardLayout +@JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, + defStyleAttr: Int = 0, var cardParams: CardParams) : FrameLayout(context, attrs, defStyleAttr) { + + private var rightImageView: ImageView? = null + private var leftImageView: ImageView? = null + private var cardView: View? = null + var layoutAlpha = 1f + set(value) { + field = value + cardView?.alpha = value + } + + + fun addOverlays(cardView: View?, @DrawableRes rightOverlayImage: Int?, + @DrawableRes leftOverlayImage: Int?) { + this.cardView = cardView + addView(cardView) + + leftOverlayImage?.let { + leftImageView = ImageView(context) + leftImageView?.setImageDrawable(ContextCompat.getDrawable(context, it)) + leftImageView?.alpha = 0f + addView(leftImageView) + } + + rightOverlayImage?.let { + rightImageView = ImageView(context) + rightImageView?.setImageDrawable(ContextCompat.getDrawable(context, it)) + rightImageView?.alpha = 0f + addView(rightImageView) + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + rightImageView?.translationZ = SMALL_ELEVATION + leftImageView?.translationZ = SMALL_ELEVATION + cardView?.translationZ = NO_ELEVATION + } + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + val layoutParamsCardView = cardView?.layoutParams as FrameLayout.LayoutParams + + cardView?.alpha = layoutAlpha + cardParams.onParamsMeasure(layoutParamsCardView) + + rightImageView?.layoutParams?.let { + (rightImageView?.layoutParams as FrameLayout.LayoutParams).apply { + width = layoutParamsCardView.width + height = layoutParamsCardView.height + gravity = layoutParamsCardView.gravity + + setMargins(layoutParamsCardView.leftMargin, layoutParamsCardView.topMargin, + layoutParamsCardView.rightMargin, layoutParamsCardView.bottomMargin) + } + rightImageView?.setPadding(cardView?.paddingLeft ?: 0, cardView?.paddingTop ?: 0, + cardView?.paddingRight ?: 0, cardView?.paddingBottom ?: 0) + } + + leftImageView?.layoutParams?.let { + (leftImageView?.layoutParams as FrameLayout.LayoutParams).apply { + width = layoutParamsCardView.width + height = layoutParamsCardView.height + gravity = layoutParamsCardView.gravity + setMargins(layoutParamsCardView.leftMargin, layoutParamsCardView.topMargin, + layoutParamsCardView.rightMargin, layoutParamsCardView.bottomMargin) + } + + leftImageView?.setPadding(cardView?.paddingLeft ?: 0, cardView?.paddingTop ?: 0, + cardView?.paddingRight ?: 0, cardView?.paddingBottom ?: 0) + } + } + + internal fun changeRightOverlayAlpha(progress: Float) { + rightImageView?.alpha = progress + } + + internal fun changeLeftOverlayAlpha(progress: Float) { + leftImageView?.alpha = Math.abs(progress) + } + + interface CardParams { + fun onParamsMeasure(layoutParamsCardView: LayoutParams) + } + + companion object { + private const val NO_ELEVATION = 0F + private const val SMALL_ELEVATION = 10F + } + +} \ No newline at end of file diff --git a/library/src/main/java/com/yalantis/library/CardOperator.kt b/library/src/main/java/com/yalantis/library/CardOperator.kt index b40d490..570bec4 100644 --- a/library/src/main/java/com/yalantis/library/CardOperator.kt +++ b/library/src/main/java/com/yalantis/library/CardOperator.kt @@ -1,26 +1,33 @@ package com.yalantis.library import android.animation.* -import android.support.v4.view.VelocityTrackerCompat import android.view.GestureDetector import android.view.MotionEvent -import android.view.View import android.view.VelocityTracker +import android.view.View /** * Created by anna on 11/10/17. */ -class CardOperator(val koloda: Koloda, val cardView: View, val adapterPosition: Int, val cardCallback: CardCallback) { +class CardOperator( + val koloda: Koloda, + val cardView: CardLayout, + val adapterPosition: Int, + val cardCallback: CardCallback +) { - companion object { - private const val DEFAULT_OFF_SCREEN_ANIMATION_DURATION = 600 - private val DEFAULT_OFF_SCREEN_FLING_ANIMATION_DURATION = 150 - private val DEFAULT_RESET_ANIMATION_DURATION = 600 + init { + cardView.setOnTouchListener { view, event -> onTouchView(view, event) } } private val cardGestureListener = object : GestureDetector.SimpleOnGestureListener() { - override fun onFling(e1: MotionEvent?, e2: MotionEvent?, velocityX: Float, velocityY: Float): Boolean { + override fun onFling( + e1: MotionEvent?, + e2: MotionEvent?, + velocityX: Float, + velocityY: Float + ): Boolean { if (e1?.x ?: 0f > e2?.x ?: 0f && cardBeyondLeftBorder()) { cardCallback.onCardActionUp(adapterPosition, cardView, true) animateOffScreenLeft(DEFAULT_OFF_SCREEN_FLING_ANIMATION_DURATION, true, false) @@ -59,37 +66,35 @@ class CardOperator(val koloda: Koloda, val cardView: View, val adapterPosition: private var initialCardPositionX: Float = 0.toFloat() private var initialCardPositionY: Float = 0.toFloat() private var animationCycle: AnimationCycle = AnimationCycle.NO_ANIMATION - private var velocityTrecker: VelocityTracker? = null - - init { - cardView.setOnTouchListener { view, event -> onTouchView(view, event) } - } + private var velocityTracker: VelocityTracker? = null fun animateOffScreenLeft(duration: Int, notify: Boolean, isClicked: Boolean) { - var targetY = cardView.y - if (initialCardPositionY > cardView.y) { - targetY -= initialCardPositionY - cardView.y + val pvhX = PropertyValuesHolder.ofFloat("x", cardView.x, cardView.x - calculateTransition()) + val pvhY = PropertyValuesHolder.ofFloat("y", cardView.y, cardView.y * 2) + animateCardOffScreen(duration, pvhX, pvhY) + + if (!isClicked) { + cardCallback.onCardSwipedLeft(adapterPosition, cardView, notify) } else { - targetY += (cardView.y - initialCardPositionY) * 3 + cardCallback.onCardMovedOnClickLeft(adapterPosition, cardView, notify) } + } - val maxCardWidth = koloda.getMaxCardWidth(cardView) - val transitionX = (koloda.parentWidth / 2 + cardView.width / 2) + - cardView.x + Math.abs(maxCardWidth / 2) + fun animateOffScreenRight(duration: Int, notify: Boolean, isClicked: Boolean) { - val pvhX = PropertyValuesHolder.ofFloat("x", cardView.x, cardView.x - transitionX) + val pvhX = PropertyValuesHolder.ofFloat("x", cardView.x, cardView.x + calculateTransition()) val pvhY = PropertyValuesHolder.ofFloat("y", cardView.y, cardView.y * 2) animateCardOffScreen(duration, pvhX, pvhY) if (!isClicked) { - cardCallback.onCardSwipedLeft(adapterPosition, cardView, notify) + cardCallback.onCardSwipedRight(adapterPosition, cardView, notify) } else { - cardCallback.onCardMovedOnClickLeft(adapterPosition, cardView, notify) + cardCallback.onCardMovedOnClickRight(adapterPosition, cardView, notify) } } - fun animateOffScreenRight(duration: Int, notify: Boolean, isClicked: Boolean) { + private fun calculateTransition(): Float { var targetY = cardView.y if (initialCardPositionY > cardView.y) { @@ -99,21 +104,15 @@ class CardOperator(val koloda: Koloda, val cardView: View, val adapterPosition: } val maxCardWidth = koloda.getMaxCardWidth(cardView) - val transitionX = koloda.parentWidth - (koloda.parentWidth / 2 - cardView.width / 2) + + return koloda.parentWidth - (koloda.parentWidth / 2 - cardView.width / 2) + cardView.x + Math.abs(maxCardWidth / 2) - - val pvhX = PropertyValuesHolder.ofFloat("x", cardView.x, cardView.x + transitionX) - val pvhY = PropertyValuesHolder.ofFloat("y", cardView.y, cardView.y * 2) - animateCardOffScreen(duration, pvhX, pvhY) - - if (!isClicked) { - cardCallback.onCardSwipedRight(adapterPosition, cardView, notify) - } else { - cardCallback.onCardMovedOnClickRight(adapterPosition, cardView, notify) - } } - private fun animateCardOffScreen(duration: Int, pvhX: PropertyValuesHolder, pvhY: PropertyValuesHolder) { + private fun animateCardOffScreen( + duration: Int, + pvhX: PropertyValuesHolder, + pvhY: PropertyValuesHolder + ) { swipedCardOffScreen() val valueAnimator = ValueAnimator() @@ -134,18 +133,28 @@ class CardOperator(val koloda: Koloda, val cardView: View, val adapterPosition: private fun checkCardPosition() { when { - cardBeyondLeftBorder() -> animateOffScreenLeft(DEFAULT_OFF_SCREEN_ANIMATION_DURATION, true, false) - cardBeyondRightBorder() -> animateOffScreenRight(DEFAULT_OFF_SCREEN_ANIMATION_DURATION, true, false) + cardBeyondLeftBorder() -> animateOffScreenLeft( + DEFAULT_OFF_SCREEN_ANIMATION_DURATION, + true, + false + ) + cardBeyondRightBorder() -> animateOffScreenRight( + DEFAULT_OFF_SCREEN_ANIMATION_DURATION, + true, + false + ) else -> resetCardPosition(DEFAULT_RESET_ANIMATION_DURATION) } } private fun resetCardPosition(duration: Int) { - currentCardAnimator = ObjectAnimator.ofPropertyValuesHolder(cardView, - PropertyValuesHolder.ofFloat(View.X, initialCardPositionX), - PropertyValuesHolder.ofFloat(View.Y, initialCardPositionY), - PropertyValuesHolder.ofFloat(View.ROTATION, 0f)) - .setDuration(duration.toLong()) + currentCardAnimator = ObjectAnimator.ofPropertyValuesHolder( + cardView, + PropertyValuesHolder.ofFloat(View.X, initialCardPositionX), + PropertyValuesHolder.ofFloat(View.Y, initialCardPositionY), + PropertyValuesHolder.ofFloat(View.ROTATION, 0f) + ) + .setDuration(duration.toLong()) currentCardAnimator?.duration = 200 currentCardAnimator?.addUpdateListener { updateCardProgress() } currentCardAnimator?.addListener(object : AnimatorListenerAdapter() { @@ -169,7 +178,8 @@ class CardOperator(val koloda: Koloda, val cardView: View, val adapterPosition: } private fun updateCardProgress() { - var sideProgress = ((cardView.x + (cardView.width / 2)) - (koloda.parentWidth / 2)) / (koloda.parentWidth / 2) + var sideProgress = + ((cardView.x + (cardView.width / 2)) - (koloda.parentWidth / 2)) / (koloda.parentWidth / 2) if (sideProgress > 1f) { sideProgress = 1f } else if (sideProgress < -1f) { @@ -181,8 +191,17 @@ class CardOperator(val koloda: Koloda, val cardView: View, val adapterPosition: private fun updateCardProgress(sideProgress: Float) { cardCallback.onCardDrag(adapterPosition, cardView, sideProgress) - val cardOffsetProgress = Math.max(Math.abs(cardView.x / cardView.width), - Math.abs(cardView.y / cardView.height)) + val cardOffsetProgress = Math.max( + Math.abs(cardView.x / cardView.width), + Math.abs(cardView.y / cardView.height) + ) + + if (sideProgress > 0) { + cardView.changeRightOverlayAlpha(sideProgress) + } else { + cardView.changeLeftOverlayAlpha(sideProgress) + } + if (!(isSwipedOffScreen && cardOffsetProgress > 1)) { cardCallback.onCardOffset(adapterPosition, cardView, sideProgress) } @@ -207,10 +226,10 @@ class CardOperator(val koloda: Koloda, val cardView: View, val adapterPosition: val action = event.actionMasked when (action) { MotionEvent.ACTION_DOWN -> { - if (velocityTrecker == null) { - velocityTrecker = VelocityTracker.obtain() + if (velocityTracker == null) { + velocityTracker = VelocityTracker.obtain() } else { - velocityTrecker?.clear() + velocityTracker?.clear() } if (animationCycle == AnimationCycle.NO_ANIMATION && !isBeingDragged) { isBeingDragged = true @@ -228,10 +247,10 @@ class CardOperator(val koloda: Koloda, val cardView: View, val adapterPosition: } MotionEvent.ACTION_MOVE -> { - velocityTrecker?.addMovement(event) + velocityTracker?.addMovement(event) val pointerId = event.getPointerId(event.actionIndex) - velocityTrecker?.computeCurrentVelocity(pointerId, 40f) + velocityTracker?.computeCurrentVelocity(pointerId, 40f) val pointerIndex = event.findPointerIndex(activePointerId) @@ -239,11 +258,13 @@ class CardOperator(val koloda: Koloda, val cardView: View, val adapterPosition: val dx = event.getX(pointerIndex) - initialTouchX val dy = event.getY(pointerIndex) - initialTouchY - val posX = (cardView.x + dx) + Math.abs((VelocityTrackerCompat.getXVelocity(velocityTrecker, pointerId))) - val posY = (cardView.y + dy) + Math.abs((VelocityTrackerCompat.getYVelocity(velocityTrecker, pointerId))) + velocityTracker?.let { + val posX = (cardView.x + dx) + Math.abs(it.getXVelocity(pointerId)) + val posY = (cardView.y + dy) + Math.abs(it.getYVelocity(pointerId)) - cardView.x = posX - cardView.y = posY + cardView.x = posX + cardView.y = posY + } updateCardProgress() } @@ -252,7 +273,11 @@ class CardOperator(val koloda: Koloda, val cardView: View, val adapterPosition: MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { activePointerId = MotionEvent.INVALID_POINTER_ID checkCardPosition() - cardCallback.onCardActionUp(adapterPosition, cardView, (cardBeyondLeftBorder() || cardBeyondRightBorder())) + cardCallback.onCardActionUp( + adapterPosition, + cardView, + (cardBeyondLeftBorder() || cardBeyondRightBorder()) + ) } MotionEvent.ACTION_POINTER_UP -> { @@ -282,4 +307,10 @@ class CardOperator(val koloda: Koloda, val cardView: View, val adapterPosition: animateOffScreenLeft(DEFAULT_OFF_SCREEN_ANIMATION_DURATION, true, true) } + companion object { + private const val DEFAULT_OFF_SCREEN_ANIMATION_DURATION = 600 + private const val DEFAULT_OFF_SCREEN_FLING_ANIMATION_DURATION = 150 + private const val DEFAULT_RESET_ANIMATION_DURATION = 600 + } + } diff --git a/library/src/main/java/com/yalantis/library/Koloda.kt b/library/src/main/java/com/yalantis/library/Koloda.kt index 733f2bb..4864b47 100644 --- a/library/src/main/java/com/yalantis/library/Koloda.kt +++ b/library/src/main/java/com/yalantis/library/Koloda.kt @@ -1,36 +1,47 @@ package com.yalantis.library -import android.animation.ValueAnimator +import android.annotation.TargetApi import android.content.Context import android.database.DataSetObserver +import android.graphics.Color +import android.os.Build +import android.support.annotation.DrawableRes import android.util.AttributeSet -import android.widget.FrameLayout +import android.util.Log import android.view.LayoutInflater import android.view.View import android.widget.Adapter -import android.os.Build -import android.annotation.TargetApi -import android.util.Log +import android.widget.FrameLayout /** * Created by anna on 11/10/17. */ -class Koloda : FrameLayout { +class Koloda +@JvmOverloads +constructor(context: Context, attrs: AttributeSet? = null, + defStyleAttr: Int = 0) : FrameLayout(context, attrs, defStyleAttr), CardLayout.CardParams { - companion object { - private const val DEFAULT_MAX_VISIBLE_CARDS = 3 - private const val DEFAULT_ROTATION_ANGLE = 30f - private const val DEFAULT_SCALE_DIFF = 0.04f + init { + init(attrs) } private var maxVisibleCards = DEFAULT_MAX_VISIBLE_CARDS - private var cardPositionOffsetX = resources.getDimensionPixelSize(R.dimen.default_card_spacing) private var cardPositionOffsetY = resources.getDimensionPixelSize(R.dimen.default_card_spacing) private var cardRotationDegrees = DEFAULT_ROTATION_ANGLE + private var scaleDiff = DEFAULT_SCALE_DIFF private var dyingViews = hashSetOf() private var activeViews = linkedSetOf() private var dataSetObservable: DataSetObserver? = null var isNeedCircleLoading = false + @DrawableRes + var rightOverlay: Int? = null + @DrawableRes + var leftOverlay: Int? = null + var cardXPos = 0 + var cardYPos = 0 + var cardWidth = 0 + var cardHeight = 0 + private var alphaAnimation = false var adapter: Adapter? = null set(value) { @@ -40,28 +51,21 @@ class Koloda : FrameLayout { field?.registerDataSetObserver(createDataSetObserver()) dataSetObservable?.onChanged() } - private var adapterPosition = 0 + + private var adapterPosition = -1 private var deckMap = hashMapOf() var kolodaListener: KolodaListener? = null internal var parentWidth = 0 private var swipeEnabled = true - constructor(context: Context) : this(context, null) - - constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0) - - constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { - init(attrs) - } - private fun init(attrs: AttributeSet?) { val a = context.obtainStyledAttributes(attrs, R.styleable.Koloda) val cardLayoutId = a.getResourceId(R.styleable.Koloda_koloda_card_layout, -1) maxVisibleCards = a.getInt(R.styleable.Koloda_koloda_max_visible_cards, DEFAULT_MAX_VISIBLE_CARDS) - cardPositionOffsetX = a.getDimensionPixelSize(R.styleable.Koloda_koloda_card_offsetX, resources.getDimensionPixelSize(R.dimen.default_card_spacing)) cardPositionOffsetY = a.getDimensionPixelSize(R.styleable.Koloda_koloda_card_offsetY, resources.getDimensionPixelSize(R.dimen.default_card_spacing)) cardRotationDegrees = a.getFloat(R.styleable.Koloda_koloda_card_rotate_angle, DEFAULT_ROTATION_ANGLE) - + scaleDiff = a.getFloat(R.styleable.Koloda_koloda_card_scale_diff, DEFAULT_SCALE_DIFF) + alphaAnimation = a.getBoolean(R.styleable.Koloda_koloda_card_animation_alpha, false) a.recycle() if (isInEditMode) { @@ -74,16 +78,20 @@ class Koloda : FrameLayout { */ private fun addCardToDeck() { - if (adapterPosition < adapter?.count ?: 0) { - val newCard = adapter?.getView(adapterPosition, null, this) - initializeCardPosition(newCard) - newCard?.let { + if (adapterPosition < adapter?.count?.minus(1) ?: 0) { + + val cardLayout = CardLayout(context, cardParams = this) + + initializeCardPosition(cardLayout) + cardLayout.let { addView(it, 0) - deckMap.put(it, CardOperator(this, it, adapterPosition++, cardCallback)) + deckMap[it] = CardOperator(this, it, adapterPosition++, cardCallback) + val newCard = adapter?.getView(adapterPosition, null, it) + it.addOverlays(newCard, rightOverlay, leftOverlay) } } else if (isNeedCircleLoading) { - adapterPosition = 0 - addCardToDeck() + adapterPosition = -1 + dataSetObservable?.onChanged() } } @@ -94,10 +102,8 @@ class Koloda : FrameLayout { private fun initializeCardPosition(view: View?) { val childCount = childCount - dyingViews.size - scaleView(view, 0f, childCount) + scaleView(view, childCount = childCount) view?.translationY = (cardPositionOffsetY * childCount).toFloat() - Log.i("----> elem init", childCount.toString()) - Log.i("----> translation init", view?.translationY.toString()) setZTranslations(childCount) } @@ -123,25 +129,26 @@ class Koloda : FrameLayout { } } - fun getMaxCardWidth(cardView: View): Float = cardView.height * Math.tan(Math.toRadians(cardRotationDegrees.toDouble())).toFloat() + fun getMaxCardWidth(cardView: View): Float = cardView.height * Math.tan(Math + .toRadians(cardRotationDegrees.toDouble())).toFloat() + /** + * Checks capability of card view swiping + * + * @param - card of Desk + */ fun canSwipe(card: View): Boolean { - Log.e("====>Swipe ", swipeEnabled.toString()) - Log.e("====>Active viewsempty ", activeViews.isEmpty().toString()) - Log.e("====>Or contains ", (activeViews.contains(card)).toString()) - Log.e("====>Index ", (indexOfChild(card) >= childCount - 2).toString()) return (swipeEnabled && (activeViews.isEmpty() || activeViews.contains(card)) && indexOfChild(card) >= childCount - 2) } private fun updateDeckCardsPosition(progress: Float) { - val visibleChildCount = Math.min(childCount, maxVisibleCards + 1) - var childCount = Math.min(childCount, maxVisibleCards) + val childCount = Math.min(childCount, maxVisibleCards) var cardsWillBeMoved = 0 var cardView: View - (0 until visibleChildCount).map { + (0 until childCount).map { cardView = getChildAt(it) if (deckMap.containsKey(cardView) && deckMap[cardView]?.isBeingDragged != true) { cardsWillBeMoved++ @@ -151,18 +158,23 @@ class Koloda : FrameLayout { if (progress != 0.0f) { for (i in 0 until cardsWillBeMoved) { cardView = getChildAt(i) - cardView.translationY = (cardPositionOffsetY * Math.min(cardsWillBeMoved, visibleChildCount - i - 1) - cardPositionOffsetY * Math.abs(progress)) + cardView.translationY = (cardPositionOffsetY * Math + .min(cardsWillBeMoved, maxVisibleCards - i - 1) - cardPositionOffsetY * Math.abs(progress)) } } } - private fun scaleView(view: View?, progress: Float, childCount: Int) { - val currentScale = 1f - (childCount * DEFAULT_SCALE_DIFF) - val nextScale = 1f - ((childCount - 1) * DEFAULT_SCALE_DIFF) + private fun scaleView(view: View?, progress: Float = 0f, childCount: Int) { + val currentScale = 1f - (childCount * scaleDiff) + val nextScale = 1f - ((childCount - 1) * scaleDiff) val scale = currentScale + (nextScale - currentScale) * Math.abs(progress) if (scale <= 1f) { view?.scaleX = scale view?.scaleY = scale + if (alphaAnimation) { + (view as CardLayout).layoutAlpha = scale + } + } } @@ -171,6 +183,13 @@ class Koloda : FrameLayout { card.rotation = rotation } + override fun onParamsMeasure(layoutParamsCardView: LayoutParams) { + cardXPos = layoutParamsCardView.leftMargin + cardYPos = layoutParamsCardView.topMargin + cardPositionOffsetY + cardPositionOffsetY + cardWidth = layoutParamsCardView.width + cardHeight = layoutParamsCardView.height + } + override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { super.onLayout(changed, left, top, right, bottom) if (changed) { @@ -198,6 +217,7 @@ class Koloda : FrameLayout { private fun createDataSetObserver(): DataSetObserver { dataSetObservable = object : DataSetObserver() { + override fun onChanged() { super.onChanged() addCards() @@ -211,6 +231,7 @@ class Koloda : FrameLayout { } private fun addCards() { + val childCount = childCount - dyingViews.size for (i in childCount until maxVisibleCards) { addCardToDeck() @@ -222,6 +243,7 @@ class Koloda : FrameLayout { } private var cardCallback: CardCallback = object : CardCallback { + override fun onCardActionDown(adapterPosition: Int, card: View) { activeViews.add(card) } @@ -235,8 +257,8 @@ class Koloda : FrameLayout { updateDeckCardsPosition(offsetProgress) } - override fun onCardActionUp(adapterPosition: Int, card: View, isCardNeedRemove: Boolean) { - if (isCardNeedRemove) { + override fun onCardActionUp(adapterPosition: Int, card: View, isCardRemove: Boolean) { + if (isCardRemove) { activeViews.remove(card) } } @@ -297,29 +319,36 @@ class Koloda : FrameLayout { } fun onClickRight() { - val childCount = childCount - val topCard = getChildAt(childCount - 1 - dyingViews.size) - topCard?.let { - activeViews.add(it) - val cardOperator: CardOperator? = deckMap[it] - cardOperator?.onClickRight() - it.rotation = 10f - } + onButtonClick(true) } fun onClickLeft() { + onButtonClick(false) + } + + /** + * Button right or left click remove card from desk + * @param isSwipeCardToRight - true for right click remove aimation false for left click + * remove animation + */ + fun onButtonClick(isSwipeCardToRight: Boolean) { val childCount = childCount val topCard = getChildAt(childCount - 1 - dyingViews.size) topCard?.let { activeViews.add(it) val cardOperator: CardOperator? = deckMap[it] - cardOperator?.onClickLeft() - it.rotation = -10f + if (isSwipeCardToRight) { + cardOperator?.onClickRight() + it.rotation = 10f + } else { + cardOperator?.onClickLeft() + it.rotation = -10f + } } } private fun findPositionAfterClick() { - var childCount = Math.min(childCount, maxVisibleCards) + val childCount = Math.min(childCount, maxVisibleCards) (0 until childCount).map { val view = getChildAt(it) scaleView(view, 0f, childCount - it - 1) @@ -327,4 +356,69 @@ class Koloda : FrameLayout { } } + /** + * Reload all data. Start show data from the beginning + */ + fun reloadAdapterData() { + removeAllViews() + adapterPosition = 0 + dataSetObservable?.onChanged() + } + + /** + * Reload previous card after every call. If current card position in adapter == 0 this method do nothing + */ + fun reloadPreviousCard() { + if (isNeedCircleLoading) { + var pos = adapterPosition - (DEFAULT_MAX_VISIBLE_CARDS) + if (pos < 0) + pos = adapter?.count?.plus(pos) ?: 0 + + addCardOnTop(pos) + + } else { + addCardOnTop(adapterPosition - (DEFAULT_MAX_VISIBLE_CARDS)) + } + } + + private fun addCardOnTop(position: Int) { + removeView(getChildAt(0)) + activeViews.clear() + adapterPosition = position + 2 + adapter?.let { + if (adapterPosition >= it.count) { + adapterPosition -= it.count + } + } + + val cardLayout = CardLayout(context, cardParams = this) + + cardLayout.let { + addView(it, Math.min(childCount, maxVisibleCards + 1)) + val newCard = adapter?.getView(position, null, it) + deckMap[it] = CardOperator(this, it, position, cardCallback) + it.addOverlays(newCard, rightOverlay, leftOverlay) + animateWhenAddOnTop() + dataSetObservable?.onChanged() + } + } + + private fun animateWhenAddOnTop() { + val visibleChildCount = Math.min(childCount, maxVisibleCards + 1) + val childCount = Math.min(childCount, maxVisibleCards) + + (0 until visibleChildCount).map { + val view = getChildAt(it) + val count = childCount - it - 1 + scaleView(view, childCount = count) + view?.translationY = (cardPositionOffsetY * count).toFloat() + } + } + + companion object { + private const val DEFAULT_MAX_VISIBLE_CARDS = 3 + private const val DEFAULT_ROTATION_ANGLE = 30f + private const val DEFAULT_SCALE_DIFF = 0.04f + } + } \ No newline at end of file diff --git a/library/src/main/res/values/attrs.xml b/library/src/main/res/values/attrs.xml index bf953be..23359bd 100644 --- a/library/src/main/res/values/attrs.xml +++ b/library/src/main/res/values/attrs.xml @@ -4,9 +4,10 @@ - + + \ No newline at end of file