>
20 |
21 | /**
22 | * 注册单个 AdapterDelegate
23 | */
24 | fun registerAdapterDelegate(adapterDelegate: AdapterDelegate<*, *>) {
25 | this.adapterDelegates.add(adapterDelegate)
26 | }
27 |
28 | /**
29 | * 注册单个 AdapterDelegate,并指定 index
30 | */
31 | fun registerAdapterDelegate(index: Int, adapterDelegate: AdapterDelegate<*, *>) {
32 | this.adapterDelegates.add(index, adapterDelegate)
33 | }
34 |
35 | /**
36 | * 注册多个 AdapterDelegate
37 | */
38 | fun registerAdapterDelegates(vararg adapterDelegates: AdapterDelegate<*, *>) {
39 | this.adapterDelegates.addAll(adapterDelegates)
40 | }
41 |
42 | /**
43 | * 注册多个 AdapterDelegate
44 | */
45 | fun registerAdapterDelegates(index: Int,vararg adapterDelegates: AdapterDelegate<*, *>) {
46 | this.adapterDelegates.addAll(index, adapterDelegates.toList())
47 | }
48 |
49 | /**
50 | * 注销单个 AdapterDelegate
51 | */
52 | fun unregisterAdapterDelegate(adapterDelegate: AdapterDelegate<*, *>) {
53 | this.adapterDelegates.remove(adapterDelegate)
54 | }
55 |
56 | /**
57 | * 注销所有 AdapterDelegate
58 | */
59 | fun clearAdapterDelegates() {
60 | adapterDelegates.clear()
61 | }
62 | }
--------------------------------------------------------------------------------
/flap-annotations/src/main/java/me/yifeiyuan/flap/annotations/Delegate.java:
--------------------------------------------------------------------------------
1 | package me.yifeiyuan.flap.annotations;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | /**
9 | *
10 | * 被 @Delegate 注解的组件,会生成一个 AdapterDelegate 类,生成的类的命名规则:组件类名+AdapterDelegate。
11 | *
12 | *
13 | * Created by 程序亦非猿 on 2021/9/22.
14 | *
15 | * Flap Github: https://github.com/AlanCheen/Flap
16 | *
17 | * @author 程序亦非猿 [Follow me]( https://github.com/AlanCheen)
18 | * @since 2020/9/22
19 | * @since 3.0.0
20 | */
21 | @Retention(RetentionPolicy.SOURCE)
22 | @Target(ElementType.TYPE)
23 | public @interface Delegate {
24 |
25 | /**
26 | * 返回组件的布局 ID,也会被当做组件的 itemViewType
27 | *
28 | * 注意:layoutId 只支持在 App 模块中使用,如果要在子模块中使用,请用 layoutName
29 | *
30 | * @see #layoutName()
31 | *
32 | * @return the layout res id
33 | * @since 1.1.0
34 | */
35 | int layoutId() default -1;
36 |
37 | /**
38 | * 该组件的布局文件的名字,例如一个布局文件叫 a_b_c.xml ,那么该值为 a_b_c,不需要后缀 .xml
39 | *
40 | * @return 组件布局的名字
41 | * @since 2.1.0
42 | */
43 | String layoutName() default "";
44 |
45 | /**
46 | * 是否使用 DataBinding,假如使用 DataBinding,那么组件的构造方法需要做一定的修改;
47 | * DataBinding 的优先级大于 ViewBinding
48 | *
49 | * @see #useViewBinding()
50 | *
51 | * @return 如果要使用 DataBinding 则设置 true
52 | * @since 1.5.1
53 | */
54 | boolean useDataBinding() default false;
55 |
56 | /**
57 | * 是否使用 ViewBinding,假如使用 ViewBinding,那么组件的构造方法需要做一定的修改
58 | * DataBinding 的优先级大于 ViewBinding
59 | *
60 | * @see #useDataBinding()
61 | *
62 | * @return 如果要使用 ViewBinding 则设置 true
63 | * @since 2.2.0
64 | */
65 | boolean useViewBinding() default false;
66 |
67 | }
68 |
--------------------------------------------------------------------------------
/flap/src/main/java/me/yifeiyuan/flap/skeleton/SkeletonAdapter.kt:
--------------------------------------------------------------------------------
1 | package me.yifeiyuan.flap.skeleton
2 |
3 | import android.view.LayoutInflater
4 | import android.view.View
5 | import android.view.ViewGroup
6 | import androidx.annotation.LayoutRes
7 | import androidx.recyclerview.widget.RecyclerView
8 |
9 | /**
10 | * Created by 程序亦非猿 on 2022/8/11.
11 | *
12 | * @since 3.0.1
13 | */
14 | class SkeletonAdapter : RecyclerView.Adapter() {
15 |
16 | var shimmer: Boolean = false
17 | var skeletonItemCount: Int = 0
18 |
19 | @LayoutRes
20 | var skeletonLayoutRes: Int = -1
21 |
22 | var multiSkeletonLayoutRes: ((position: Int) -> Int)? = null
23 |
24 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SkeletonViewHolder {
25 | val inflater = LayoutInflater.from(parent.context)
26 | val view: View
27 | if (shimmer) {
28 | val shimmerView = ShimmerFrameLayout(parent.context)
29 | shimmerView.layoutParams = RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT)
30 | shimmerView.shimmer?.autoStart = true
31 | inflater.inflate(viewType, shimmerView, true)
32 | view = shimmerView
33 | } else {
34 | view = inflater.inflate(viewType, parent, false)
35 | }
36 | return SkeletonViewHolder(view)
37 | }
38 |
39 | override fun onBindViewHolder(holder: SkeletonViewHolder, position: Int) {}
40 |
41 | override fun getItemCount(): Int {
42 | return skeletonItemCount
43 | }
44 |
45 | override fun getItemViewType(position: Int): Int {
46 | if (multiSkeletonLayoutRes != null) {
47 | return multiSkeletonLayoutRes!!.invoke(position)
48 | }
49 | return skeletonLayoutRes
50 | }
51 | }
52 |
53 | class SkeletonViewHolder(view: View) : RecyclerView.ViewHolder(view)
--------------------------------------------------------------------------------
/flap/src/main/java/me/yifeiyuan/flap/widget/FlapGridLayoutManager.kt:
--------------------------------------------------------------------------------
1 | package me.yifeiyuan.flap.widget
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import androidx.recyclerview.widget.GridLayoutManager
6 | import androidx.recyclerview.widget.RecyclerView
7 | import me.yifeiyuan.flap.FlapDebug
8 |
9 | /**
10 | * Created by 程序亦非猿 on 2021/9/30.
11 | * @since 3.0.0
12 | */
13 | open class FlapGridLayoutManager
14 | : GridLayoutManager {
15 |
16 | constructor(context: Context?, attrs: AttributeSet? = null, defStyleAttr: Int = 0, defStyleRes: Int = 0) : super(context, attrs, defStyleAttr, defStyleRes)
17 | constructor(context: Context?, spanCount: Int) : super(context, spanCount)
18 | constructor(context: Context?, spanCount: Int, orientation: Int = RecyclerView.VERTICAL, reverseLayout: Boolean = false) : super(context, spanCount, orientation, reverseLayout)
19 |
20 | companion object {
21 | private const val TAG = "FlapGridLayoutManager"
22 | }
23 |
24 | var supportsPredictiveItemAnimations = false
25 |
26 | init {
27 | isSmoothScrollbarEnabled = false
28 | }
29 |
30 | /**
31 | * Disable predictive animations. There is a bug in RecyclerView which causes views that
32 | * are being reloaded to pull invalid ViewHolders from the internal recycler stack if the
33 | * adapter size has decreased since the ViewHolder was recycled.
34 | *
35 | * https://stackoverflow.com/questions/30220771/recyclerview-inconsistency-detected-invalid-item-position
36 | */
37 | override fun supportsPredictiveItemAnimations(): Boolean {
38 | return supportsPredictiveItemAnimations
39 | }
40 |
41 | override fun onLayoutChildren(recycler: RecyclerView.Recycler?, state: RecyclerView.State?) {
42 | try {
43 | super.onLayoutChildren(recycler, state)
44 | } catch (e: Exception) {
45 | e.printStackTrace()
46 | FlapDebug.e(TAG, "onLayoutChildren: ", e)
47 | }
48 | }
49 | }
--------------------------------------------------------------------------------
/app/src/main/res/menu/activity_main_drawer.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/java/me/yifeiyuan/flapdev/components/SimpleTextComponent.kt:
--------------------------------------------------------------------------------
1 | package me.yifeiyuan.flapdev.components
2 |
3 | import android.view.LayoutInflater
4 | import android.view.View
5 | import android.view.ViewGroup
6 | import android.widget.TextView
7 | import me.yifeiyuan.flap.Component
8 | import me.yifeiyuan.flap.ViewTypeGenerator
9 | import me.yifeiyuan.flap.delegate.AdapterDelegate
10 | import me.yifeiyuan.flap.dsl.adapterDelegate
11 | import me.yifeiyuan.flap.ext.bindTextView
12 | import me.yifeiyuan.flapdev.R
13 |
14 | /**
15 | * Created by 程序亦非猿 on 2018/12/4.
16 | *
17 | * 作为文档 Demo
18 | */
19 |
20 | data class SimpleTextModel(val content: String) {
21 | override fun toString(): String {
22 | return "content=$content,SimpleTextModel"
23 | }
24 | }
25 |
26 | fun createSimpleTextDelegate() = adapterDelegate(R.layout.flap_component_simple_text) {
27 | onBind { model ->
28 | bindTextView(R.id.tv_content) {
29 | text = model.content
30 | }
31 | }
32 | }
33 |
34 | class SimpleTextComponent(itemView: View) : Component(itemView) {
35 |
36 | private val tvContent: TextView = findViewById(R.id.tv_content)
37 |
38 | override fun onBind(model: SimpleTextModel, position: Int, payloads: List) {
39 | tvContent.text = model.content
40 | }
41 | }
42 |
43 | //自定义 AdapterDelegate 实现
44 | class SimpleTextComponentDelegate : AdapterDelegate {
45 |
46 | override fun isDelegateFor(model: Any): Boolean {
47 | return SimpleTextModel::class.java == model::class.java
48 | }
49 |
50 | override fun onCreateViewHolder(inflater: LayoutInflater, parent: ViewGroup, viewType: Int): SimpleTextComponent {
51 | return SimpleTextComponent(inflater.inflate(R.layout.flap_component_simple_text, parent, false))
52 | }
53 |
54 | override fun getItemViewType(model: Any): Int {
55 | return ViewTypeGenerator.generateViewType()
56 | // return R.layout.flap_item_simple_text
57 | }
58 | }
--------------------------------------------------------------------------------
/flap/src/main/java/me/yifeiyuan/flap/widget/FlapLinearLayoutManager.kt:
--------------------------------------------------------------------------------
1 | package me.yifeiyuan.flap.widget
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import androidx.recyclerview.widget.LinearLayoutManager
6 | import androidx.recyclerview.widget.RecyclerView
7 | import me.yifeiyuan.flap.FlapDebug
8 |
9 | /**
10 | * Created by 程序亦非猿 on 2021/9/30.
11 | * @since 3.0.0
12 | */
13 | open class FlapLinearLayoutManager
14 | : LinearLayoutManager {
15 |
16 | constructor(context: Context) : super(context)
17 |
18 | constructor(context: Context, orientation: Int = RecyclerView.VERTICAL, reverseLayout: Boolean = false) : super(context, orientation, reverseLayout)
19 |
20 | constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0, defStyleRes: Int = 0) : super(context, attrs, defStyleAttr, defStyleRes)
21 |
22 | companion object {
23 | private const val TAG = "FlapLinearLayoutManager"
24 | }
25 |
26 | var supportsPredictiveItemAnimations = false
27 |
28 | init {
29 | //当第一个 Item 高度为 0,会影响 RV 计算是否能滑动,所以设置为 false
30 | isSmoothScrollbarEnabled = false
31 | }
32 |
33 | /**
34 | * Disable predictive animations. There is a bug in RecyclerView which causes views that
35 | * are being reloaded to pull invalid ViewHolders from the internal recycler stack if the
36 | * adapter size has decreased since the ViewHolder was recycled.
37 | *
38 | * https://stackoverflow.com/questions/30220771/recyclerview-inconsistency-detected-invalid-item-position
39 | */
40 | override fun supportsPredictiveItemAnimations(): Boolean {
41 | return supportsPredictiveItemAnimations
42 | }
43 |
44 | override fun onLayoutChildren(recycler: RecyclerView.Recycler?, state: RecyclerView.State?) {
45 | try {
46 | super.onLayoutChildren(recycler, state)
47 | } catch (e: Exception) {
48 | e.printStackTrace()
49 | FlapDebug.e(TAG, "onLayoutChildren: ", e)
50 | }
51 | }
52 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/flap/src/main/res/values/shimmer_attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/flap/src/main/java/me/yifeiyuan/flap/delegate/FallbackAdapterDelegate.kt:
--------------------------------------------------------------------------------
1 | package me.yifeiyuan.flap.delegate
2 |
3 | import android.view.LayoutInflater
4 | import android.view.View
5 | import android.view.ViewGroup
6 | import android.widget.TextView
7 | import me.yifeiyuan.flap.Component
8 | import me.yifeiyuan.flap.FlapDebug
9 |
10 | /**
11 | * FallbackAdapterDelegate is a build-in AdapterDelegate that would be used when something went wrong.
12 | * So that Flap won't crash your App.
13 | *
14 | * FallbackAdapterDelegate 是一个默认代理所有数据模型的 AdapterDelegate,可以用来处理未知模型兜底逻辑。
15 | *
16 | * 如果一个 model 没有任何 AdapterDelegate 代理,那么 FallbackAdapterDelegate 会负责接手处理。
17 | *
18 | * 开发者也可以自己定义自己的 FallbackAdapterDelegate。
19 | *
20 | * @see me.yifeiyuan.flap.FlapInitializer.globalFallbackAdapterDelegate
21 | * @see me.yifeiyuan.flap.Flap.fallbackDelegate
22 | * @see DefaultFallbackComponent
23 | *
24 | * Created by 程序亦非猿 on 2021/9/22.
25 | *
26 | * Flap Github: https://github.com/AlanCheen/Flap
27 | * @author 程序亦非猿 [Follow me]( https://github.com/AlanCheen)
28 | * @since 2020/9/22
29 | * @since 3.0.0
30 | */
31 | abstract class FallbackAdapterDelegate : AdapterDelegate> {
32 | override fun isDelegateFor(model: Any): Boolean {
33 | return true
34 | }
35 | }
36 |
37 | abstract class FallbackComponent(v: View) : Component(v) {
38 |
39 | override fun isClickable(): Boolean {
40 | return false
41 | }
42 |
43 | override fun isLongClickable(): Boolean {
44 | return false
45 | }
46 |
47 | override fun isDragEnabled(): Boolean {
48 | return false
49 | }
50 |
51 | override fun isSwipeEnabled(): Boolean {
52 | return false
53 | }
54 | }
55 |
56 | internal class DefaultFallbackAdapterDelegate : FallbackAdapterDelegate() {
57 | override fun onCreateViewHolder(inflater: LayoutInflater, parent: ViewGroup, viewType: Int): DefaultFallbackComponent {
58 | return DefaultFallbackComponent(TextView(parent.context))
59 | }
60 | }
61 |
62 | internal class DefaultFallbackComponent(v: View) : FallbackComponent(v) {
63 | override fun onBind(model: Any) {}
64 | }
65 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/skeleton_layout3.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
23 |
24 |
35 |
36 |
40 |
41 |
46 |
47 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/skeleton_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
24 |
25 |
35 |
36 |
40 |
41 |
46 |
47 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/flap/src/main/java/me/yifeiyuan/flap/widget/FlapStaggeredGridLayoutManager.kt:
--------------------------------------------------------------------------------
1 | package me.yifeiyuan.flap.widget
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import androidx.recyclerview.widget.RecyclerView
6 | import androidx.recyclerview.widget.StaggeredGridLayoutManager
7 | import me.yifeiyuan.flap.FlapDebug
8 |
9 | /**
10 | * Created by 程序亦非猿 on 2021/9/30.
11 | * @since 3.0.0
12 | */
13 | open class FlapStaggeredGridLayoutManager
14 | : StaggeredGridLayoutManager {
15 |
16 | companion object {
17 | private const val TAG = "FlapStaggeredGridLayoutManager"
18 | }
19 |
20 | constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0, defStyleRes: Int = 0) : super(context, attrs, defStyleAttr, defStyleRes)
21 |
22 | constructor(spanCount: Int, orientation: Int = RecyclerView.VERTICAL) : super(spanCount, orientation)
23 |
24 | var supportsPredictiveItemAnimations = false
25 |
26 | init {
27 | gapStrategy = GAP_HANDLING_NONE
28 | }
29 |
30 | /**
31 | * Disable predictive animations. There is a bug in RecyclerView which causes views that
32 | * are being reloaded to pull invalid ViewHolders from the internal recycler stack if the
33 | * adapter size has decreased since the ViewHolder was recycled.
34 | *
35 | * https://stackoverflow.com/questions/30220771/recyclerview-inconsistency-detected-invalid-item-position
36 | */
37 | override fun supportsPredictiveItemAnimations(): Boolean {
38 | return supportsPredictiveItemAnimations
39 | }
40 |
41 | override fun onLayoutChildren(recycler: RecyclerView.Recycler?, state: RecyclerView.State?) {
42 | try {
43 | super.onLayoutChildren(recycler, state)
44 | } catch (e: Exception) {
45 | e.printStackTrace()
46 | FlapDebug.e(TAG, "onLayoutChildren: ", e)
47 | }
48 | }
49 |
50 | override fun onScrollStateChanged(state: Int) {
51 | try {
52 | super.onScrollStateChanged(state)
53 | } catch (e: Exception) {
54 | e.printStackTrace()
55 | FlapDebug.e(TAG, "onScrollStateChanged: ", e)
56 | }
57 | }
58 | }
--------------------------------------------------------------------------------
/app/src/main/java/me/yifeiyuan/flapdev/components/TestConfig.kt:
--------------------------------------------------------------------------------
1 | package me.yifeiyuan.flapdev.components
2 |
3 | import me.yifeiyuan.flap.differ.IDiffer
4 | import me.yifeiyuan.flap.dsl.adapterDelegate
5 | import me.yifeiyuan.flap.ext.bindTextView
6 | import me.yifeiyuan.flapdev.FlapApplication.Companion.application
7 | import me.yifeiyuan.flapdev.R
8 | import me.yifeiyuan.flapdev.toPixel
9 | import kotlin.random.Random
10 |
11 | /**
12 | *
13 | * 由 Model 属性决定 Component 的功能开关
14 | *
15 | * Created by 程序亦非猿 on 2022/9/13.
16 | */
17 |
18 | class TestConfigModel : IDiffer {
19 |
20 | var id: Int = -1
21 | var title: String? = null
22 | var content: String? = null
23 |
24 | var dragEnable: Boolean = false
25 | var swipeEnable: Boolean = false
26 |
27 | var swipeFlags: Int = 0
28 | var dragFlags: Int = 0
29 |
30 | var clickEnable: Boolean = false
31 | var longClickEnable: Boolean = false
32 |
33 | var height: Int = -1
34 |
35 | init {
36 | height = Random.nextInt(application!!.toPixel(60), application!!.toPixel(120))
37 | }
38 |
39 | override fun areItemsTheSame(newItem: Any): Boolean {
40 | return false
41 | }
42 |
43 | override fun areContentsTheSame(newItem: Any): Boolean {
44 | return false
45 | }
46 |
47 | override fun getChangePayload(newItem: Any): Any? {
48 | return super.getChangePayload(newItem)
49 | }
50 | }
51 |
52 | fun createFullConfigAdapterDelegate() = adapterDelegate(R.layout.component_full_feature) {
53 |
54 | onBind { model, position, payloads ->
55 |
56 | bindTextView(R.id.title) {
57 | text = model.title
58 | }
59 |
60 | bindTextView(R.id.content) {
61 | text = model.content
62 | }
63 |
64 | swipeEnable = model.swipeEnable
65 | dragEnable = model.dragEnable
66 | swipeFlags = model.swipeFlags
67 | dragFlags = model.dragFlags
68 | clickable = model.clickEnable
69 | longClickable = model.longClickEnable
70 |
71 | if (model.height > 0) {
72 | val lp = itemView.layoutParams.apply {
73 | height = model.height
74 | }
75 | itemView.layoutParams = lp
76 | }
77 | }
78 | }
--------------------------------------------------------------------------------
/flap/src/main/java/me/yifeiyuan/flap/service/IAdapterServiceManager.kt:
--------------------------------------------------------------------------------
1 | package me.yifeiyuan.flap.service
2 |
3 | import androidx.annotation.RestrictTo
4 |
5 | /**
6 | * IAdapterServiceManager is for managing all the AdapterServices.
7 | *
8 | * You can register an AdapterService in Application or Activity or Fragment.
9 | *
10 | * And get the AdapterService when binding Component by calling {getAdapterService} or {Component#callService}
11 | *
12 | * @see me.yifeiyuan.flap.FlapRegistry
13 | * @see AdapterServiceManager
14 | *
15 | * AdapterService 管理,可以注册或获取 AdapterService
16 | *
17 | * 可以在 Activity 中注册 AdapterService,在 Component 中使用。
18 | *
19 | * Created by 程序亦非猿 on 2022/8/18.
20 | * @since 3.0.3
21 | */
22 | @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
23 | interface IAdapterServiceManager {
24 |
25 | val adapterServices: MutableMap, AdapterService>
26 |
27 | /**
28 | * 注册 AdapterService,并反射调用无参构造器进行实例化
29 | */
30 | fun registerAdapterService(serviceClass: Class) {
31 | try {
32 | val service = serviceClass.getConstructor().newInstance()
33 | adapterServices[serviceClass] = service
34 | } catch (e: Exception) {
35 | e.printStackTrace()
36 | }
37 | }
38 |
39 | /**
40 | * 注册多个 AdapterService,并反射调用无参构造器进行实例化
41 | */
42 | fun registerAdapterServices(vararg serviceClasses: Class) {
43 | try {
44 | serviceClasses.forEach {
45 | val service = it.getConstructor().newInstance()
46 | adapterServices[it] = service
47 | }
48 | } catch (e: Exception) {
49 | e.printStackTrace()
50 | }
51 | }
52 |
53 | /**
54 | *
55 | * 注册 AdapterService 实例
56 | */
57 | fun registerAdapterService(serviceClass: Class, service: T) {
58 | adapterServices[serviceClass] = service
59 | }
60 |
61 | /**
62 | * Return an AdapterService instance of the Given service class.
63 | * If no AdapterService is found it returns null.
64 | *
65 | * 获取 AdapterService
66 | */
67 | @Suppress("UNCHECKED_CAST")
68 | fun getAdapterService(serviceClass: Class): T? {
69 | return adapterServices[serviceClass] as? T
70 | }
71 |
72 | }
--------------------------------------------------------------------------------
/flap-animation/src/main/java/me/yifeiyuan/flap/animation/BaseAdapterAnimation.kt:
--------------------------------------------------------------------------------
1 | package me.yifeiyuan.flap.animation
2 |
3 | import android.animation.Animator
4 | import android.view.View
5 | import android.view.animation.Interpolator
6 | import android.view.animation.LinearInterpolator
7 | import androidx.recyclerview.widget.RecyclerView
8 | import me.yifeiyuan.flap.Component
9 | import me.yifeiyuan.flap.FlapAdapter
10 | import me.yifeiyuan.flap.hook.AdapterHook
11 |
12 | /**
13 | * Adapter Animation ,在完成 bind 后执行动画
14 | *
15 | * Created by 程序亦非猿 on 2022/8/29.
16 | *
17 | * @since 3.0.7
18 | */
19 | abstract class BaseAdapterAnimation : AdapterHook {
20 |
21 | var duration: Long = 300L
22 | var isFirstOnly: Boolean = true
23 | var interpolator: Interpolator = LinearInterpolator()
24 | private var lastPosition = -1
25 |
26 | override fun onPostBindViewHolder(adapter: RecyclerView.Adapter<*>, component: Component<*>, data: Any, position: Int, payloads: MutableList) {
27 | super.onPostBindViewHolder(adapter, component, data, position, payloads)
28 | val adapterPosition = component.adapterPosition
29 | if (!isFirstOnly || adapterPosition > lastPosition) {
30 | val animator = createAnimator(component.itemView, component, data, position)
31 | animator?.let {
32 | it.duration = duration
33 | it.interpolator = interpolator
34 | it.start()
35 | }
36 | lastPosition = adapterPosition
37 | } else {
38 | clear(component.itemView)
39 | }
40 | }
41 |
42 | abstract fun createAnimator(view: View, component: Component<*>, data: Any, position: Int): Animator?
43 |
44 | private fun clear(v: View) {
45 | v.apply {
46 | alpha = 1f
47 | scaleY = 1f
48 | scaleX = 1f
49 | translationY = 0f
50 | translationX = 0f
51 | rotation = 0f
52 | rotationY = 0f
53 | rotationX = 0f
54 | pivotY = v.measuredHeight / 2f
55 | pivotX = v.measuredWidth / 2f
56 | animate().setInterpolator(null).startDelay = 0
57 | }
58 | }
59 |
60 | fun reset() {
61 | lastPosition = -1
62 | }
63 |
64 | fun attachToAdapter(flapAdapter: FlapAdapter) {
65 | flapAdapter.registerAdapterHook(this)
66 | }
67 | }
--------------------------------------------------------------------------------
/app/src/main/java/me/yifeiyuan/flapdev/components/TestAdapterApiComponent.kt:
--------------------------------------------------------------------------------
1 | package me.yifeiyuan.flapdev.components
2 |
3 | import android.util.Log
4 | import android.widget.TextView
5 | import me.yifeiyuan.flap.dsl.adapterDelegate
6 | import me.yifeiyuan.flap.event.Event
7 | import me.yifeiyuan.flap.ext.bindButton
8 | import me.yifeiyuan.flapdev.R
9 | import me.yifeiyuan.flapdev.LogService
10 |
11 | /**
12 | * Created by 程序亦非猿 on 2022/7/29.
13 | */
14 |
15 | private const val TAG = "TestAllComponent"
16 |
17 | class TestAdapterApiModel
18 |
19 | fun createTestAdapterApiComponentDelegate() = adapterDelegate(R.layout.component_test_all_feature){
20 |
21 | //展示信息
22 | val messageTextView = findViewById(R.id.message)
23 |
24 | onBind { model, position, payloads ->
25 |
26 | bindButton(R.id.testFireEvent) {
27 | // fireEvent 发送事件
28 | setOnClickListener{
29 | val showToastEvent = Event("showToast", "Fire event:showToast,position=$position") {
30 | Log.d(TAG, "onBind: showToastEvent success")
31 |
32 | messageTextView.text = "showToast event 收到回调:success"
33 | }
34 | fireEvent(showToastEvent)
35 |
36 | val intE = Event("intEvent", 233333)
37 | fireEvent(intE)
38 | }
39 | }
40 |
41 | bindButton(R.id.testGetParam){
42 |
43 | setOnClickListener {
44 | val stringValue = getParam("stringValue")
45 | val intValue = getParam("intValue")
46 | val booleanValue = getParam("booleanValue")
47 |
48 | val results :String = StringBuilder().
49 | append("stringValue=$stringValue ;;")
50 | .append("intValue=$intValue ;;")
51 | .append("booleanValue=$booleanValue ;;")
52 | .toString()
53 |
54 | messageTextView.text = "Adapter.getParam results=$results"
55 | }
56 | }
57 |
58 | bindButton(R.id.testGetAdapterService) {
59 | setOnClickListener {
60 |
61 | callService {
62 | log("LogService Message")
63 | messageTextView.text = testResult()
64 | }
65 | }
66 | }
67 | }
68 | }
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/flap/src/main/java/me/yifeiyuan/flap/pool/ComponentPool.kt:
--------------------------------------------------------------------------------
1 | package me.yifeiyuan.flap.pool
2 |
3 | import android.content.ComponentCallbacks2
4 | import android.content.res.Configuration
5 | import androidx.recyclerview.widget.RecyclerView
6 | import androidx.recyclerview.widget.RecyclerView.RecycledViewPool
7 | import me.yifeiyuan.flap.FlapDebug
8 |
9 | /**
10 | * 自定义的 RecycledViewPool,实现了 ComponentCallbacks2 ,可以在内存不足的时候清理缓存
11 | *
12 | * @see me.yifeiyuan.flap.Flap.setComponentPoolEnable 设置开关
13 | *
14 | * Created by 程序亦非猿 on 2021/9/22.
15 | *
16 | * Flap Github: https://github.com/AlanCheen/Flap
17 | * @author 程序亦非猿 [Follow me]( https://github.com/AlanCheen)
18 | * @since 2020/9/22
19 | * @since 3.0.2
20 | */
21 | open class ComponentPool : RecycledViewPool(), ComponentCallbacks2 {
22 |
23 | companion object {
24 | private const val TAG = "ComponentPool"
25 | }
26 |
27 | override fun setMaxRecycledViews(viewType: Int, max: Int) {
28 | super.setMaxRecycledViews(viewType, max)
29 | FlapDebug.d(TAG, "setMaxRecycledViews() called with: viewType = $viewType, max = $max")
30 | }
31 |
32 | override fun getRecycledViewCount(viewType: Int): Int {
33 | val result = super.getRecycledViewCount(viewType)
34 | FlapDebug.d(TAG, "getRecycledViewCount() called with: viewType = $viewType,result=$result")
35 | return result
36 | }
37 |
38 | override fun getRecycledView(viewType: Int): RecyclerView.ViewHolder? {
39 | val result = super.getRecycledView(viewType)
40 | FlapDebug.d(TAG, "getRecycledView() called with: viewType = $viewType,result=$result")
41 | return result
42 | }
43 |
44 | override fun putRecycledView(scrap: RecyclerView.ViewHolder) {
45 | super.putRecycledView(scrap)
46 | FlapDebug.d(TAG, "putRecycledView() called with: scrap = $scrap")
47 | }
48 |
49 | //参考 Glide 的 MemoryCache 实现
50 | override fun onTrimMemory(level: Int) {
51 | when (level) {
52 | in ComponentCallbacks2.TRIM_MEMORY_BACKGROUND..ComponentCallbacks2.TRIM_MEMORY_COMPLETE -> {
53 | clear()
54 | }
55 | else -> {
56 | }
57 | }
58 | FlapDebug.d(TAG, "onTrimMemory() called with: level = $level")
59 | }
60 |
61 | override fun onConfigurationChanged(newConfig: Configuration) {}
62 |
63 | override fun onLowMemory() {
64 | FlapDebug.d(TAG, "onLowMemory: ")
65 | clear()
66 | }
67 |
68 | override fun clear() {
69 | super.clear()
70 | FlapDebug.d("ComponentPool", "ComponentPool 执行清理缓存")
71 | }
72 | }
--------------------------------------------------------------------------------
/app/src/main/java/me/yifeiyuan/flapdev/components/DiffComponent.kt:
--------------------------------------------------------------------------------
1 | package me.yifeiyuan.flapdev.components
2 |
3 | import android.os.SystemClock
4 | import me.yifeiyuan.flap.differ.IDiffer
5 | import me.yifeiyuan.flap.dsl.adapterDelegate
6 | import me.yifeiyuan.flap.ext.bindButton
7 | import me.yifeiyuan.flap.ext.bindTextView
8 | import me.yifeiyuan.flapdev.R
9 |
10 | /**
11 | * Created by 程序亦非猿 on 2022/8/1.
12 | *
13 | * 如果都返回 true ,内容修改后再下拉刷新,不会有 onbind 行为
14 | */
15 |
16 | class TestDiffModel(var content: String, var id: Int, var desc: String) : IDiffer {
17 |
18 | override fun areItemsTheSame(newItem: Any): Boolean {
19 | if (newItem.javaClass == TestDiffModel::class.java) {
20 | return id == (newItem as TestDiffModel).id
21 | } else {
22 | return false
23 | }
24 | // return true
25 | }
26 |
27 | override fun areContentsTheSame(newItem: Any): Boolean {
28 | return content == (newItem as TestDiffModel).content
29 | // return true
30 | }
31 |
32 | override fun getChangePayload(newItem: Any): Any? {
33 | return (newItem as TestDiffModel).content
34 | }
35 |
36 | override fun toString(): String {
37 | return "(id=$id,content='$content',desc='$desc')TestDiffModel"
38 | }
39 | }
40 |
41 | fun createDiffDelegate() = adapterDelegate(R.layout.component_diff) {
42 | onBind { model, position, payloads ->
43 |
44 | //当 payloads 更新时,事件点击需要重新设置
45 | bindButton(R.id.modifyContent) {
46 | setOnClickListener {
47 | model.content = "修改后 Content:" + (SystemClock.uptimeMillis() % 10000).toInt().toString()
48 |
49 | adapter.notifyItemChanged(position, model.content)//不会闪
50 | // adapter.notifyItemChanged(position)//会闪
51 | }
52 | }
53 |
54 | bindButton(R.id.modifyId) {
55 | setOnClickListener {
56 | model.id = (SystemClock.uptimeMillis() % 10000).toInt()
57 | adapter.notifyItemChanged(position)
58 | }
59 | }
60 |
61 | if (payloads.isNotEmpty()) {
62 | bindTextView(R.id.content) {
63 | text = "展示 content :${payloads.get(0)}"
64 | }
65 | } else {
66 | bindTextView(R.id.content) {
67 | text = "展示 content :${model.content}"
68 | }
69 |
70 | bindTextView(R.id.id) {
71 | text = "展示 ID :${model.id}"
72 | }
73 |
74 | bindTextView(R.id.desc) {
75 | text = "展示 desc :${model.desc}"
76 | }
77 | }
78 | }
79 | }
--------------------------------------------------------------------------------
/flap-dsl-viewbinding/src/main/java/me/yifeiyuan/flap/dsl/viewbinding/AdapterDelegateViewBindingDsl.kt:
--------------------------------------------------------------------------------
1 | package me.yifeiyuan.flap.dsl.viewbinding
2 |
3 | import android.view.LayoutInflater
4 | import android.view.ViewGroup
5 | import androidx.recyclerview.widget.RecyclerView
6 | import androidx.viewbinding.ViewBinding
7 | import me.yifeiyuan.flap.delegate.AdapterDelegate
8 | import me.yifeiyuan.flap.dsl.DslComponent
9 |
10 | /**
11 | * 支持 AdapterDelegate ViewBinding DSL 功能
12 | * Created by 程序亦非猿 on 2022/9/20.
13 | * @since 3.1.3
14 | */
15 | inline fun adapterDelegateViewBinding(
16 | noinline viewBinding: (layoutInflater: LayoutInflater, parent: ViewGroup) -> V,
17 | itemViewType: Int = 0,
18 | noinline isDelegateFor: ((model: Any) -> Boolean) = { m -> m.javaClass == T::class.java },
19 | itemId: Long = RecyclerView.NO_ID,
20 | noinline componentInitializer: ViewBindingDslComponent.() -> Unit): ViewBindingDslAdapterDelegate {
21 | return ViewBindingDslAdapterDelegate(T::class.java, viewBinding, itemViewType, itemId, isDelegateFor = isDelegateFor, block = componentInitializer)
22 | }
23 |
24 | /**
25 | * 支持 AdapterDelegate ViewBinding DSL 功能
26 | * Created by 程序亦非猿 on 2022/9/20.
27 | * @since 3.1.3
28 | */
29 | class ViewBindingDslAdapterDelegate(
30 | private var modelClass: Class,
31 | private var viewBinding: (layoutInflater: LayoutInflater, parent: ViewGroup) -> V,
32 | private var itemViewType: Int,
33 | private var itemId: Long = RecyclerView.NO_ID,
34 | private var isDelegateFor: ((model: Any) -> Boolean) = { m -> m.javaClass == modelClass },
35 | private var block: ViewBindingDslComponent.() -> Unit,
36 | ) : AdapterDelegate> {
37 |
38 | override fun isDelegateFor(model: Any): Boolean {
39 | return isDelegateFor.invoke(model)
40 | }
41 |
42 | override fun onCreateViewHolder(inflater: LayoutInflater, parent: ViewGroup, viewType: Int): ViewBindingDslComponent {
43 | val binding = viewBinding(inflater, parent)
44 | val component = ViewBindingDslComponent(binding)
45 | block.invoke(component)
46 | return component
47 | }
48 |
49 | override fun getItemId(model: Any, position: Int): Long {
50 | return itemId
51 | }
52 |
53 | override fun getItemViewType(model: Any): Int {
54 | return itemViewType
55 | }
56 | }
57 |
58 | /**
59 | * 支持 AdapterDelegate ViewBinding DSL 功能
60 | * Created by 程序亦非猿 on 2022/9/20.
61 | * @since 3.1.3
62 | */
63 | class ViewBindingDslComponent(val binding: V) : DslComponent(binding.root)
--------------------------------------------------------------------------------
/README_EN.md:
--------------------------------------------------------------------------------
1 | # Flap
2 |
3 | [](https://travis-ci.org/AlanCheen/Flap)    [](./LICENSE) [](https://github.com/AlanCheen) [](https://github.com/AlanCheen/Flap/pulls)
4 |
5 | ------
6 |
7 | ## Usage
8 |
9 | 1. Step1 : create a Model
10 | 2. Step2 : create a layout file for component(A ViewHolder)
11 | 3. Step3 : create a AdapterDelegate and register it.
12 |
13 |
14 | 1)Create a Model, lets say `SimpleTextModel` :
15 | ```kotlin
16 | data class SimpleTextModel(val content: String)
17 | ```
18 | 2)A simple layout that containers a TextView :
19 | ```xml
20 |
21 |
26 |
27 |
37 |
38 |
39 | ```
40 |
41 | 3)Create a `AdapterDelegate` by `adapterDelegate` DSL ,and
override `onBind` method:
42 |
43 | ```kotlin
44 | val simpleTextDelegate = adapterDelegate(R.layout.flap_item_simple_text) {
45 | onBind { model ->
46 | bindTextView(R.id.tv_content) {
47 | text = model.content
48 | }
49 | }
50 | }
51 | ```
52 |
53 | Use a `FlapAdapter` instead of `Adapter` and register AdapterDelegate to a FlapAdapter: :
54 |
55 | ```kotlin
56 | //create FlapAdapter
57 | var adapter: FlapAdapter = FlapAdapter()
58 |
59 | //register AdapterDelegate
60 | adapter.registerAdapterDelegate(simpleTextDelegate)
61 |
62 | val dataList = ArrayList()
63 | dataList.add(SimpleTextModel("Android"))
64 | dataList.add(SimpleTextModel("Java"))
65 | dataList.add(SimpleTextModel("Kotlin"))
66 |
67 | //setData
68 | adapter.setDataAndNotify(dataList);
69 |
70 | recyclerView.adapter = adapter
71 | ```
72 |
73 | Here we go!!
74 |
75 | 
76 |
77 | ## License
78 | Apache 2.0
--------------------------------------------------------------------------------
/app/src/main/java/me/yifeiyuan/flapdev/testcases/FlapDifferAdapterTestcase.kt:
--------------------------------------------------------------------------------
1 | package me.yifeiyuan.flapdev.testcases
2 |
3 | import android.os.Handler
4 | import me.yifeiyuan.flap.FlapAdapter
5 | import me.yifeiyuan.flap.differ.IDiffer
6 | import me.yifeiyuan.flap.differ.FlapDifferAdapter
7 | import me.yifeiyuan.flapdev.components.TestDiffModel
8 | import java.util.ArrayList
9 |
10 |
11 | /**
12 | * 测试 FlapDifferAdapter 功能
13 | *
14 | * 测试说明,点击按钮后,再下拉刷新,只有被修改了的数据才会有刷新动画
15 | *
16 | * @see FlapDifferAdapter
17 | *
18 | * Created by 程序亦非猿 on 2022/8/1.
19 | */
20 | class FlapDifferAdapterTestcase : BaseTestcaseFragment() {
21 |
22 | //如果不测试 header footer,就注释掉 onViewCreated
23 | // override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
24 | // super.onViewCreated(view, savedInstanceState)
25 | //
26 | // val headerFooterAdapter = HeaderFooterAdapter(adapter)
27 | //
28 | // val headerView = LayoutInflater.from(activity).inflate(R.layout.header_layout, null, false)
29 | // headerFooterAdapter.setupHeaderView(headerView)
30 | //
31 | // val footerView = LayoutInflater.from(activity).inflate(R.layout.footer_layout, null, false)
32 | // headerFooterAdapter.setupFooterView(footerView)
33 | //
34 | // adapter.doOnItemClick { recyclerView, childView, position ->
35 | // val component = recyclerView.getChildViewHolder(childView)
36 | // if (headerFooterAdapter.isHeader(component)) {
37 | // toast("点击了 position = $position,是 Header!")
38 | // return@doOnItemClick
39 | // }
40 | // if (headerFooterAdapter.isFooter(component)) {
41 | // toast("点击了 position = $position,是 Footer!")
42 | // return@doOnItemClick
43 | // }
44 | // val realPosition = if (position == 0) position else position - headerFooterAdapter.getHeaderCount()
45 | // toast("点击了 position = $position,model=${adapter.getItemData(realPosition)}")
46 | // }
47 | //
48 | // recyclerView.adapter = headerFooterAdapter
49 | // }
50 |
51 | override fun createAdapter(): FlapAdapter {
52 | return FlapDifferAdapter().apply {
53 | setData(createRefreshData())
54 | }
55 | }
56 |
57 | override fun createRefreshData(size: Int): MutableList {
58 | val list = mutableListOf()
59 | repeat(20) {
60 | list.add(TestDiffModel("content,$it of 20", it, "this is desc :${it % 3}"))
61 | }
62 | return list
63 | }
64 |
65 | override fun loadMoreData(size: Int) {
66 | Handler().postDelayed({
67 | val list = ArrayList()
68 | repeat(size) {
69 | list.add(TestDiffModel("content,$it of 20,more data", it, "this is desc :${it % 3}"))
70 | }
71 | adapter.appendDataAndNotify(list)
72 | }, 500)
73 | }
74 |
75 | }
--------------------------------------------------------------------------------
/app/src/main/java/me/yifeiyuan/flapdev/testcases/PreloadTestcase.kt:
--------------------------------------------------------------------------------
1 | package me.yifeiyuan.flapdev.testcases
2 |
3 | import android.os.Bundle
4 | import android.os.Handler
5 | import android.util.Log
6 | import android.view.Menu
7 | import android.view.MenuInflater
8 | import android.view.MenuItem
9 | import android.view.View
10 | import me.yifeiyuan.flap.hook.PreloadHook
11 | import me.yifeiyuan.flapdev.R
12 | import me.yifeiyuan.flapdev.components.SimpleTextModel
13 | import java.util.*
14 |
15 | /**
16 | * Created by 程序亦非猿 on 2021/10/19.
17 | */
18 | class PreloadTestcase : BaseTestcaseFragment() {
19 |
20 | companion object {
21 | private const val TAG = "PreloadTestcase"
22 | }
23 |
24 | private var testPreloadErrorCase = false
25 |
26 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
27 | super.onViewCreated(view, savedInstanceState)
28 | setHasOptionsMenu(true)
29 | setupPreload()
30 |
31 | recyclerView.layoutManager = linearLayoutManager
32 | recyclerView.addItemDecoration(linearItemDecoration)
33 | }
34 |
35 | private fun setupPreload() {
36 | //滑动到底部
37 | adapter.doOnPreload(offset = 0, minItemCount = 2, direction = PreloadHook.SCROLL_DOWN) {
38 | requestMoreData()
39 | }
40 |
41 | //滑动到顶部
42 | adapter.doOnPreload(offset = 2, minItemCount = 2, direction = PreloadHook.SCROLL_UP) {
43 |
44 | toast("顶部,开始预加载")
45 |
46 | Handler().postDelayed({
47 | val list = ArrayList()
48 | val size = 5
49 | repeat(size) {
50 | list.add(SimpleTextModel("头部加载更多数据 $it of $size"))
51 | }
52 | adapter.addDataAndNotify(list)
53 | }, 300)
54 | }
55 | }
56 |
57 | private fun requestMoreData() {
58 | Log.d(TAG, "requestMoreData")
59 | if (testPreloadErrorCase) {
60 | Log.d(TAG, "预加载失败场景,必须调用 setPreloadComplete ")
61 | adapter.setPreloadComplete() // 当出错时,需要手动调用,不然不会再进行检查
62 | } else {
63 | Log.d(TAG, "onViewCreated: 开始预加载")
64 | toast("底部,开始预加载")
65 | loadMoreData()
66 | }
67 | }
68 |
69 | override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
70 | inflater.inflate(R.menu.preload, menu)
71 | }
72 |
73 | override fun onOptionsItemSelected(item: MenuItem): Boolean {
74 | when (item.itemId) {
75 | R.id.setLoadMoreError -> {
76 | item.isChecked = item.isChecked.not()
77 | testPreloadErrorCase = item.isChecked
78 | }
79 | R.id.setLoadMoreEnable -> {
80 | item.isChecked = item.isChecked.not()
81 | adapter.setPreloadEnable(item.isChecked)
82 | }
83 | }
84 | return super.onOptionsItemSelected(item)
85 | }
86 | }
--------------------------------------------------------------------------------
/flap/src/main/java/me/yifeiyuan/flap/ext/ItemClicksHelper.kt:
--------------------------------------------------------------------------------
1 | package me.yifeiyuan.flap.ext
2 |
3 | import android.view.View
4 | import androidx.recyclerview.widget.RecyclerView
5 | import me.yifeiyuan.flap.ComponentConfig
6 | import me.yifeiyuan.flap.hook.AdapterHook
7 |
8 |
9 | typealias OnItemClickListener = ((recyclerView: RecyclerView, childView: View, position: Int) -> Unit)
10 | typealias OnItemLongClickListener = ((recyclerView: RecyclerView, childView: View, position: Int) -> Boolean)
11 |
12 | /**
13 | * 给 RecyclerView 添加 Component 级别的单击、长按事件的帮助类
14 | *
15 | * @see OnItemClickListener
16 | * @see me.yifeiyuan.flap.FlapAdapter.doOnItemClick
17 | * @see OnItemLongClickListener
18 | * @see me.yifeiyuan.flap.FlapAdapter.doOnItemLongClick
19 | *
20 | * 实现参考:
21 | * https://www.littlerobots.nl/blog/Handle-Android-RecyclerView-Clicks/
22 | * https://stackoverflow.com/questions/24885223/why-doesnt-recyclerview-have-onitemclicklistener
23 | *
24 | * Created by 程序亦非猿 on 2022/7/28.
25 | *
26 | * @since 3.0.0
27 | */
28 | internal class ItemClicksHelper : RecyclerView.OnChildAttachStateChangeListener, AdapterHook {
29 |
30 | lateinit var recyclerView: RecyclerView
31 |
32 | var onItemClickListener: OnItemClickListener? = null
33 |
34 | var onItemLongClickListener: OnItemLongClickListener? = null
35 |
36 | private val internalOnClickListener = View.OnClickListener { v ->
37 | val holder: RecyclerView.ViewHolder = recyclerView.getChildViewHolder(v)
38 | if (holder is ComponentConfig && holder.isClickable()) {
39 | onItemClickListener?.invoke(recyclerView, v, holder.position)
40 | }
41 | }
42 |
43 | private val internalOnLongClickListener = View.OnLongClickListener { v ->
44 | val holder: RecyclerView.ViewHolder = recyclerView.getChildViewHolder(v)
45 | if (holder is ComponentConfig && holder.isLongClickable()) {
46 | onItemLongClickListener?.invoke(recyclerView, v, holder.position) ?: false
47 | } else {
48 | false
49 | }
50 | }
51 |
52 | override fun onChildViewAttachedToWindow(view: View) {
53 | onItemClickListener?.let {
54 | view.setOnClickListener(internalOnClickListener)
55 | }
56 |
57 | onItemLongClickListener?.let {
58 | view.setOnLongClickListener(internalOnLongClickListener)
59 | }
60 | }
61 |
62 | override fun onChildViewDetachedFromWindow(view: View) {
63 | //do nothing
64 | }
65 |
66 | override fun onAdapterAttachedToRecyclerView(adapter: RecyclerView.Adapter<*>, recyclerView: RecyclerView) {
67 | this.recyclerView = recyclerView
68 | this.recyclerView.addOnChildAttachStateChangeListener(this)
69 | }
70 |
71 | override fun onAdapterDetachedFromRecyclerView(adapter: RecyclerView.Adapter<*>, recyclerView: RecyclerView) {
72 | recyclerView.removeOnChildAttachStateChangeListener(this)
73 | }
74 | }
--------------------------------------------------------------------------------
/flap-dsl-databinding/src/main/java/me/yifeiyuan/flap/dsl/databinding/AdapterDelegateDataBindingDsl.kt:
--------------------------------------------------------------------------------
1 | package me.yifeiyuan.flap.dsl.databinding
2 |
3 | import android.view.LayoutInflater
4 | import android.view.ViewGroup
5 | import androidx.annotation.LayoutRes
6 | import androidx.databinding.DataBindingUtil
7 | import androidx.databinding.ViewDataBinding
8 | import androidx.recyclerview.widget.RecyclerView
9 | import me.yifeiyuan.flap.delegate.AdapterDelegate
10 | import me.yifeiyuan.flap.dsl.DslComponent
11 |
12 | /**
13 | * 支持 AdapterDelegate DataBinding DSL 功能
14 | * Created by 程序亦非猿 on 2022/9/20.
15 | * @since 3.1.4
16 | */
17 | inline fun adapterDelegateDataBinding(
18 | @LayoutRes layoutId: Int,
19 | noinline dataBinding: ((layoutInflater: LayoutInflater, parent: ViewGroup) -> D) = { l, p -> DataBindingUtil.inflate(l, layoutId, p, false) },
20 | noinline isDelegateFor: ((model: Any) -> Boolean) = { m -> m.javaClass == T::class.java },
21 | itemId: Long = RecyclerView.NO_ID,
22 | noinline componentInitializer: DataBindingDslComponent.() -> Unit): DataBindingDslAdapterDelegate {
23 | return DataBindingDslAdapterDelegate(T::class.java, dataBinding, layoutId, itemId, isDelegateFor, componentInitializer)
24 | }
25 |
26 | /**
27 | * 支持 AdapterDelegate DataBinding DSL 功能
28 | * Created by 程序亦非猿 on 2022/9/20.
29 | * @since 3.1.4
30 | */
31 | class DataBindingDslAdapterDelegate(
32 | private var modelClass: Class,
33 | private var dataBinding: ((layoutInflater: LayoutInflater, parent: ViewGroup) -> D) = { l, p -> DataBindingUtil.inflate(l, layoutId, p, false) },
34 | @LayoutRes private var layoutId: Int,
35 | private var itemId: Long = RecyclerView.NO_ID,
36 | private var isDelegateFor: ((model: Any) -> Boolean) = { m -> m.javaClass == modelClass },
37 | private var block: DataBindingDslComponent.() -> Unit,
38 | ) : AdapterDelegate> {
39 |
40 | override fun isDelegateFor(model: Any): Boolean {
41 | return isDelegateFor.invoke(model)
42 | }
43 |
44 | override fun onCreateViewHolder(inflater: LayoutInflater, parent: ViewGroup, viewType: Int): DataBindingDslComponent {
45 | val viewDataBinding = dataBinding.invoke(inflater, parent)
46 | val component = DataBindingDslComponent(viewDataBinding)
47 | block.invoke(component)
48 | return component
49 | }
50 |
51 | override fun getItemId(model: Any, position: Int): Long {
52 | return itemId
53 | }
54 |
55 | override fun getItemViewType(model: Any): Int {
56 | return layoutId
57 | }
58 | }
59 |
60 | /**
61 | * 支持 AdapterDelegate DataBinding DSL 功能
62 | * Created by 程序亦非猿 on 2022/9/20.
63 | * @since 3.1.4
64 | */
65 | class DataBindingDslComponent(val binding: V) : DslComponent(binding.root)
--------------------------------------------------------------------------------
/flap/src/main/java/me/yifeiyuan/flap/ext/ComponentBinder.kt:
--------------------------------------------------------------------------------
1 | package me.yifeiyuan.flap.ext
2 |
3 | import android.view.View
4 | import android.widget.*
5 | import androidx.recyclerview.widget.RecyclerView
6 | import me.yifeiyuan.flap.Component
7 |
8 | /**
9 | * 如果不想写 Component.findViewById 那么也可以用 Component 的 bindXXXView 来做绑定。
10 | *
11 | * 只是挑选了一些常用的基础组件提供 binder 方法,目前包含:
12 | *
13 | * @see bindView
14 | * @see bindTextView
15 | * @see bindImageView
16 | * @see bindEditText
17 | * @see bindButton
18 | * @see bindCheckBox
19 | * @see bindRecyclerView
20 | * @see bindImageButton
21 | * @see bindProgressBar
22 | * @see bindRatingBar
23 | * @see bindRadioButton
24 | * @see bindRadioGroup
25 | *
26 | * Created by 程序亦非猿 on 2021/9/29.
27 | * @since 3.0.0
28 | */
29 |
30 | fun Component<*>.bindView(viewId: Int, binder: V.() -> Unit) {
31 | val view = itemView.findViewById(viewId)
32 | view.binder()
33 | }
34 |
35 | fun Component<*>.bindTextView(viewId: Int, binder: TextView.() -> Unit) {
36 | val view = itemView.findViewById(viewId)
37 | view.binder()
38 | }
39 |
40 | fun Component<*>.setText(viewId: Int, text: CharSequence?) {
41 | itemView.findViewById(viewId).text = text
42 | }
43 |
44 | fun Component<*>.bindButton(viewId: Int, binder: Button.() -> Unit) {
45 | val textView = itemView.findViewById