Repository: iielse/learn-coordinatorlayout-behavior Branch: master Commit: 306d8c9cc401 Files: 65 Total size: 128.6 KB Directory structure: gitextract_cqq1i4ei/ ├── .gitignore ├── .idea/ │ ├── gradle.xml │ ├── misc.xml │ ├── modules.xml │ ├── runConfigurations.xml │ └── vcs.xml ├── README.md ├── app/ │ ├── .gitignore │ ├── build.gradle │ ├── libs/ │ │ └── overscroll-release-v1.1-20160904.jar │ ├── proguard-rules.pro │ └── src/ │ └── main/ │ ├── AndroidManifest.xml │ ├── java/ │ │ └── com/ │ │ └── example/ │ │ └── learn/ │ │ ├── App.kt │ │ ├── BaseActivity.kt │ │ ├── Bus.kt │ │ ├── BusEvent.kt │ │ ├── Candy.kt │ │ ├── Data.kt │ │ ├── business/ │ │ │ ├── MerchantActivity.kt │ │ │ ├── MerchantCommentLayout.kt │ │ │ ├── MerchantContentLayout.kt │ │ │ ├── MerchantFoodLayout.kt │ │ │ ├── MerchantInfoLayout.kt │ │ │ ├── MerchantPageBehavior.kt │ │ │ ├── MerchantPageLayout.kt │ │ │ ├── MerchantSettleLayout.kt │ │ │ ├── MerchantTitleLayout.kt │ │ │ ├── MyEventLayout.kt │ │ │ └── TicketView.kt │ │ └── view/ │ │ ├── ScrollHelper.kt │ │ ├── SmartTabLayout1.java │ │ ├── ViewPager2.java │ │ └── ViewState.kt │ └── res/ │ ├── drawable/ │ │ ├── ic_launcher_background.xml │ │ ├── merchant_search_shape.xml │ │ ├── settle_submit_shape.xml │ │ ├── shadow_b_000_shape.xml │ │ ├── shadow_b_fff_shape.xml │ │ ├── tab_title_selector.xml │ │ └── transparent.xml │ ├── layout/ │ │ ├── merchant_activity.xml │ │ ├── merchant_content_layout.xml │ │ ├── merchant_food_list_content.xml │ │ ├── merchant_food_list_content_image.xml │ │ ├── merchant_food_list_recommend.xml │ │ ├── merchant_food_list_recommend_cell.xml │ │ ├── merchant_food_list_side.xml │ │ ├── merchant_food_list_sticky_title.xml │ │ ├── merchant_food_list_top.xml │ │ ├── merchant_page_cell_layout.xml │ │ ├── merchant_page_food_layout.xml │ │ ├── merchant_page_layout.xml │ │ ├── merchant_settle_layout.xml │ │ ├── merchant_title_layout.xml │ │ └── ticket_view.xml │ └── values/ │ ├── frames.xml │ └── strings.xml ├── app-behavior.apk ├── 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/workspace.xml /.idea/libraries .DS_Store /build /captures .externalNativeBuild ================================================ FILE: .idea/gradle.xml ================================================ ================================================ FILE: .idea/misc.xml ================================================ ================================================ FILE: .idea/modules.xml ================================================ ================================================ FILE: .idea/runConfigurations.xml ================================================ ================================================ FILE: .idea/vcs.xml ================================================ ================================================ FILE: README.md ================================================ # behavior-learn 该项目是为了练习-> 使用`CoordinatorLayout.Behavior` 实现页面复杂联动效果 代码模仿实现美团商家详情界面内容联动 ![](https://github.com/iielse/behavior-learn/blob/master/preview2.gif) [download apk](https://github.com/iielse/behavior-learn/blob/master/app-behavior.apk) 开发使用知识点顺带涉及到: 1. Scroller+Handler 实现View自动滑动 2. View属性动画 3. 触摸事件分发机制 4. ConstraintLayout约束布局 emmmmmmm 继续加油咯~ ================================================ FILE: app/.gitignore ================================================ /build ================================================ FILE: app/build.gradle ================================================ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' android { compileSdkVersion COMPILE_SDK_VERSION as int buildToolsVersion ANDROID_SUPPORT_VERSION defaultConfig { applicationId APPLICATION_ID minSdkVersion MIN_SDK_VERSION as int targetSdkVersion TARGET_SDK_VERSION as int versionCode VERSION_CODE as int versionName VERSION_NAME flavorDimensions VERSION_CODE multiDexEnabled true ndk { abiFilters 'armeabi' } } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } sourceSets { main.jniLibs.srcDirs = ['libs'] } lintOptions { abortOnError false } packagingOptions { exclude 'META-INF/DEPENDENCIES.txt' exclude 'META-INF/LICENSE.txt' exclude 'META-INF/NOTICE.txt' exclude 'META-INF/NOTICE' exclude 'META-INF/LICENSE' exclude 'META-INF/DEPENDENCIES' exclude 'META-INF/notice.txt' exclude 'META-INF/license.txt' exclude 'META-INF/dependencies.txt' exclude 'META-INF/LGPL2.1' } } dependencies { implementation rootProject.ext.support_multidex implementation rootProject.ext.support_support_v4 implementation rootProject.ext.support_appcompat_v7 implementation rootProject.ext.support_recyclerview_v7 implementation rootProject.ext.support_design implementation 'com.android.support.constraint:constraint-layout:1.1.1' implementation rootProject.ext.kotlin implementation rootProject.ext.kotlin_stdlib implementation rootProject.ext.kotlin_anko_common implementation rootProject.ext.glide annotationProcessor rootProject.ext.glide_compiler implementation rootProject.ext.glide_transformations implementation rootProject.ext.list_pull_to_refresh implementation rootProject.ext.list_adapter implementation rootProject.ext.eventbus implementation files('libs/overscroll-release-v1.1-20160904.jar') implementation 'com.oushangfeng:PinnedSectionItemDecoration:1.2.4' implementation 'com.ogaclejapan.smarttablayout:library:1.6.1@aar' implementation 'com.hyman:flowlayout-lib:1.1.1' } repositories { mavenCentral() flatDir { dirs 'libs' } } ================================================ 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 # hide the original source file name. #-renamesourcefileattribute SourceFile ================================================ FILE: app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: app/src/main/java/com/example/learn/App.kt ================================================ package com.example.learn import android.app.Application import android.content.Context import android.support.multidex.MultiDex class App : Application() { override fun attachBaseContext(base: Context) { super.attachBaseContext(base) MultiDex.install(this) } override fun onCreate() { super.onCreate() INSTANCE = this } companion object { private var INSTANCE: App? = null @Synchronized fun get(): App = INSTANCE!! } } ================================================ FILE: app/src/main/java/com/example/learn/BaseActivity.kt ================================================ package com.example.learn import android.annotation.SuppressLint import android.support.v7.app.AppCompatActivity @SuppressLint("Registered") open class BaseActivity : AppCompatActivity() ================================================ FILE: app/src/main/java/com/example/learn/Bus.kt ================================================ package com.example.learn object Bus { private var EVENT_ID_PRODUCER = 1 val FOOD_TOP_HEIGHT = EVENT_ID_PRODUCER++ val FOOD_RECOMMEND_HEIGHT = EVENT_ID_PRODUCER++ } ================================================ FILE: app/src/main/java/com/example/learn/BusEvent.kt ================================================ package com.example.learn class BusEvent { constructor(a: Int) { act = a } constructor(a: Int, o: Any) : this(a) { obj = o } constructor(a: Int, o: Any, o2: Any) : this(a, o) { obj2 = o2 } constructor(a: Int, o: Any, o2: Any, o3: Any) : this(a, o, o2) { obj3 = o3 } var act: Int = 0 var obj: Any? = null var obj2: Any? = null var obj3: Any? = null } ================================================ FILE: app/src/main/java/com/example/learn/Candy.kt ================================================ package com.example.learn import android.annotation.SuppressLint import android.app.Activity import android.content.Context import android.content.ContextWrapper import android.graphics.drawable.Drawable import android.net.Uri import android.os.Handler import android.os.Looper import android.support.v4.app.FragmentActivity import android.support.v4.content.ContextCompat import android.util.DisplayMetrics import android.util.Log import android.view.View import android.view.WindowManager import android.view.animation.AnimationUtils import android.widget.ImageView import android.widget.TextView import android.widget.Toast import com.bumptech.glide.Glide import com.bumptech.glide.Priority import com.bumptech.glide.load.MultiTransformation import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions import com.bumptech.glide.request.RequestOptions import jp.wasabeef.glide.transformations.BitmapTransformation import org.greenrobot.eventbus.EventBus val dispatcher = Handler(Looper.getMainLooper()) val crossFade = DrawableTransitionOptions().crossFade(200) val centerCrop = RequestOptions().centerCrop().placeholder(android.R.color.transparent).priority(Priority.NORMAL) val argbEvaluator = android.animation.ArgbEvaluator() fun ImageView?.load(res: Any?, transform: List? = null) { if (this == null) return context.activity()?.let { val requestOptions = if (transform != null) RequestOptions.bitmapTransform(MultiTransformation(transform)) else centerCrop Glide.with(it).load(when (res) { is Int?, is String?, is Uri?, is Drawable? -> res else -> throw IllegalArgumentException("imageView load un support res type") }).apply(requestOptions).transition(crossFade).into(this) } } fun TextView?.text(res: Any?) { if (this == null) return text = when (res) { is Int -> resString(res) is String -> res else -> throw IllegalArgumentException("imageView load un support res type") } } fun Context?.activity(): Activity? { if (this == null) return null if (this.javaClass.name.contains("com.android.internal.policy.DecorContext")) { try { val field = this.javaClass.getDeclaredField("mPhoneWindow") field.isAccessible = true val obj = field.get(this) val m1 = obj.javaClass.getMethod("getContext") return (m1.invoke(obj)) as Activity } catch (e: Exception) { e.printStackTrace() } } var context = this while (context is ContextWrapper) { if (context is Activity) { return context } context = context.baseContext } return null } fun View?.activity(): Activity? { if (this == null) return null return context.activity() } fun Activity?.v4(): FragmentActivity? { if (this == null) return null return this as? FragmentActivity } fun Any.resDrawable(drawableRes: Int, scale: Float = 1F): Drawable = ContextCompat.getDrawable(App.get(), drawableRes)!!.apply { setBounds(0, 0, (minimumWidth * scale).toInt(), (minimumHeight * scale).toInt()) } fun Any.resString(stringRes: Int): String = App.get().getString(stringRes) fun Any.resDimension(dimensionRes: Int): Int = App.get().resources.getDimensionPixelSize(dimensionRes) fun Any.dp(dp: Int): Int { val dm = DisplayMetrics() (App.get().getSystemService(Context.WINDOW_SERVICE) as WindowManager) .defaultDisplay.getMetrics(dm) return (dp * dm.density + 0.5f).toInt() } fun Any.screenWidth(): Int { val dm = DisplayMetrics() (App.get().getSystemService(Context.WINDOW_SERVICE) as WindowManager) .defaultDisplay.getMetrics(dm) return dm.widthPixels } fun Any.screenHeight(): Int { val dm = DisplayMetrics() (App.get().getSystemService(Context.WINDOW_SERVICE) as WindowManager) .defaultDisplay.getMetrics(dm) return dm.heightPixels } @SuppressLint("PrivateApi") fun Any.statusBarHeight(): Int { var statusHeight = -1 try { val clazz = Class.forName("com.android.internal.R\$dimen") val `object` = clazz.newInstance() val height = Integer.parseInt(clazz.getField("status_bar_height").get(`object`).toString()) statusHeight = App.get().resources.getDimensionPixelSize(height) } catch (e: Exception) { e.printStackTrace() } return statusHeight } fun Any.toast(content: String) { Toast.makeText(App.get(), content, Toast.LENGTH_SHORT).show() } fun Activity.postDelayed1(r: Runnable, delayMillis: Long = 0) { dispatcher.postDelayed({ if (isFinishing) return@postDelayed r.run() }, delayMillis) } fun View.postDelayed1(r: Runnable, delayMillis: Long = 0) { activity()?.postDelayed1(r, delayMillis) } fun Any.log(content: String, lv: Int = Log.DEBUG) { when (lv) { Log.DEBUG -> Log.d("candy", content) Log.ERROR -> Log.e("candy", content) } } fun Any.postEvent(a: Int, o: Any? = null, o2: Any? = null, o3: Any? = null) { when { o != null && o2 != null && o3 != null -> EventBus.getDefault().post(BusEvent(a, o, o2, o3)) o != null && o2 != null -> EventBus.getDefault().post(BusEvent(a, o, o2)) o != null -> EventBus.getDefault().post(BusEvent(a, o)) else -> EventBus.getDefault().post(BusEvent(a)) } } fun View.anim(animRes: Int, visible: Int = View.VISIBLE) { if (visibility != visible) { visibility = visible startAnimation(AnimationUtils.loadAnimation(App.get(), animRes)) } } ================================================ FILE: app/src/main/java/com/example/learn/Data.kt ================================================ package com.example.learn import com.chad.library.adapter.base.entity.MultiItemEntity import com.example.learn.business.* object Data { fun foodDetails(): List { return listOf( FoodTop(), FoodRecommend(foodRecommends()), FoodCover(R.mipmap.pic1), FoodCover(R.mipmap.pic2), FoodCover(R.mipmap.pic3), FoodTitle("热销"), FoodContent("皮蛋瘦肉粥", R.mipmap.pic4, "皮蛋瘦肉粥是非常受欢迎的粥,其中骚处自行体会..", 16F), FoodContent("香菇瘦肉粥", R.mipmap.pic5, "对于吃货来说:治愈系没事,不过一碗热腾腾的粥", 16F), FoodContent("红枣桂圆粥", R.mipmap.pic6, "对于吃货来说:治愈系没事,不过一碗热腾腾的粥", 16F), FoodTitle("养颜甜粥"), FoodContent("小米南瓜粥", R.mipmap.pic7, "对于吃货来说:治愈系没事,不过一碗热腾腾的粥", 14F), FoodContent("清火绿豆粥", R.mipmap.pic8, "对于吃货来说:治愈系没事,不过一碗热腾腾的粥", 14F), FoodContent("红心地瓜粥", R.mipmap.pic9, "对于吃货来说:治愈系没事,不过一碗热腾腾的粥", 14F), FoodContent("紫薯黑米粥", R.mipmap.pic10, "对于吃货来说:治愈系没事,不过一碗热腾腾的粥", 16F), FoodContent("红枣桂圆粥", R.mipmap.pic6, "对于吃货来说:治愈系没事,不过一碗热腾腾的粥", 16F), FoodContent("柠檬八宝粥", R.mipmap.pic11, "对于吃货来说:治愈系没事,不过一碗热腾腾的粥", 17F), FoodTitle("营养水果粥"), FoodContent("苹果雪梨粥", R.mipmap.pic12, "对于吃货来说:治愈系没事,不过一碗热腾腾的粥", 16F), FoodContent("香蕉苹果粥", R.mipmap.pic13, "对于吃货来说:治愈系没事,不过一碗热腾腾的粥", 16F), FoodContent("香蕉雪梨粥", R.mipmap.pic14, "对于吃货来说:治愈系没事,不过一碗热腾腾的粥", 16F) ) } fun foodMenus(): List { return listOf( FoodTitle("进店须知"), FoodTitle("热销"), FoodTitle("养颜甜粥"), FoodTitle("营养水果粥") ) } private fun foodRecommends(): List { return listOf( FoodContent("烧肉拼叉烧饭+肉包+可乐+虎邦辣椒酱", R.mipmap.pic15, "", 50.6F), FoodContent("绿豆汤+小肉+卤蛋+脆笋片", R.mipmap.pic16, "", 50.6F), FoodContent("冰镇绿豆汤", R.mipmap.pic15, "", 15F), FoodContent("台式炒饭+可乐+葱油饼套餐", R.mipmap.pic16, "", 33F) ) } } ================================================ FILE: app/src/main/java/com/example/learn/business/MerchantActivity.kt ================================================ package com.example.learn.business import android.os.Bundle import com.example.learn.BaseActivity import com.example.learn.R import com.example.learn.log import kotlinx.android.synthetic.main.merchant_activity.* class MerchantActivity : BaseActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.merchant_activity) initialView() log("MerchantActivity onCreate") } private fun initialView() { layContent.laySettle = laySettle } } ================================================ FILE: app/src/main/java/com/example/learn/business/MerchantCommentLayout.kt ================================================ package com.example.learn.business import android.content.Context import android.view.LayoutInflater import android.view.View import android.widget.FrameLayout import com.example.learn.R import kotlinx.android.synthetic.main.merchant_page_cell_layout.view.* class MerchantCommentLayout(context: Context) : FrameLayout(context), ScrollableViewProvider { override fun getScrollableView(): View { return vRecycler } init { LayoutInflater.from(context).inflate(R.layout.merchant_page_cell_layout, this) initialData() } private fun initialData() { vRecycler.setBackgroundColor(0xFFEFEFEF.toInt()) } } ================================================ FILE: app/src/main/java/com/example/learn/business/MerchantContentLayout.kt ================================================ package com.example.learn.business import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.content.Context import android.support.constraint.ConstraintLayout import android.util.AttributeSet import android.view.LayoutInflater import android.view.View import com.example.learn.R import com.example.learn.dp import com.example.learn.load import com.example.learn.view.stateRefresh import com.example.learn.view.stateSave import com.example.learn.view.statesChangeByAnimation import jp.wasabeef.glide.transformations.BlurTransformation import kotlinx.android.synthetic.main.merchant_content_layout.view.* class MerchantContentLayout(context: Context, attrs: AttributeSet?) : ConstraintLayout(context, attrs) { var laySettle: MerchantSettleLayout? = null private var firstLayout: Boolean = false private var isExpanded = false private var effected: Float = 0f init { LayoutInflater.from(context).inflate(R.layout.merchant_content_layout, this) vCover.load(R.mipmap.cover, listOf(BlurTransformation())) layScroll.setOnTouchListener { _, _ -> !isExpanded } vTicket1.set(3, 27, "2018.06.12") vTicket2.set(5, 40, "2018.06.12") vSwitch.setOnClickListener { switch(!isExpanded) } vHide.setOnClickListener { switch(!isExpanded) } } private fun animViews(): Array = arrayOf(laySimple, vAvatar, vMerchantName, layTicket, vTicket1, vTicket2, vSwitchIcon) override fun onWindowFocusChanged(hasWindowFocus: Boolean) { super.onWindowFocusChanged(hasWindowFocus) if (!firstLayout) { firstLayout = true laySimple.stateSave(R.id.vs1).a(1F) laySimple.stateSave(R.id.vs2).a(0F) vMerchantName.stateSave(R.id.vs1).a(0F) vMerchantName.stateSave(R.id.vs2).a(1F) layTicket.stateSave(R.id.vs1).mt(dp(15)) layTicket.stateSave(R.id.vs2).mt(dp(70)) vSwitchIcon.stateSave(R.id.vs1) vSwitchIcon.stateSave(R.id.vs2).r(180F) vAvatar.stateSave(R.id.vs1) val tx: Float = ((width - vAvatar.width) / 2 - (vAvatar.layoutParams as MarginLayoutParams).leftMargin).toFloat() vAvatar.stateSave(R.id.vs2).tx(tx).ty(dp(10).toFloat()) } } // 效果简单实现,具体内容应该根据业务动态计算变化高度区间。 fun effectByOffset(transY: Float) { val p2: Float = when { transY <= dp(10) -> 0F transY > dp(10) && transY < dp(30) -> (transY - dp(10)) / dp(20) else -> 1F } vt2.alpha = p2 vt222.alpha = p2 val p3: Float = when { transY <= dp(40) -> 0F transY > dp(40) && transY < dp(60) -> (transY - dp(40)) / dp(20) else -> 1F } vt3.alpha = p3 vt333.alpha = p3 val p4: Float = when { transY <= dp(70) -> 0F transY > dp(70) && transY < dp(90) -> (transY - dp(70)) / dp(20) else -> 1F } vt4.alpha = p4 vt444.alpha = p4 val p5: Float = when { transY <= dp(110) -> 0F transY > dp(110) && transY < dp(130) -> (transY - dp(110)) / dp(20) else -> 1F } vt5.alpha = p5 vt555.alpha = p5 effected = when { transY <= dp(140) -> 0F transY > dp(140) && transY < dp(230) -> (transY - dp(140)) / dp(90) else -> 1F } animViews().forEach { it.stateRefresh(R.id.vs1, R.id.vs2, effected) } } fun switch(expanded: Boolean, byScrollerSlide: Boolean = false) { if (isExpanded == expanded) { return } layScroll.scrollTo(0, 0) isExpanded = expanded // 目标 val start = effected val end = if (expanded) 1F else 0F statesChangeByAnimation(animViews(), R.id.vs1, R.id.vs2, start, end, null, if (!byScrollerSlide) internalAnimListener else null, 300) laySettle?.switch(isExpanded) } var animListener: AnimatorListenerAdapter1? = null private val internalAnimListener: AnimatorListenerAdapter = object : AnimatorListenerAdapter() { override fun onAnimationStart(animation: Animator?) { animListener?.onAnimationStart(animation, isExpanded) } } interface AnimatorListenerAdapter1 { fun onAnimationStart(animation: Animator?, toExpanded: Boolean) } } ================================================ FILE: app/src/main/java/com/example/learn/business/MerchantFoodLayout.kt ================================================ package com.example.learn.business import android.content.Context import android.support.v7.widget.RecyclerView import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.FrameLayout import android.widget.ImageView import com.chad.library.adapter.base.BaseMultiItemQuickAdapter import com.chad.library.adapter.base.BaseQuickAdapter import com.chad.library.adapter.base.BaseViewHolder import com.chad.library.adapter.base.entity.MultiItemEntity import com.example.learn.* import com.oushangfeng.pinnedsectionitemdecoration.PinnedHeaderItemDecoration import com.oushangfeng.pinnedsectionitemdecoration.utils.FullSpanUtil import kotlinx.android.synthetic.main.merchant_page_food_layout.view.* import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode class MerchantFoodLayout(context: Context) : FrameLayout(context), ScrollableViewProvider { var topHeight: Int = 0 var recommendHeight: Int = 0 init { LayoutInflater.from(context).inflate(R.layout.merchant_page_food_layout, this) initialData() } private fun initialData() { val foodAdapter = FoodAdapter(Data.foodDetails()) vRecycler.addItemDecoration(PinnedHeaderItemDecoration.Builder(FoodAdapter.TYPE_TITLE) .setDividerId(R.drawable.transparent) .create()) vRecycler.adapter = foodAdapter foodAdapter.openLoadAnimation() vRecycler.addOnScrollListener(object : RecyclerView.OnScrollListener() { var totalDy = 0 override fun onScrolled(recyclerView: RecyclerView?, dx: Int, dy: Int) { totalDy -= dy val transY = if (totalDy > -(topHeight + recommendHeight)) totalDy else -(topHeight + recommendHeight) vSide.translationY = transY.toFloat() } }) vSide.isNestedScrollingEnabled = false vSide.adapter = SideAdapter(Data.foodMenus()) } override fun getScrollableView(): View { return vRecycler } override fun onAttachedToWindow() { super.onAttachedToWindow() EventBus.getDefault().register(this) } override fun onDetachedFromWindow() { super.onDetachedFromWindow() EventBus.getDefault().unregister(this) } @Subscribe(threadMode = ThreadMode.MAIN) fun handleBusEvent(event: BusEvent) { when (event.act) { Bus.FOOD_TOP_HEIGHT -> { topHeight = event.obj as Int adjustSideLayoutPosition() } Bus.FOOD_RECOMMEND_HEIGHT -> { recommendHeight = event.obj as Int adjustSideLayoutPosition() } } } private fun adjustSideLayoutPosition() { val lp = (vSide.layoutParams as MarginLayoutParams) if (lp.topMargin != topHeight + recommendHeight) { lp.topMargin = topHeight + recommendHeight lp.height = screenHeight() - resDimension(R.dimen.title_height) - resDimension(R.dimen.merchant_tab_height) vSide.layoutParams = lp vSide.anim(android.R.anim.fade_in, View.VISIBLE) } } } class FoodTop : MultiItemEntity { override fun getItemType(): Int = FoodAdapter.TYPE_TOP } class FoodRecommend(val data: List) : MultiItemEntity { override fun getItemType(): Int = FoodAdapter.TYPE_RECOMMEND } class FoodCover(val url: Int) : MultiItemEntity { override fun getItemType(): Int = FoodAdapter.TYPE_CONTENT_IMAGE } class FoodTitle(val title: String) : MultiItemEntity { override fun getItemType(): Int = FoodAdapter.TYPE_TITLE } class FoodContent(val name: String, val icon: Int, val desc: String, val price: Float) : MultiItemEntity { override fun getItemType(): Int = FoodAdapter.TYPE_CONTENT } class FoodAdapter(data: List) : BaseMultiItemQuickAdapter(data) { companion object { private var TYPE_PRODUCER = 1 val TYPE_TOP = TYPE_PRODUCER++ val TYPE_RECOMMEND = TYPE_PRODUCER++ val TYPE_CONTENT_IMAGE = TYPE_PRODUCER++ val TYPE_TITLE = TYPE_PRODUCER++ val TYPE_CONTENT = TYPE_PRODUCER++ } init { addItemType(TYPE_TOP, R.layout.merchant_food_list_top) addItemType(TYPE_RECOMMEND, R.layout.merchant_food_list_recommend) addItemType(TYPE_CONTENT_IMAGE, R.layout.merchant_food_list_content_image) addItemType(TYPE_TITLE, R.layout.merchant_food_list_sticky_title) addItemType(TYPE_CONTENT, R.layout.merchant_food_list_content) } override fun onAttachedToRecyclerView(recyclerView: RecyclerView) { super.onAttachedToRecyclerView(recyclerView) FullSpanUtil.onAttachedToRecyclerView(recyclerView, this, TYPE_TITLE) } override fun onViewAttachedToWindow(holder: BaseViewHolder) { super.onViewAttachedToWindow(holder) FullSpanUtil.onViewAttachedToWindow(holder, this, TYPE_TITLE) } override fun convert(helper: BaseViewHolder, item: MultiItemEntity) { when (helper.itemViewType) { TYPE_TOP -> { helper.itemView.postDelayed1(Runnable { postEvent(Bus.FOOD_TOP_HEIGHT, helper.itemView.height) }) } TYPE_RECOMMEND -> { helper.itemView.postDelayed1(Runnable { postEvent(Bus.FOOD_RECOMMEND_HEIGHT, helper.itemView.height) }) val view: RecyclerView = helper.getView(R.id.vRecommends) view.adapter = FoodRecommendCellAdapter((item as FoodRecommend).data) } TYPE_CONTENT_IMAGE -> { (item as FoodCover).apply { helper.setImageUrl(R.id.vImage, url) } } TYPE_TITLE -> { (item as FoodTitle).apply { helper.setText(R.id.vTitle, title) } } TYPE_CONTENT -> { (item as FoodContent).apply { helper.setText(R.id.vPrice, "¥$price") .setText(R.id.vName, name) .setText(R.id.vDesc, desc) .setImageUrl(R.id.vIcon, icon) } } } } } fun BaseViewHolder.setImageUrl(viewId: Int, imageUrl: Int): BaseViewHolder { val view: ImageView = getView(viewId) view.load(imageUrl) return this } class FoodRecommendCellAdapter(data: List) : BaseQuickAdapter(data) { init { mLayoutResId = R.layout.merchant_food_list_recommend_cell } override fun convert(helper: BaseViewHolder, item: FoodContent) { (helper.itemView.layoutParams as ViewGroup.MarginLayoutParams).leftMargin = if (mData.indexOf(item) != 0) dp(2) else 0 helper.setText(R.id.vPrice, "¥${item.price}") .setText(R.id.vName, item.name) .setImageUrl(R.id.vImage, item.icon) } } class SideAdapter(data: List) : BaseQuickAdapter(data) { init { mLayoutResId = R.layout.merchant_food_list_side } override fun convert(helper: BaseViewHolder, item: FoodTitle) { item.apply { helper.setText(R.id.vTitle, title) } } } ================================================ FILE: app/src/main/java/com/example/learn/business/MerchantInfoLayout.kt ================================================ package com.example.learn.business import android.content.Context import android.view.LayoutInflater import android.view.View import android.widget.FrameLayout import com.example.learn.R import com.example.learn.log import kotlinx.android.synthetic.main.merchant_page_cell_layout.view.* class MerchantInfoLayout(context: Context) : FrameLayout(context), ScrollableViewProvider { override fun getScrollableView(): View { return vRecycler } init { LayoutInflater.from(context).inflate(R.layout.merchant_page_cell_layout, this) initialData() } private fun initialData() { } } ================================================ FILE: app/src/main/java/com/example/learn/business/MerchantPageBehavior.kt ================================================ package com.example.learn.business import android.animation.Animator import android.content.Context import android.graphics.Color import android.support.design.widget.CoordinatorLayout import android.util.AttributeSet import android.view.View import android.widget.Scroller import com.example.learn.R import com.example.learn.argbEvaluator import com.example.learn.dp import com.example.learn.view.ViewPager2 import kotlinx.android.synthetic.main.merchant_page_layout.view.* class MerchantPageBehavior(context: Context, attrs: AttributeSet) : CoordinatorLayout.Behavior(context, attrs) { private lateinit var selfView: MerchantPageLayout private lateinit var layTitle: MerchantTitleLayout // 商店标题 private lateinit var vPager: ViewPager2 // 商品菜单所在pager private lateinit var layContent: MerchantContentLayout // 商店详情 private lateinit var laySettle: MerchantSettleLayout private val pagingTouchSlop = dp(5) private var horizontalPagingTouch = 0 // 菜单横项列表(推荐商品)内容的触摸滑动距离 private var isScrollRecommends = false private var verticalPagingTouch = 0 // 菜单竖项列表(商品,评价,商家)内容的触摸滑动距离 private var simpleTopDistance = 0 private var isScrollToFullFood = false // 上滑显示商品菜单 private var isScrollToHideFood = false // 下滑显示商店详情 private val scroller = Scroller(context) private val scrollDuration = 800 private val handler = android.os.Handler() private val flingRunnable = object : Runnable { override fun run() { if (scroller.computeScrollOffset()) { selfView.translationY = scroller.currY.toFloat() layContent.effectByOffset(selfView.translationY) laySettle.effectByOffset(selfView.translationY) handler.post(this) } else { isScrollToHideFood = false } } } private val mAnimListener = object : MerchantContentLayout.AnimatorListenerAdapter1 { override fun onAnimationStart(animation: Animator?, toExpanded: Boolean) { if (toExpanded) { val defaultDisplayHeight = (selfView.height - simpleTopDistance) scroller.startScroll(0, selfView.translationY.toInt(), 0, (defaultDisplayHeight - selfView.translationY).toInt(), scrollDuration) } else { scroller.startScroll(0, selfView.translationY.toInt(), 0, (-selfView.translationY).toInt(), scrollDuration) } handler.post(flingRunnable) isScrollToHideFood = true } } override fun onLayoutChild(parent: CoordinatorLayout, child: MerchantPageLayout, layoutDirection: Int): Boolean { selfView = child vPager = child.findViewById(R.id.vPager) val lp = selfView.layoutParams as CoordinatorLayout.LayoutParams if (lp.height == CoordinatorLayout.LayoutParams.MATCH_PARENT) { simpleTopDistance = lp.topMargin - layTitle.height lp.height = parent.height - layTitle.height child.layoutParams = lp return true } return super.onLayoutChild(parent, child, layoutDirection) } override fun layoutDependsOn(parent: CoordinatorLayout, child: MerchantPageLayout, dependency: View): Boolean { when { dependency.id == R.id.layTitle -> layTitle = dependency as MerchantTitleLayout dependency.id == R.id.layContent -> layContent = (dependency as MerchantContentLayout).apply { animListener = mAnimListener } dependency.id == R.id.laySettle -> laySettle = dependency as MerchantSettleLayout else -> return false } return true } override fun onDependentViewChanged(parent: CoordinatorLayout, child: MerchantPageLayout, dependency: View): Boolean = true override fun onStartNestedScroll(coordinatorLayout: CoordinatorLayout, child: MerchantPageLayout, directTargetChild: View, target: View, axes: Int, type: Int): Boolean = true override fun onNestedScrollAccepted(coordinatorLayout: CoordinatorLayout, child: MerchantPageLayout, directTargetChild: View, target: View, axes: Int, type: Int) { scroller.abortAnimation() isScrollToHideFood = false super.onNestedScrollAccepted(coordinatorLayout, child, directTargetChild, target, axes, type) } override fun onNestedPreScroll(coordinatorLayout: CoordinatorLayout, child: MerchantPageLayout, target: View, dx: Int, dy: Int, consumed: IntArray, type: Int) { if (isScrollToHideFood) { consumed[1] = dy return // scroller 滑动中.. do nothing } verticalPagingTouch += dy if (vPager.isScrollable && Math.abs(verticalPagingTouch) > pagingTouchSlop) { vPager.isScrollable = false // 屏蔽 pager横向滑动干扰 } horizontalPagingTouch += dx if (R.id.vRecommends == target.id) { if (!isScrollRecommends) { consumed[0] = dx if (vPager.isScrollable && Math.abs(horizontalPagingTouch) > pagingTouchSlop) isScrollRecommends = true } if (isScrollRecommends) { consumed[1] = dy return // 横项滑动推荐列表中 } } if ((child.translationY < 0 || (child.translationY == 0F && dy > 0)) && !child.canScrollVertically()) { val effect = layTitle.effectByOffset(dy) selfView.vSmartTab.setBackgroundColor(argbEvaluator.evaluate(effect, Color.WHITE, 0xFFFAFAFA.toInt()) as Int) val transY = -simpleTopDistance * effect if (transY != child.translationY) { child.translationY = transY consumed[1] = dy } if (type == 1) { isScrollToFullFood = true } } else if ((child.translationY > 0 || (child.translationY == 0F && dy < 0)) && !child.canScrollVertically()) { if (isScrollToFullFood) { child.translationY = 0F // top fling to bottom } else { child.translationY -= dy layContent.effectByOffset(child.translationY) laySettle.effectByOffset(child.translationY) } consumed[1] = dy } } override fun onNestedPreFling(coordinatorLayout: CoordinatorLayout, child: MerchantPageLayout, target: View, velocityX: Float, velocityY: Float): Boolean { return onUserStopDragging() } override fun onStopNestedScroll(coordinatorLayout: CoordinatorLayout, child: MerchantPageLayout, target: View, type: Int) { isScrollToFullFood = false verticalPagingTouch = 0 vPager.isScrollable = true horizontalPagingTouch = 0 isScrollRecommends = false if (!isScrollToHideFood) { onUserStopDragging() } } private fun onUserStopDragging(): Boolean { if (selfView.translationY < 0f) { return false } val defaultDisplayHeight = (selfView.height - simpleTopDistance) if (defaultDisplayHeight * 0.4F > selfView.translationY) { scroller.startScroll(0, selfView.translationY.toInt(), 0, (-selfView.translationY).toInt(), scrollDuration) layContent.switch(false, true) laySettle.switch(false) } else { scroller.startScroll(0, selfView.translationY.toInt(), 0, (defaultDisplayHeight - selfView.translationY).toInt(), scrollDuration) layContent.switch(true, true) laySettle.switch(true) } handler.post(flingRunnable) isScrollToHideFood = true return true } } ================================================ FILE: app/src/main/java/com/example/learn/business/MerchantPageLayout.kt ================================================ package com.example.learn.business import android.content.Context import android.support.v4.view.PagerAdapter import android.util.AttributeSet import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.LinearLayout import com.example.learn.R import kotlinx.android.synthetic.main.merchant_page_layout.view.* class MerchantPageLayout(context: Context, attrs: AttributeSet?) : LinearLayout(context, attrs) { lateinit var pagerAdapter: MerchantPageAdapter init { LayoutInflater.from(context).inflate(R.layout.merchant_page_layout, this) } override fun onFinishInflate() { super.onFinishInflate() pagerAdapter = MerchantPageAdapter(context) vPager.adapter = pagerAdapter vSmartTab.setViewPager(vPager) } fun canScrollVertically(): Boolean { val view = (pagerAdapter.getItem(vPager.currentItem) as ScrollableViewProvider).getScrollableView() return view.canScrollVertically(-1) } } class MerchantPageAdapter(context: Context) : PagerAdapter() { private val layFood = MerchantFoodLayout(context) private val layInfo = MerchantInfoLayout(context) private val layComment = MerchantCommentLayout(context) override fun getCount(): Int = 3 override fun isViewFromObject(view: View, `object`: Any): Boolean = view == `object` override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) = container.removeView(`object` as View) override fun getPageTitle(position: Int): CharSequence = when (position) { 0 -> "点菜" 1 -> "评论(9999+)" 2 -> "商家" else -> "" } override fun instantiateItem(container: ViewGroup, position: Int): Any { val content = getItem(position) container.addView(content) return content } fun getItem(position: Int): View { return when (position) { 0 -> layFood 1 -> layComment 2 -> layInfo else -> throw RuntimeException("getItem error position $position") } } } interface ScrollableViewProvider { fun getScrollableView(): View } ================================================ FILE: app/src/main/java/com/example/learn/business/MerchantSettleLayout.kt ================================================ package com.example.learn.business import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.content.Context import android.support.constraint.ConstraintLayout import android.util.AttributeSet import android.view.LayoutInflater import android.view.View import com.example.learn.R import com.example.learn.dp import com.example.learn.view.stateRefresh import com.example.learn.view.stateSave import com.example.learn.view.statesChangeByAnimation class MerchantSettleLayout(context: Context, attrs: AttributeSet?) : ConstraintLayout(context, attrs) { private var firstLayout: Boolean = false private var isExpanded = false // layContent 内容是否展开查看中 private var effected: Float = 0f init { LayoutInflater.from(context).inflate(R.layout.merchant_settle_layout, this) } private fun animViews(): Array = arrayOf(this) override fun onWindowFocusChanged(hasWindowFocus: Boolean) { super.onWindowFocusChanged(hasWindowFocus) if (!firstLayout) { firstLayout = true stateSave(R.id.vs1).a(1F) stateSave(R.id.vs2).a(0F) } } // 效果简单实现,具体内容应该根据业务动态计算变化高度区间。 fun effectByOffset(transY: Float) { effected = when { transY <= dp(110) -> 0F transY > dp(110) && transY < dp(140) -> (transY - dp(110)) / dp(30) else -> 1F } animViews().forEach { it.stateRefresh(R.id.vs1, R.id.vs2, effected) } } fun switch(expanded: Boolean) { if (isExpanded == expanded) { return } isExpanded = expanded // 目标 val start = effected val end = if (expanded) 1F else 0F statesChangeByAnimation(animViews(), R.id.vs1, R.id.vs2, start, end, null, object : AnimatorListenerAdapter() { override fun onAnimationStart(animation: Animator?) { visibility = View.VISIBLE } override fun onAnimationEnd(animation: Animator?) { if (isExpanded) visibility = View.INVISIBLE } }, 300) } } ================================================ FILE: app/src/main/java/com/example/learn/business/MerchantTitleLayout.kt ================================================ package com.example.learn.business import android.content.Context import android.content.res.ColorStateList import android.graphics.Color import android.graphics.drawable.Drawable import android.support.v4.graphics.drawable.DrawableCompat import android.util.AttributeSet import android.view.LayoutInflater import android.view.animation.AccelerateDecelerateInterpolator import android.widget.FrameLayout import com.example.learn.R import com.example.learn.argbEvaluator import com.example.learn.dp import com.example.learn.resDimension import com.example.learn.resDrawable import kotlinx.android.synthetic.main.merchant_title_layout.view.* class MerchantTitleLayout(context: Context, attrs: AttributeSet?) : FrameLayout(context, attrs) { private val adInterpolator = AccelerateDecelerateInterpolator() private val offsetMax = resDimension(R.dimen.merchant_offset).toFloat() private var offset = 0F private val drawableBack = resDrawable(R.mipmap.back_white) private val drawableSearch = resDrawable(R.mipmap.icon_search) private val drawableCollection = resDrawable(R.mipmap.icon_collection) private val drawableTogether = resDrawable(R.mipmap.icon_together) private val drawableMenu = resDrawable(R.mipmap.icon_menu) init { LayoutInflater.from(context).inflate(R.layout.merchant_title_layout, this) } fun effectByOffset(dy: Int): Float { if (dy > 0 && offset == offsetMax) return 1F else if (dy < 0 && offset == 0F) return 0F offset += dy if (offset > offsetMax) offset = offsetMax else if (offset < 0) offset = 0F val effect = adInterpolator.getInterpolation(offset / offsetMax) setBackgroundColor(argbEvaluator.evaluate(effect, Color.TRANSPARENT, 0xFFFAFAFA.toInt()) as Int) val e: Int = argbEvaluator.evaluate(effect, Color.WHITE, 0xFF646464.toInt()) as Int vBack.setImageDrawable(tintDrawable(drawableBack, ColorStateList.valueOf(e))) vTogether.setImageDrawable(tintDrawable(drawableTogether, ColorStateList.valueOf(e))) vMenu.setImageDrawable(tintDrawable(drawableMenu, ColorStateList.valueOf(e))) vCollection.setImageDrawable(tintDrawable(drawableCollection, ColorStateList.valueOf(e))) vSearch.setImageDrawable(tintDrawable(drawableSearch, ColorStateList.valueOf(e))) vSearch.scaleX = (1 - 0.4 * effect).toFloat() vSearch.scaleY = (1 - 0.4 * effect).toFloat() vSearch.translationX = -(vSearchBorder.width - vSearch.width + dp(3)) * effect vSearchBorder.alpha = effect vSearchBorder.pivotX = vSearchBorder.width.toFloat() vSearchBorder.scaleX = (0.2 + 0.8 * effect).toFloat() vSearchHint.alpha = effect vSearchHint.translationX = (vSearchHint.width / 3) * (1 - effect) return effect } private fun tintDrawable(drawable: Drawable, colors: ColorStateList): Drawable { val wrappedDrawable = DrawableCompat.wrap(drawable) DrawableCompat.setTintList(wrappedDrawable, colors) return wrappedDrawable } } ================================================ FILE: app/src/main/java/com/example/learn/business/MyEventLayout.kt ================================================ package com.example.learn.business import android.content.Context import android.util.AttributeSet import android.view.MotionEvent import android.view.ViewConfiguration import android.widget.FrameLayout class MyEventLayout(context: Context, attrs: AttributeSet?) : FrameLayout(context, attrs) { private var needDispatchDown = false private var mLastMoveEvent: MotionEvent? = null override fun onInterceptTouchEvent(ev: MotionEvent): Boolean { return true } override fun onTouchEvent(ev: MotionEvent): Boolean { when (ev.action) { MotionEvent.ACTION_DOWN -> { needDispatchDown = true } MotionEvent.ACTION_MOVE -> { dispatchHorizontalTouchEventToChild(ev) } MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { needDispatchDown = false } } mLastMoveEvent = ev return true } private fun dispatchHorizontalTouchEventToChild(current: MotionEvent) { mLastMoveEvent?.apply { if (needDispatchDown) { val d = MotionEvent.obtain(downTime, eventTime + ViewConfiguration.getLongPressTimeout(), MotionEvent.ACTION_DOWN, current.x, y, metaState) getChildAt(0).onTouchEvent(d) needDispatchDown = false } val e = MotionEvent.obtain(downTime, eventTime + ViewConfiguration.getLongPressTimeout(), MotionEvent.ACTION_MOVE, current.x, y, metaState) getChildAt(0).onTouchEvent(e) } } } ================================================ FILE: app/src/main/java/com/example/learn/business/TicketView.kt ================================================ package com.example.learn.business import android.content.Context import android.support.constraint.ConstraintLayout import android.util.AttributeSet import android.view.LayoutInflater import com.example.learn.R import com.example.learn.view.AnimationUpdateListener import com.example.learn.view.stateRefresh import com.example.learn.view.stateSave import com.example.learn.text import kotlinx.android.synthetic.main.ticket_view.view.* class TicketView(context: Context, attrs: AttributeSet?) : ConstraintLayout(context, attrs), AnimationUpdateListener { private var firstLayout: Boolean = false init { LayoutInflater.from(context).inflate(R.layout.ticket_view, this) } override fun onWindowFocusChanged(hasWindowFocus: Boolean) { super.onWindowFocusChanged(hasWindowFocus) if (!firstLayout) { firstLayout = true vBorder1.stateSave(R.id.vs1).a(1F) vBorder1.stateSave(R.id.vs2).ws(3.8F).hs(3.8F).a(0F) vBorder2.stateSave(R.id.vs1).a(0F) vBorder2.stateSave(R.id.vs2).ws(3.8F).hs(3.8F).a(1F) vSimple.stateSave(R.id.vs1) vSimple.stateSave(R.id.vs2).a(0f) layDetail.stateSave(R.id.vs1) layDetail.stateSave(R.id.vs2).sx(1F).sy(1F).a(1F) } } fun set(amount: Int, limit: Int, expireTime: String) { vSimple.text("领¥$amount") vDetail1.text("¥$amount") vDetail2.text("满$limit 可用") vDetail3.text("有效期至$expireTime") } override fun onAnimationUpdate(tag1: Int, tag2: Int, p: Float) { arrayOf(vBorder1, vBorder2, vSimple, layDetail).forEach { it.stateRefresh(tag1, tag2, p) } } } ================================================ FILE: app/src/main/java/com/example/learn/view/ScrollHelper.kt ================================================ package com.example.learn.view import android.support.v7.widget.LinearLayoutManager import android.support.v7.widget.RecyclerView import com.example.learn.log object ScrollHelper { open class OnScrollStateChangedListener { open fun onScrollToBottom() {} } fun init(recyclerView: RecyclerView, callback: OnScrollStateChangedListener) { if (recyclerView.layoutManager !is LinearLayoutManager) throw IllegalArgumentException("just support LinearLayoutManager") val layoutManager: LinearLayoutManager = recyclerView.layoutManager as LinearLayoutManager recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() { private var scrollDown = false override fun onScrolled(vRecycler: RecyclerView?, dx: Int, dy: Int) { scrollDown = if (layoutManager.reverseLayout) dy < 0 else dy > 0 } override fun onScrollStateChanged(vRecycler: RecyclerView?, newState: Int) { when (newState) { RecyclerView.SCROLL_STATE_IDLE -> { val lastVisibleItem = layoutManager.findLastCompletelyVisibleItemPosition() val totalItemCount = layoutManager.itemCount log("onScrollStateChanged lastVisibleItem[$lastVisibleItem] totalItemCount[$totalItemCount] scrollDown[$scrollDown]") if (lastVisibleItem >= totalItemCount - 1 && scrollDown) { callback.onScrollToBottom() log("onScrollStateChanged onScrollToBottom") } } RecyclerView.SCROLL_STATE_DRAGGING -> { } } } }) } open class OnScrollStateChangedListener2 { open fun onScrollToTop() {} } fun init1(recyclerView: RecyclerView, callback: OnScrollStateChangedListener2) { if (recyclerView.layoutManager !is LinearLayoutManager) throw IllegalArgumentException("just1 support LinearLayoutManager") val layoutManager: LinearLayoutManager = recyclerView.layoutManager as LinearLayoutManager recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() { private var scrollDown = false override fun onScrolled(vRecycler: RecyclerView?, dx: Int, dy: Int) { scrollDown = if (layoutManager.reverseLayout) dy < 0 else dy > 0 } override fun onScrollStateChanged(vRecycler: RecyclerView?, newState: Int) { when (newState) { RecyclerView.SCROLL_STATE_IDLE -> { val firstVisibleItem = layoutManager.findFirstCompletelyVisibleItemPosition() log("onScrollStateChanged firstVisibleItem[$firstVisibleItem] scrollUp[${!scrollDown}]") if (firstVisibleItem <= 0 && !scrollDown) { callback.onScrollToTop() log("onScrollStateChanged onScrollToTop") } } RecyclerView.SCROLL_STATE_DRAGGING -> { } } } }) } } ================================================ FILE: app/src/main/java/com/example/learn/view/SmartTabLayout1.java ================================================ package com.example.learn.view; import android.content.Context; import android.graphics.Typeface; import android.support.v4.view.ViewPager; import android.text.TextUtils; import android.util.AttributeSet; import android.view.View; import android.widget.TextView; public class SmartTabLayout1 extends com.ogaclejapan.smarttablayout.SmartTabLayout { private ViewPager vPager; private View vLastTab; private ViewPager.OnPageChangeListener mSmartPageChangeListener; public void setSmartPageChangeListener(ViewPager.OnPageChangeListener cb) { mSmartPageChangeListener = cb; } public SmartTabLayout1(Context context) { this(context, null); } public SmartTabLayout1(Context context, AttributeSet attrs) { super(context, attrs); } public void useFakeBoldSelectedTextTab() { final ViewPager.OnPageChangeListener onPageChangeListener = new ViewPager.OnPageChangeListener() { @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { if (mSmartPageChangeListener != null) mSmartPageChangeListener.onPageScrolled(position, positionOffset, positionOffsetPixels); } @Override public void onPageSelected(int position) { if (mSmartPageChangeListener != null) mSmartPageChangeListener.onPageSelected(position); if (vLastTab != null) { if (vLastTab instanceof TextView) { ((TextView) vLastTab).getPaint().setFakeBoldText(false); } } vLastTab = getTabAt(position); if (vLastTab instanceof TextView) { ((TextView) vLastTab).getPaint().setFakeBoldText(true); } } @Override public void onPageScrollStateChanged(int state) { if (mSmartPageChangeListener != null) mSmartPageChangeListener.onPageScrollStateChanged(state); } }; onPageChangeListener.onPageSelected(vPager.getCurrentItem()); setOnPageChangeListener(onPageChangeListener); } @Override public void setViewPager(ViewPager viewPager) { vPager = viewPager; super.setViewPager(viewPager); useFakeBoldSelectedTextTab(); } @Override protected TextView createDefaultTabView(CharSequence title) { TextView textView = super.createDefaultTabView(title); textView.setTypeface(Typeface.DEFAULT); textView.setSingleLine(true); textView.setEllipsize(TextUtils.TruncateAt.MIDDLE); return textView; } } ================================================ FILE: app/src/main/java/com/example/learn/view/ViewPager2.java ================================================ package com.example.learn.view; import android.content.Context; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.view.ViewPager; import android.util.AttributeSet; import android.view.MotionEvent; public class ViewPager2 extends ViewPager { private boolean scrollable = true; public ViewPager2(@NonNull Context context, @Nullable AttributeSet attrs) { super(context, attrs); } public boolean isScrollable() { return scrollable; } public void setScrollable(boolean scrollable) { this.scrollable = scrollable; } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { return scrollable && super.onInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent ev) { return scrollable && super.onTouchEvent(ev); } } ================================================ FILE: app/src/main/java/com/example/learn/view/ViewState.kt ================================================ package com.example.learn.view import android.animation.AnimatorListenerAdapter import android.animation.ValueAnimator import android.view.View import android.view.ViewGroup import android.view.animation.AccelerateDecelerateInterpolator class ViewState { var topMargin: Int = 0 var bottomMargin: Int = 0 var leftMargin: Int = 0 var rightMargin: Int = 0 var width: Int = 0 var height: Int = 0 var translationX: Float = 0F var translationY: Float = 0F var scaleX: Float = 0F var scaleY: Float = 0F var rotation: Float = 0F var alpha: Float = 0F fun sx(scaleX: Float): ViewState { this.scaleX = scaleX return this } fun sxBy(value: Float): ViewState { this.scaleX *= value return this } fun sy(scaleY: Float): ViewState { this.scaleY = scaleY return this } fun syBy(value: Float): ViewState { this.scaleY *= value return this } fun a(alpha: Float): ViewState { this.alpha = alpha return this } fun w(width: Int): ViewState { this.width = width return this } fun h(height: Int): ViewState { this.height = height return this } fun r(rotation: Float): ViewState { this.rotation = rotation return this } fun ws(s: Float): ViewState { this.width = (width * s).toInt() return this } fun hs(s: Float): ViewState { this.height = (height * s).toInt() return this } fun tx(translationX: Float): ViewState { this.translationX = translationX return this } fun ty(translationY: Float): ViewState { this.translationY = translationY return this } fun ml(leftMargin: Int): ViewState { this.leftMargin = leftMargin return this } fun mr(rightMargin: Int): ViewState { this.rightMargin = rightMargin return this } fun mt(topMargin: Int): ViewState { this.topMargin = topMargin return this } fun mn(bottomMargin: Int): ViewState { this.bottomMargin = bottomMargin return this } fun copy(view: View): ViewState { this.width = view.width this.height = view.height this.translationX = view.translationX this.translationY = view.translationY this.scaleX = view.scaleX this.scaleY = view.scaleY this.rotation = view.rotation this.alpha = view.alpha (view.layoutParams as? ViewGroup.MarginLayoutParams)?.let { this.topMargin = it.topMargin this.bottomMargin = it.bottomMargin this.leftMargin = it.leftMargin this.rightMargin = it.rightMargin } return this } } fun View.stateSet(tag: Int, vs: ViewState) { setTag(tag, vs) } fun View.stateRead(tag: Int): ViewState? { return getTag(tag) as? ViewState } fun View.stateSave(tag: Int): ViewState { val vs = stateRead(tag) ?: ViewState() vs.copy(this) stateSet(tag, vs) return vs } fun View.stateRefresh(tag1: Int, tag2: Int, p: Float) { if (this is AnimationUpdateListener) { onAnimationUpdate(tag1, tag2, p) } else { val vs1 = stateRead(tag1) val vs2 = stateRead(tag2) if (vs1 != null && vs2 != null) { if (vs1.translationX != vs2.translationX) translationX = vs1.translationX + (vs2.translationX - vs1.translationX) * p if (vs1.translationY != vs2.translationY) translationY = vs1.translationY + (vs2.translationY - vs1.translationY) * p if (vs1.scaleX != vs2.scaleX) scaleX = vs1.scaleX + (vs2.scaleX - vs1.scaleX) * p if (vs1.scaleY != vs2.scaleY) scaleY = vs1.scaleY + (vs2.scaleY - vs1.scaleY) * p if (vs1.rotation != vs2.rotation) rotation = (vs1.rotation + (vs2.rotation - vs1.rotation) * p) % 360 if (vs1.alpha != vs2.alpha) alpha = vs1.alpha + (vs2.alpha - vs1.alpha) * p val o = layoutParams var lpChanged = false if (vs1.width != vs2.width) { o.width = (vs1.width + (vs2.width - vs1.width) * p).toInt() lpChanged = true } if (vs1.height != vs2.height) { o.height = (vs1.height + (vs2.height - vs1.height) * p).toInt() lpChanged = true } (o as? ViewGroup.MarginLayoutParams)?.let { if (vs1.topMargin != vs2.topMargin) { it.topMargin = (vs1.topMargin + (vs2.topMargin - vs1.topMargin) * p).toInt() lpChanged = true } if (vs1.bottomMargin != vs2.bottomMargin) { it.bottomMargin = (vs1.bottomMargin + (vs2.bottomMargin - vs1.bottomMargin) * p).toInt() lpChanged = true } if (vs1.leftMargin != vs2.leftMargin) { it.leftMargin = (vs1.leftMargin + (vs2.leftMargin - vs1.leftMargin) * p).toInt() lpChanged = true } if (vs1.topMargin != vs2.topMargin) { it.topMargin = (vs1.topMargin + (vs2.topMargin - vs1.topMargin) * p).toInt() lpChanged = true } } if (lpChanged) layoutParams = o } } } fun Any?.statesChangeByAnimation(views: Array, tag1: Int, tag2: Int, start: Float = 0F, end: Float = 1F, updateCallback: AnimationUpdateListener? = null, updateStateListener: AnimatorListenerAdapter? = null, duration1: Long = 400L, startDelay1: Long = 0L): ValueAnimator { return ValueAnimator.ofFloat(start, end).apply { startDelay = startDelay1 duration = duration1 interpolator = AccelerateDecelerateInterpolator() addUpdateListener { animation -> val p = animation.animatedValue as Float updateCallback?.onAnimationUpdate(tag1, tag2, p) for (it in views) it.stateRefresh(tag1, tag2, animation.animatedValue as Float) } updateStateListener?.let { addListener(it) } start() } } interface AnimationUpdateListener { fun onAnimationUpdate(tag1: Int, tag2: Int, p: Float) } ================================================ FILE: app/src/main/res/drawable/ic_launcher_background.xml ================================================ ================================================ FILE: app/src/main/res/drawable/merchant_search_shape.xml ================================================ ================================================ FILE: app/src/main/res/drawable/settle_submit_shape.xml ================================================ ================================================ FILE: app/src/main/res/drawable/shadow_b_000_shape.xml ================================================ ================================================ FILE: app/src/main/res/drawable/shadow_b_fff_shape.xml ================================================ ================================================ FILE: app/src/main/res/drawable/tab_title_selector.xml ================================================ ================================================ FILE: app/src/main/res/drawable/transparent.xml ================================================ ================================================ FILE: app/src/main/res/layout/merchant_activity.xml ================================================ ================================================ FILE: app/src/main/res/layout/merchant_content_layout.xml ================================================ ================================================ FILE: app/src/main/res/layout/merchant_food_list_content.xml ================================================ ================================================ FILE: app/src/main/res/layout/merchant_food_list_content_image.xml ================================================ ================================================ FILE: app/src/main/res/layout/merchant_food_list_recommend.xml ================================================ ================================================ FILE: app/src/main/res/layout/merchant_food_list_recommend_cell.xml ================================================ ================================================ FILE: app/src/main/res/layout/merchant_food_list_side.xml ================================================ ================================================ FILE: app/src/main/res/layout/merchant_food_list_sticky_title.xml ================================================ ================================================ FILE: app/src/main/res/layout/merchant_food_list_top.xml ================================================ ================================================ FILE: app/src/main/res/layout/merchant_page_cell_layout.xml ================================================ ================================================ FILE: app/src/main/res/layout/merchant_page_food_layout.xml ================================================ ================================================ FILE: app/src/main/res/layout/merchant_page_layout.xml ================================================ ================================================ FILE: app/src/main/res/layout/merchant_settle_layout.xml ================================================ ================================================ FILE: app/src/main/res/layout/merchant_title_layout.xml ================================================ ================================================ FILE: app/src/main/res/layout/ticket_view.xml ================================================ ================================================ FILE: app/src/main/res/values/frames.xml ================================================ #000 #000 #000 10dp 14.5dp 48dp 38dp #E5E5E5 202dp 80dp ================================================ FILE: app/src/main/res/values/strings.xml ================================================ behavior-learn com.example.learn.business.MerchantPageBehavior ================================================ FILE: build.gradle ================================================ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { repositories { google() jcenter() } dependencies { classpath 'com.android.tools.build:gradle:3.0.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${KOTLIN_VERSION}" } } allprojects { repositories { google() jcenter() mavenCentral() maven { url "https://jitpack.io" } maven { url 'https://maven.google.com' } } } task clean(type: Delete) { delete rootProject.buildDir } ext { support_multidex = 'com.android.support:multidex:1.0.2' support_support_v4 = "com.android.support:support-v4:${ANDROID_SUPPORT_VERSION}" support_appcompat_v7 = "com.android.support:appcompat-v7:${ANDROID_SUPPORT_VERSION}" support_recyclerview_v7 = "com.android.support:recyclerview-v7:${ANDROID_SUPPORT_VERSION}" support_design = "com.android.support:design:${ANDROID_SUPPORT_VERSION}" kotlin = "org.jetbrains.kotlin:kotlin-stdlib:${KOTLIN_VERSION}" kotlin_stdlib = "org.jetbrains.kotlin:kotlin-stdlib:${KOTLIN_VERSION}" kotlin_anko_common = "org.jetbrains.anko:anko-common:${ANKO_VERSION}" glide = 'com.github.bumptech.glide:glide:4.6.1' glide_compiler = 'com.github.bumptech.glide:compiler:4.6.1' glide_transformations = 'jp.wasabeef:glide-transformations:3.0.1' list_pull_to_refresh = 'in.srain.cube:ultra-ptr:1.0.11' list_adapter = 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.40' eventbus = 'org.greenrobot:eventbus:3.1.1' } ================================================ FILE: gradle/wrapper/gradle-wrapper.properties ================================================ #Wed May 30 14:37:50 CST 2018 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-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. 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 ANDROID_SUPPORT_VERSION=27.0.2 COMPILE_SDK_VERSION=27 TARGET_SDK_VERSION=27 MIN_SDK_VERSION=16 KOTLIN_VERSION=1.1.51 ANKO_VERSION=0.9 APPLICATION_ID=com.example.learn VERSION_CODE=1 VERSION_NAME=1.0.0 DEBUG=false ================================================ FILE: gradlew ================================================ #!/usr/bin/env bash ############################################################################## ## ## Gradle start up script for UN*X ## ############################################################################## # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS="" APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # 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 case "`uname`" in CYGWIN* ) cygwin=true ;; Darwin* ) darwin=true ;; MINGW* ) msys=true ;; esac # 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 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" ] ; 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 # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules function splitJvmOpts() { JVM_OPTS=("$@") } eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" ================================================ 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 @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= set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @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 Windowz variants if not "%OS%" == "Windows_NT" goto win9xME_args if "%@eval[2+2]" == "4" goto 4NT_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=%* goto execute :4NT_args @rem Get arguments from the 4NT Shell from JP Software 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'