Repository: bitvale/DroidMotion Branch: master Commit: 8bf3d8e269fd Files: 73 Total size: 100.9 KB Directory structure: gitextract_vvxwq36_/ ├── .gitignore ├── LICENSE ├── README.md ├── app/ │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── com/ │ │ └── bitvale/ │ │ └── droidmotion/ │ │ └── ExampleInstrumentedTest.kt │ ├── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── com/ │ │ │ └── bitvale/ │ │ │ └── droidmotion/ │ │ │ ├── MainActivity.kt │ │ │ ├── adapter/ │ │ │ │ ├── BaseViewHolder.kt │ │ │ │ └── RecyclerAdapter.kt │ │ │ ├── common/ │ │ │ │ ├── Constants.kt │ │ │ │ └── Extensions.kt │ │ │ ├── fragment/ │ │ │ │ ├── BaseFragment.kt │ │ │ │ ├── DetailsFragment.kt │ │ │ │ └── RecyclerFragment.kt │ │ │ ├── listener/ │ │ │ │ ├── BottomNavigationViewListener.kt │ │ │ │ └── OnBackPressedListener.kt │ │ │ └── model/ │ │ │ └── DataProvider.kt │ │ └── res/ │ │ ├── anim/ │ │ │ ├── item_animation_fall_down.xml │ │ │ └── layout_animation_fall_down.xml │ │ ├── animator/ │ │ │ └── main_list_animator.xml │ │ ├── drawable/ │ │ │ ├── bottom_navigation_selector.xml │ │ │ ├── ic_complaint.xml │ │ │ ├── ic_correct.xml │ │ │ ├── ic_error.xml │ │ │ ├── ic_export.xml │ │ │ ├── ic_file.xml │ │ │ ├── ic_incorrect.xml │ │ │ ├── ic_left.xml │ │ │ ├── ic_menu_account.xml │ │ │ ├── ic_menu_dialog.xml │ │ │ ├── ic_menu_gift.xml │ │ │ ├── ic_menu_main.xml │ │ │ ├── ic_menu_wallet.xml │ │ │ ├── ic_negative.xml │ │ │ ├── ic_not_received.xml │ │ │ ├── ic_positive.xml │ │ │ ├── ic_received.xml │ │ │ ├── ic_right.xml │ │ │ ├── ic_sort.xml │ │ │ ├── img_apple.xml │ │ │ ├── img_dribble.xml │ │ │ ├── img_facebook.xml │ │ │ ├── img_github.xml │ │ │ ├── img_google_play.xml │ │ │ ├── img_instagram.xml │ │ │ ├── img_twitter.xml │ │ │ ├── img_vimeo.xml │ │ │ ├── img_vk.xml │ │ │ ├── img_youtube.xml │ │ │ └── splash_screen.xml │ │ ├── layout/ │ │ │ ├── activity_main.xml │ │ │ ├── fragment_details.xml │ │ │ ├── fragment_recycler.xml │ │ │ ├── item_card.xml │ │ │ └── item_details.xml │ │ ├── menu/ │ │ │ └── bottom_navigation_main.xml │ │ ├── transition/ │ │ │ └── shared_element_transition.xml │ │ ├── values/ │ │ │ ├── colors.xml │ │ │ ├── dimens.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ ├── values-sw600dp/ │ │ │ └── dimens.xml │ │ └── values-sw720dp/ │ │ └── dimens.xml │ └── test/ │ └── java/ │ └── com/ │ └── bitvale/ │ └── droidmotion/ │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat └── settings.gradle ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ *.iml .gradle /local.properties /.idea/caches/build_file_checksums.ser /.idea/libraries /.idea/modules.xml /.idea/workspace.xml .DS_Store /build /captures .externalNativeBuild .idea app/release/ ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2018 Alexander Kolpakov Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # DroidMotion sample [![Platform](https://img.shields.io/badge/platform-android-green.svg)](http://developer.android.com/index.html) [![API](https://img.shields.io/badge/API-19%2B-brightgreen.svg?style=flat)](https://android-arsenal.com/api?level=19) [![GitHub license](https://img.shields.io/github/license/mashape/apistatus.svg)](/LICENSE) This is an Android sample project that shows how to implement a simple android motion. # You can see the description of the implementation on medium and get the sample app with similar motions on Google Play. Android app on Google Play Read on Medium Design on Dribbble # ================================================ FILE: app/.gitignore ================================================ /build ================================================ FILE: app/build.gradle ================================================ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' android { compileSdkVersion rootProject.compileSdkVersion defaultConfig { applicationId "com.bitvale.droidmotion" minSdkVersion rootProject.minSdkVersion targetSdkVersion rootProject.targetSdkVersion versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary = true } buildTypes { debug { applicationIdSuffix '.debug' versionNameSuffix '-DEBUG' useProguard false minifyEnabled false } release { minifyEnabled true useProguard true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } androidExtensions { experimental = true } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) // Testing dependencies testImplementation "junit:junit:$rootProject.junitVersion" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$rootProject.kotlinVersion" implementation "androidx.appcompat:appcompat:$rootProject.androidxVersion" implementation "androidx.recyclerview:recyclerview:$rootProject.androidxVersion" implementation "androidx.cardview:cardview:$rootProject.androidxVersion" implementation "com.google.android.material:material:$rootProject.materialVersion" implementation "androidx.constraintlayout:constraintlayout:$rootProject.constraintLayoutVersion" implementation "androidx.core:core-ktx:$rootProject.androidxVersion" } ================================================ FILE: app/proguard-rules.pro ================================================ # 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 # hideBottomNavigationView the original source file name. #-renamesourcefileattribute SourceFile ================================================ FILE: app/src/androidTest/java/com/bitvale/droidmotion/ExampleInstrumentedTest.kt ================================================ package com.bitvale.droidmotion import androidx.test.InstrumentationRegistry import androidx.test.runner.AndroidJUnit4 import org.junit.Test import org.junit.runner.RunWith import org.junit.Assert.* /** * Instrumented test, which will execute on an Android device. * * See [testing documentation](http://d.android.com/tools/testing). */ @RunWith(AndroidJUnit4::class) class ExampleInstrumentedTest { @Test fun useAppContext() { // Context of the app under test. val appContext = InstrumentationRegistry.getTargetContext() assertEquals("com.bitvale.animation", appContext.packageName) } } ================================================ FILE: app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: app/src/main/java/com/bitvale/droidmotion/MainActivity.kt ================================================ package com.bitvale.droidmotion import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment import com.bitvale.androidmotion.R import com.bitvale.droidmotion.common.replaceFragmentInActivity import com.bitvale.droidmotion.fragment.RecyclerFragment import com.bitvale.droidmotion.listener.BottomNavigationViewListener import com.bitvale.droidmotion.listener.OnBackPressedListener import kotlinx.android.synthetic.main.activity_main.* /** * Created by Alexander Kolpakov on 25.07.2018 */ class MainActivity : AppCompatActivity(), BottomNavigationViewListener { override fun onCreate(savedInstanceState: Bundle?) { setTheme(R.style.AppTheme) super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) setupFragment(RecyclerFragment.newInstance()) } private fun setupFragment(fragment: Fragment) { supportFragmentManager.findFragmentById(R.id.fragment_container) ?: fragment.let { replaceFragmentInActivity(it, R.id.fragment_container) } } override fun hideBottomNavigationView() { if (bottom_navigation.translationY == 0f) bottom_navigation.animate() .translationY(bottom_navigation.height.toFloat()) .setDuration(250) .start() } override fun showBottomNavigationView() { if (bottom_navigation.translationY >= bottom_navigation.height.toFloat()) bottom_navigation.animate() .translationY(0f) .setDuration(400) .start() } override fun onBackPressed() { val fragmentList = supportFragmentManager.fragments var proceedToSuper = true for (fragment in fragmentList) { if (fragment is OnBackPressedListener) { proceedToSuper = false (fragment as OnBackPressedListener).onBackPressed() } } if (proceedToSuper) super.onBackPressed() } } ================================================ FILE: app/src/main/java/com/bitvale/droidmotion/adapter/BaseViewHolder.kt ================================================ package com.bitvale.droidmotion.adapter import android.os.Build import androidx.recyclerview.widget.RecyclerView import android.view.View import android.view.ViewGroup import com.bitvale.androidmotion.R import com.bitvale.droidmotion.common.inflate import com.bitvale.droidmotion.model.DataProvider.BaseData import com.bitvale.droidmotion.model.DataProvider.Card import com.bitvale.droidmotion.model.DataProvider.Details import com.bitvale.droidmotion.common.TRANSITION_CARD import kotlinx.android.extensions.LayoutContainer import kotlinx.android.synthetic.main.item_card.* import kotlinx.android.synthetic.main.item_details.* /** * Created by Alexander Kolpakov on 29.07.2018 */ abstract class BaseViewHolder(view: View) : RecyclerView.ViewHolder(view), LayoutContainer { override val containerView: View? get() = itemView abstract fun bind(data: T, listener: View.OnClickListener? = null) class CardViewHolder(parent: ViewGroup) : BaseViewHolder(parent.inflate(R.layout.item_card)) { override fun bind(data: Card, listener: View.OnClickListener?) { containerView?.setOnClickListener(listener) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { containerView?.transitionName = TRANSITION_CARD + adapterPosition } tv_title.text = data.name tv_amount.text = data.amount tv_date.text = data.date tv_status.text = data.status.code img_status.setImageResource(data.status.iconId) img_card.setImageResource(data.imageId) } } class DetailsViewHolder(parent: ViewGroup) : BaseViewHolder
(parent.inflate(R.layout.item_details)) { override fun bind(data: Details, listener: View.OnClickListener?) { tv_details_title.text = data.title tv_details_subtitle.text = data.subtitle tv_details_amount.text = data.amount } } } ================================================ FILE: app/src/main/java/com/bitvale/droidmotion/adapter/RecyclerAdapter.kt ================================================ package com.bitvale.droidmotion.adapter import androidx.recyclerview.widget.RecyclerView import android.view.View import android.view.ViewGroup import com.bitvale.droidmotion.model.DataProvider.BaseData import com.bitvale.droidmotion.model.DataProvider.Details import com.bitvale.droidmotion.adapter.BaseViewHolder.DetailsViewHolder import com.bitvale.droidmotion.adapter.BaseViewHolder.CardViewHolder /** * Created by Alexander Kolpakov on 17.07.2018 */ class RecyclerAdapter(private var dataSet: List, private var listener: View.OnClickListener? = null) : RecyclerView.Adapter>() { @Suppress("UNCHECKED_CAST") override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder { return when (viewType) { 0 -> DetailsViewHolder(parent) as BaseViewHolder else -> CardViewHolder(parent) as BaseViewHolder } } override fun getItemCount() = dataSet.size override fun getItemViewType(position: Int): Int { val type = dataSet[0].javaClass return when (type) { Details::class.java -> 0 else -> 1 } } override fun onBindViewHolder(holder: BaseViewHolder, position: Int) = holder.bind(dataSet[position], listener) } ================================================ FILE: app/src/main/java/com/bitvale/droidmotion/common/Constants.kt ================================================ package com.bitvale.droidmotion.common const val TRANSITION_CARD = "card_transition_" const val TRANSITION_TOOLBAR = "toolbar_transition" const val EXTRA_COORDINATES = "coordinates" const val EXTRA_POSITION = "position" ================================================ FILE: app/src/main/java/com/bitvale/droidmotion/common/Extensions.kt ================================================ package com.bitvale.droidmotion.common import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.content.Context import android.os.Build import android.util.TypedValue import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.ImageView import androidx.annotation.LayoutRes import androidx.appcompat.app.AppCompatActivity import androidx.cardview.widget.CardView import androidx.core.view.drawToBitmap import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentTransaction import com.bitvale.androidmotion.R /** * Created by Alexander Kolpakov on 17.07.2018 */ fun ViewGroup.inflate(@LayoutRes layoutId: Int, attachToRoot: Boolean = false): View { return LayoutInflater.from(context).inflate(layoutId, this, attachToRoot) } fun Context.getStatusBarHeight(): Int { val resourceId = resources.getIdentifier("status_bar_height", "dimen", "android") return if (resourceId > 0) resources.getDimensionPixelSize(resourceId) else 0 } fun Context.getToolbarHeight(): Int { val tv = TypedValue() return if (theme.resolveAttribute(android.R.attr.actionBarSize, tv, true)) TypedValue.complexToDimensionPixelSize(tv.data, resources.displayMetrics) else 0 } fun AppCompatActivity.replaceFragmentInActivity(fragment: Fragment, frameId: Int) { supportFragmentManager.transact { replace(frameId, fragment) } } private inline fun FragmentManager.transact(action: FragmentTransaction.() -> Unit) { beginTransaction().apply { action() }.commit() } fun View.copyViewImage(): View { val copy = ImageView(context) val bitmap = drawToBitmap() copy.setImageBitmap(bitmap) // On pre-Lollipop when we create a copy, the card view's shadow is copied too as content and // we do not need an additional card view. return (if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { CardView(context).apply { cardElevation = resources.getDimension(R.dimen.card_elevation) radius = resources.getDimension(R.dimen.card_corner_radius) addView(copy) } } else { copy }).apply { layoutParams = this@copyViewImage.layoutParams layoutParams.height = this@copyViewImage.height layoutParams.width = this@copyViewImage.width x = this@copyViewImage.x y = this@copyViewImage.y } } fun Animator.withEndAction(action: () -> Unit): Animator { addListener(object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator?) { action() } }) return this } fun Animator.withStartAction(action: () -> Unit): Animator { addListener(object : AnimatorListenerAdapter() { override fun onAnimationStart(animation: Animator?) { action() } }) return this } inline fun supportsLollipop(action: () -> Unit) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { action() } } ================================================ FILE: app/src/main/java/com/bitvale/droidmotion/fragment/BaseFragment.kt ================================================ package com.bitvale.droidmotion.fragment import android.os.Bundle import android.view.View import android.view.ViewGroup import androidx.cardview.widget.CardView import androidx.core.view.updatePadding import androidx.fragment.app.Fragment import com.bitvale.androidmotion.R import com.bitvale.droidmotion.common.getStatusBarHeight /** * Created by Alexander Kolpakov on 26.07.2018 */ open class BaseFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { setupToolbarMargin(view) } private fun setupToolbarMargin(view: View) { val toolbar = view.findViewById(R.id.toolbar) toolbar?.let { val statusBarHeight = context?.getStatusBarHeight() val lp = it.layoutParams as ViewGroup.LayoutParams lp.height += statusBarHeight as Int if (it is CardView) { it.setContentPadding(0, statusBarHeight, 0, 0) } else { it.updatePadding(top = statusBarHeight) } } } } ================================================ FILE: app/src/main/java/com/bitvale/droidmotion/fragment/DetailsFragment.kt ================================================ package com.bitvale.droidmotion.fragment import android.animation.AnimatorInflater import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.view.animation.AnticipateInterpolator import android.view.animation.OvershootInterpolator import androidx.core.view.doOnLayout import androidx.core.view.updatePadding import com.bitvale.androidmotion.R import com.bitvale.droidmotion.common.* import com.bitvale.droidmotion.listener.OnBackPressedListener import com.bitvale.droidmotion.model.DataProvider import kotlinx.android.synthetic.main.fragment_details.* import kotlinx.android.synthetic.main.item_card.* /** * Created by Alexander Kolpakov on 25.07.2018 */ class DetailsFragment : BaseFragment(), OnBackPressedListener { private lateinit var coordinates: FloatArray companion object { const val TAG = "DetailsFragment" fun newInstance(coordinates: FloatArray, adapterPosition: Int): DetailsFragment { val bundle = Bundle().apply { putFloatArray(EXTRA_COORDINATES, coordinates) putInt(EXTRA_POSITION, adapterPosition) } return DetailsFragment().apply { arguments = bundle } } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) arguments?.let { coordinates = it.getFloatArray(EXTRA_COORDINATES) } } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? = inflater.inflate(R.layout.fragment_details, container, false) override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) val position = if (arguments != null) (arguments as Bundle).getInt(EXTRA_POSITION) else 0 setupViews(position) if (savedInstanceState == null) { animateToolbar() fab_negative.animate() .translationY(0f) .setDuration(650) .setInterpolator(OvershootInterpolator(4f)) .start() fab_positive.animate() .translationY(0f) .setStartDelay(100) .setDuration(650) .setInterpolator(OvershootInterpolator(4f)) .start() } else { toolbar.alpha = 1f } } private fun setupViews(position: Int) { supportsLollipop { details_card.transitionName = TRANSITION_CARD + position toolbar_container.transitionName = TRANSITION_TOOLBAR } (details_card.layoutParams as ViewGroup.MarginLayoutParams).topMargin = coordinates[2].toInt() val data = DataProvider.getCardData()[position] tv_title.text = data.name tv_amount.text = data.amount tv_date.text = data.date tv_status.text = data.status.code img_status.setImageResource(data.status.iconId) img_card.setImageResource(data.imageId) fab_negative.setOnClickListener { onBackPressed() } with(recycler_view) { adapter = com.bitvale.droidmotion.adapter.RecyclerAdapter(DataProvider.getDetailsData()) setHasFixedSize(true) fab_negative.doOnLayout { val paddingBottom = (paddingBottom + fab_negative.height * 1.5).toInt() updatePadding(bottom = paddingBottom) } } } override fun onBackPressed() { animateViewsOut() } private fun animateViewsOut() { val translateTo = fab_negative.height * 2f AnimatorInflater.loadAnimator(activity, R.animator.main_list_animator).apply { setTarget(recycler_view) start() } fab_negative.animate() .translationY(translateTo) .setDuration(350) .setInterpolator(AnticipateInterpolator(2f)) .start() fab_positive.animate() .translationY(translateTo) .setStartDelay(50) .setDuration(350) .withEndAction { activity?.supportFragmentManager?.popBackStack() } .setInterpolator(AnticipateInterpolator(2f)) .start() animateToolbar(0f, 350) } private fun animateToolbar(alphaTo: Float = 1f, duration: Long = 200) { toolbar.animate().alpha(alphaTo).setDuration(duration).start() } } ================================================ FILE: app/src/main/java/com/bitvale/droidmotion/fragment/RecyclerFragment.kt ================================================ package com.bitvale.droidmotion.fragment import android.animation.AnimatorInflater import android.animation.ValueAnimator import android.os.Bundle import android.transition.TransitionInflater import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.core.view.doOnLayout import androidx.fragment.app.FragmentTransaction import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.bitvale.androidmotion.R import com.bitvale.droidmotion.adapter.RecyclerAdapter import com.bitvale.droidmotion.common.* import com.bitvale.droidmotion.listener.BottomNavigationViewListener import com.bitvale.droidmotion.model.DataProvider import kotlinx.android.synthetic.main.fragment_recycler.* /** * Created by Alexander Kolpakov on 25.07.2018 */ class RecyclerFragment : BaseFragment(), View.OnClickListener { companion object { const val TAG = "RecyclerFragment" fun newInstance(): RecyclerFragment { return RecyclerFragment() } } var bottomNavListener: BottomNavigationViewListener? = null override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { return inflater.inflate(R.layout.fragment_recycler, container, false) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) setupViews() if (savedInstanceState == null) { root.doOnLayout { toolbar.animate() .translationY(0f) .alpha(1f) .setDuration(400) .start() bottomNavListener?.showBottomNavigationView() } } else { toolbar.alpha = 1f toolbar.translationY = 0f } } private fun setupViews() { supportsLollipop { details_toolbar_transition_helper.transitionName = TRANSITION_TOOLBAR } details_toolbar_transition_helper.translationY = -resources.getDimension(R.dimen.details_toolbar_container_height) toolbar.translationY = -toolbar.context.getToolbarHeight().toFloat() val elevation = resources.getDimension(R.dimen.toolbar_elevation) with(recycler_view) { adapter = RecyclerAdapter(DataProvider.getCardData(), this@RecyclerFragment) setHasFixedSize(true) val lm = layoutManager as LinearLayoutManager addOnScrollListener(object : RecyclerView.OnScrollListener() { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { if (dy > 0) bottomNavListener?.hideBottomNavigationView() if (dy < 0) bottomNavListener?.showBottomNavigationView() if (lm.findFirstCompletelyVisibleItemPosition() == 0) { if (toolbar.cardElevation == 0f) return animateToolbarElevation(true) } else { if (toolbar.cardElevation > 0f) return toolbar.cardElevation = elevation } } }) } } override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) if (activity is BottomNavigationViewListener) { bottomNavListener = activity as BottomNavigationViewListener } else { throw ClassCastException("$activity must implement BottomNavigationViewListener") } } override fun onClick(view: View) { val fragmentTransaction = initFragmentTransaction(view) val copy = view.copyViewImage() copy.y += toolbar.height root.addView(copy) view.visibility = View.INVISIBLE startAnimation(copy, fragmentTransaction) } private fun initFragmentTransaction(view: View): FragmentTransaction? { val toY = view.resources.getDimensionPixelOffset(R.dimen.details_toolbar_container_height) - view.height / 2f val positions = FloatArray(3) positions[0] = view.x positions[1] = view.y + toolbar.height positions[2] = toY val adapterPosition = recycler_view.getChildAdapterPosition(view) val detailsFragment = DetailsFragment.newInstance(positions, adapterPosition) val transaction = fragmentManager?.beginTransaction() ?.replace(R.id.fragment_container, detailsFragment, DetailsFragment.TAG) ?.addToBackStack(null) supportsLollipop { val transition = TransitionInflater.from(context) .inflateTransition(R.transition.shared_element_transition) detailsFragment.sharedElementEnterTransition = transition transaction ?.addSharedElement(view, view.transitionName) ?.addSharedElement(details_toolbar_transition_helper, details_toolbar_transition_helper.transitionName) } return transaction } private fun startAnimation(view: View, fragmentTransaction: FragmentTransaction?) { AnimatorInflater.loadAnimator(activity, R.animator.main_list_animator).apply { setTarget(recycler_view) withStartAction { if (toolbar.cardElevation > 0) animateToolbarElevation(true) } withEndAction { recycler_view.visibility = View.INVISIBLE val toY = view.resources.getDimensionPixelOffset(R.dimen.details_toolbar_container_height) - view.height / 2f view.animate().y(toY).start() toolbar.animate() .translationY(-toolbar.height.toFloat()) .alpha(0f) .setDuration(600) .withStartAction { bottomNavListener?.hideBottomNavigationView() details_toolbar_transition_helper.animate().translationY(0f).setDuration(500).start() } .withEndAction { fragmentTransaction?.commitAllowingStateLoss() } .start() } start() } } private fun animateToolbarElevation(animateOut: Boolean) { var valueFrom = resources.getDimension(R.dimen.toolbar_elevation) var valueTo = 0f if (!animateOut) { valueTo = valueFrom valueFrom = 0f } ValueAnimator.ofFloat(valueFrom, valueTo).setDuration(250).apply { startDelay = 0 addUpdateListener { toolbar.cardElevation = it.animatedValue as Float } start() } } } ================================================ FILE: app/src/main/java/com/bitvale/droidmotion/listener/BottomNavigationViewListener.kt ================================================ package com.bitvale.droidmotion.listener /** * Created by Alexander Kolpakov on 29.07.2018 */ interface BottomNavigationViewListener { fun hideBottomNavigationView() fun showBottomNavigationView() } ================================================ FILE: app/src/main/java/com/bitvale/droidmotion/listener/OnBackPressedListener.kt ================================================ package com.bitvale.droidmotion.listener /** * Created by Alexander Kolpakov on 26.07.2018 */ interface OnBackPressedListener { fun onBackPressed() } ================================================ FILE: app/src/main/java/com/bitvale/droidmotion/model/DataProvider.kt ================================================ package com.bitvale.droidmotion.model import androidx.annotation.DrawableRes import com.bitvale.androidmotion.R /** * Created by Alexander Kolpakov on 24.07.2018 */ object DataProvider { fun getCardData(): List { val data = ArrayList() data.add(Card(0, "Google Play", "28.07.2018", "$38,456.78", Status.COMPLAINT, R.drawable.img_google_play)) data.add(Card(1, "Twitter", "27.07.2018", "$1,550.60", Status.RECEIVED, R.drawable.img_twitter)) data.add(Card(2, "YouTube", "27.07.2018", "$14,340.00", Status.CORRECT, R.drawable.img_youtube)) data.add(Card(3, "Dribbble", "26.07.2018", "$2,678.27", Status.ERROR, R.drawable.img_dribble)) data.add(Card(4, "Apple Store", "26.07.2018", "$20,479.12", Status.CORRECT, R.drawable.img_apple)) data.add(Card(5, "VK", "25.07.2018", "$13,846.13", Status.INCORRECT, R.drawable.img_vk)) data.add(Card(6, "Instagram", "25.07.2018", "$24,856.17", Status.NOT_RECEIVED, R.drawable.img_instagram)) data.add(Card(7, "Github", "24.07.2018", "$376.90", Status.COMPLAINT, R.drawable.img_github)) data.add(Card(8, "Vimeo", "23.07.2018", "$7,568.02", Status.RECEIVED, R.drawable.img_vimeo)) data.add(Card(9, "Facebook", "10.07.2018", "$18,347.32", Status.INCORRECT, R.drawable.img_facebook)) return data } fun getDetailsData(): List
{ val data = ArrayList
() data.add(Details(0, "In App purchase\"Gem\"", "$14,340.00 X1 (including VAT 10%)", "$14,340.00")) data.add(Details(0, "In App purchase \"Money\"", "$2,456.78 X1 (including VAT 10%)", "$2,456.78")) data.add(Details(0, "Interstitial Ads", "$1,150.15 X1 (including VAT 10%)", "$1,150.15")) data.add(Details(0, "Rewarded video", "$566.20 X1 (including VAT 10%)", "$566.20")) return data } interface BaseData data class Card(val id: Int, val name: String, val date: String, val amount: String, val status: Status, @DrawableRes val imageId: Int) : BaseData data class Details(val id: Int, val title: String, val subtitle: String, val amount: String) : BaseData enum class Status(val code: String, @DrawableRes val iconId: Int) { CORRECT("Correct", R.drawable.ic_correct), INCORRECT("Incorrect", R.drawable.ic_incorrect), COMPLAINT("Complaint", R.drawable.ic_complaint), ERROR("Error", R.drawable.ic_error), RECEIVED("Received", R.drawable.ic_received), NOT_RECEIVED("Not received", R.drawable.ic_not_received) } } ================================================ FILE: app/src/main/res/anim/item_animation_fall_down.xml ================================================ ================================================ FILE: app/src/main/res/anim/layout_animation_fall_down.xml ================================================ ================================================ FILE: app/src/main/res/animator/main_list_animator.xml ================================================ ================================================ FILE: app/src/main/res/drawable/bottom_navigation_selector.xml ================================================ ================================================ FILE: app/src/main/res/drawable/ic_complaint.xml ================================================ ================================================ FILE: app/src/main/res/drawable/ic_correct.xml ================================================ ================================================ FILE: app/src/main/res/drawable/ic_error.xml ================================================ ================================================ FILE: app/src/main/res/drawable/ic_export.xml ================================================ ================================================ FILE: app/src/main/res/drawable/ic_file.xml ================================================ ================================================ FILE: app/src/main/res/drawable/ic_incorrect.xml ================================================ ================================================ FILE: app/src/main/res/drawable/ic_left.xml ================================================ ================================================ FILE: app/src/main/res/drawable/ic_menu_account.xml ================================================ ================================================ FILE: app/src/main/res/drawable/ic_menu_dialog.xml ================================================ ================================================ FILE: app/src/main/res/drawable/ic_menu_gift.xml ================================================ ================================================ FILE: app/src/main/res/drawable/ic_menu_main.xml ================================================ ================================================ FILE: app/src/main/res/drawable/ic_menu_wallet.xml ================================================ ================================================ FILE: app/src/main/res/drawable/ic_negative.xml ================================================ ================================================ FILE: app/src/main/res/drawable/ic_not_received.xml ================================================ ================================================ FILE: app/src/main/res/drawable/ic_positive.xml ================================================ ================================================ FILE: app/src/main/res/drawable/ic_received.xml ================================================ ================================================ FILE: app/src/main/res/drawable/ic_right.xml ================================================ ================================================ FILE: app/src/main/res/drawable/ic_sort.xml ================================================ ================================================ FILE: app/src/main/res/drawable/img_apple.xml ================================================ ================================================ FILE: app/src/main/res/drawable/img_dribble.xml ================================================ ================================================ FILE: app/src/main/res/drawable/img_facebook.xml ================================================ ================================================ FILE: app/src/main/res/drawable/img_github.xml ================================================ ================================================ FILE: app/src/main/res/drawable/img_google_play.xml ================================================ ================================================ FILE: app/src/main/res/drawable/img_instagram.xml ================================================ ================================================ FILE: app/src/main/res/drawable/img_twitter.xml ================================================ ================================================ FILE: app/src/main/res/drawable/img_vimeo.xml ================================================ ================================================ FILE: app/src/main/res/drawable/img_vk.xml ================================================ ================================================ FILE: app/src/main/res/drawable/img_youtube.xml ================================================ ================================================ FILE: app/src/main/res/drawable/splash_screen.xml ================================================ ================================================ FILE: app/src/main/res/layout/activity_main.xml ================================================ ================================================ FILE: app/src/main/res/layout/fragment_details.xml ================================================ ================================================ FILE: app/src/main/res/layout/fragment_recycler.xml ================================================ ================================================ FILE: app/src/main/res/layout/item_card.xml ================================================ ================================================ FILE: app/src/main/res/layout/item_details.xml ================================================ ================================================ FILE: app/src/main/res/menu/bottom_navigation_main.xml ================================================ ================================================ FILE: app/src/main/res/transition/shared_element_transition.xml ================================================ ================================================ FILE: app/src/main/res/values/colors.xml ================================================ #3aa3f7 #025497 #008dff #ff4d6a @color/colorAccent @color/colorAccent #505a595b #4d4d4d #eee @android:color/white ================================================ FILE: app/src/main/res/values/dimens.xml ================================================ 26sp 22sp 18sp 16sp 14sp 12sp 4dp 8dp 16dp 24dp 32dp 48dp 64dp @dimen/space_tiny @dimen/space_small @dimen/space_normal @dimen/space_large @dimen/space_medium @dimen/space_tiny @dimen/space_small @dimen/space_normal @dimen/space_medium @dimen/space_large @dimen/space_xlarge 100dp @dimen/space_tiny @dimen/space_tiny @dimen/space_tiny @dimen/space_small 56dp @dimen/space_medium 156dp 146dp ================================================ FILE: app/src/main/res/values/strings.xml ================================================ DroidMotion My income Status Date Amount Back ================================================ FILE: app/src/main/res/values/styles.xml ================================================ ================================================ FILE: app/src/main/res/values-sw600dp/dimens.xml ================================================ 164dp ================================================ FILE: app/src/main/res/values-sw720dp/dimens.xml ================================================ 8dp 12dp 20dp 36dp 52dp 68dp 28sp 24sp 20sp 18sp 16sp 14sp 76dp 196dp ================================================ FILE: app/src/test/java/com/bitvale/droidmotion/ExampleUnitTest.kt ================================================ package com.bitvale.droidmotion import org.junit.Test import org.junit.Assert.* /** * Example local unit test, which will execute on the development machine (host). * * See [testing documentation](http://d.android.com/tools/testing). */ class ExampleUnitTest { @Test fun addition_isCorrect() { assertEquals(4, 2 + 2) } } ================================================ FILE: build.gradle ================================================ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { ext { // Sdk and tools compileSdkVersion = 28 minSdkVersion = 19 targetSdkVersion = 28 // App dependencies constraintLayoutVersion = '1.1.2' gradleVersion = '3.3.0-alpha10' junitVersion = '4.12' kotlinVersion = '1.2.61' androidxVersion = "1.0.0-rc02" materialVersion = "1.0.0-rc01" constraintLayoutVersion = '1.1.2' } repositories { google() jcenter() } dependencies { classpath "com.android.tools.build:gradle:$gradleVersion" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } } allprojects { repositories { google() jcenter() } } task clean(type: Delete) { delete rootProject.buildDir } ================================================ FILE: gradle/wrapper/gradle-wrapper.properties ================================================ #Wed Sep 12 22:37:27 EET 2018 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-4.10-all.zip ================================================ FILE: gradle.properties ================================================ # Project-wide Gradle settings. # IDE (e.g. Android Studio) users: # Gradle settings configured through the IDE *will override* # any settings specified in this file. # For more details on how to configure your build environment visit # http://www.gradle.org/docs/current/userguide/build_environment.html # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. android.enableJetifier=true android.useAndroidX=true org.gradle.jvmargs=-Xmx1536m # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true ================================================ FILE: gradlew ================================================ #!/usr/bin/env sh ############################################################################## ## ## Gradle start up script for UN*X ## ############################################################################## # Attempt to set APP_HOME # Resolve links: $0 may be a link PRG="$0" # Need this for relative symlinks. while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG=`dirname "$PRG"`"/$link" fi done SAVED="`pwd`" cd "`dirname \"$PRG\"`/" >/dev/null APP_HOME="`pwd -P`" cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS="" # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" warn () { echo "$*" } die () { echo echo "$*" echo exit 1 } # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false case "`uname`" in CYGWIN* ) cygwin=true ;; Darwin* ) darwin=true ;; MINGW* ) msys=true ;; NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD="java" which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi # Increase the maximum file descriptors if we can. if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then MAX_FD="$MAX_FD_LIMIT" fi ulimit -n $MAX_FD if [ $? -ne 0 ] ; then warn "Could not set maximum file descriptor limit: $MAX_FD" fi else warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" fi fi # For Darwin, add options to specify how the application appears in the dock if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi # For Cygwin, switch paths to Windows format before running java if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` SEP="" for dir in $ROOTDIRSRAW ; do ROOTDIRS="$ROOTDIRS$SEP$dir" SEP="|" done OURCYGPATTERN="(^($ROOTDIRS))" # Add a user-defined pattern to the cygpath arguments if [ "$GRADLE_CYGPATTERN" != "" ] ; then OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" fi # Now convert the arguments - kludge to limit ourselves to /bin/sh i=0 for arg in "$@" ; do CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` else eval `echo args$i`="\"$arg\"" fi i=$((i+1)) done case $i in (0) set -- ;; (1) set -- "$args0" ;; (2) set -- "$args0" "$args1" ;; (3) set -- "$args0" "$args1" "$args2" ;; (4) set -- "$args0" "$args1" "$args2" "$args3" ;; (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi # Escape application args save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } APP_ARGS=$(save "$@") # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then cd "$(dirname "$0")" fi exec "$JAVACMD" "$@" ================================================ FILE: gradlew.bat ================================================ @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS= @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if "%ERRORLEVEL%" == "0" goto init echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto init echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :init @rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args :win9xME_args @rem Slurp the command line arguments. set CMD_LINE_ARGS= set _SKIP=2 :win9xME_args_slurp if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% :end @rem End local scope for the variables with windows NT shell if "%ERRORLEVEL%"=="0" goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 exit /b 1 :mainEnd if "%OS%"=="Windows_NT" endlocal :omega ================================================ FILE: settings.gradle ================================================ include ':app'