Litho的使用--下拉刷新上拉加载

Litho的使用–下拉刷新上拉加载

上篇介绍了Litho的基本使用,本文看下日常操作RecyclerView的嵌套使用以及下拉刷新上拉加载。

准备工作

先模拟一下网络请求以及使用的bean类

@Event
class RvListModel {
    //kotlin默认成员变量注解后,反射修饰符为private
    //@JvmField注解过后的对象,编译后修饰符变为默认修饰符public
    @JvmField
    var list: MutableList<RvItemBean>? = null
}

这里的@Event注解后面会用到,注入列表数据的。其中RvItemBean:

class RvItemBean {
    var title: String? = null
    var content: String? = null
    var clickStr: String? = null
    var isSubRv: Boolean = false//item是否是子RecyclerView
    var iconRes: Int = 0
    var imageUrl: String? = null
}

bean类很简单,下面看下模拟的网络请求工具类:

class DataService {

    private val random = Random()
    private var dataModelEventHandler: EventHandler<RvListModel>? = null
    fun registerLoadingEvent(dataModelEventHandler: EventHandler<RvListModel>) {
        this.dataModelEventHandler = dataModelEventHandler
    }

    fun unregisterLoadingEvent() {
        this.dataModelEventHandler = null
    }

    /**
     * 加载数据
     */
    fun fetch(start: Int, count: Int) {
        //延迟2s模拟刷新操作
        Handler().postDelayed({
            val rvListModel = getData(start, count)
            //分发消息,更新界面,会走到onDataLoaded
            dataModelEventHandler!!.dispatchEvent(rvListModel)
        }, 2000)
    }

    /**
     * 刷新操作
     */
    fun reFetch(start: Int, count: Int) {

        Handler().postDelayed({
            val rvListModel = getData(start, count)
            //分发消息,更新界面,会走到onDataLoaded
            dataModelEventHandler!!.dispatchEvent(rvListModel)
        }, 2000)
    }

    fun getData(start: Int, count: Int): RvListModel {
        val rvListModel = RvListModel()
        var  list = mutableListOf<RvListModel>()
        rvListModel.list = ArrayList()
        for (i in start until start + count) {
            val rvItemBean = getRvItemBean(i)
            rvListModel.list!!.add(rvItemBean)
        }
        return rvListModel
    }

    private fun getRvItemBean(i: Int): RvItemBean {
        val rvItemBean = RvItemBean()
        rvItemBean.title = "我是标题${(random.nextInt(10) + i)}"
        rvItemBean.content = "我是内容$i"
        rvItemBean.iconRes = R.mipmap.ic_launcher
        rvItemBean.imageUrl = mImgUrls[0]
        rvItemBean.clickStr = "click$i"
        rvItemBean.isSubRv = true
        if (random.nextInt(10) % 3 == 0) {
            rvItemBean.imageUrl = mImgUrls[random.nextInt(mImgUrls.size)]
            rvItemBean.isSubRv = false
        }
        return rvItemBean
    }

    /**
     * 网络图片资源,便于随机取图片
     */
    private val mImgUrls = arrayOf(
        "http://pic37.nipic.com/20140113/8800276_184927469000_2.png",
        "http://k.zol-img.com.cn/sjbbs/7692/a7691515_s.jpg",
        "http://pic9.nipic.com/20100923/2531170_140325352643_2.jpg",
        "http://pic25.nipic.com/20121205/10197997_003647426000_2.jpg",
        "http://img1.imgtn.bdimg.com/it/u=1483033257,4287748004&fm=26&gp=0.jpg",
        "http://img2.imgtn.bdimg.com/it/u=667158997,739004683&fm=26&gp=0.jpg",
        "http://img4.imgtn.bdimg.com/it/u=852943962,2632646500&fm=26&gp=0.jpg",
        "http://img3.imgtn.bdimg.com/it/u=1451961535,3314663922&fm=26&gp=0.jpg",
        "http://img1.imgtn.bdimg.com/it/u=1533275126,1287779573&fm=26&gp=0.jpg",
        "http://img3.imgtn.bdimg.com/it/u=2132929001,1562758156&fm=26&gp=0.jpg",
        "http://img3.imgtn.bdimg.com/it/u=1239957118,2147373077&fm=26&gp=0.jpg",
        "http://img5.imgtn.bdimg.com/it/u=3558462636,3096335901&fm=26&gp=0.jpg",
        "http://img1.imgtn.bdimg.com/it/u=3893146502,314297687&fm=26&gp=0.jpg",
        "http://img2.imgtn.bdimg.com/it/u=1409224092,1124266154&fm=26&gp=0.jpg"
    )
}

已经做了简单的注释,一看就懂。

RecyclerView的使用

做了准备工作,直接开干,先造一个item的Spec

@LayoutSpec
object SubRvItemSpec {
    @OnCreateLayout
    fun onCreateLayout(c: ComponentContext, @Prop imgRes: Int?): Component {
        return card(c){
            paddingDip(YogaEdge.ALL, 16f)
            backgroundColor(Color.WHITE)
            content(image(c){
                drawableRes(imgRes ?: R.mipmap.ic_launcher)
            })
            clickHandler(ImageViewComponent.onClick(c, "click native"))
        }.build()
    }

    @OnEvent(ClickEvent::class)
    fun onClick(c: ComponentContext, @FromEvent view: View, @Param someProp: String) {
        Toast.makeText(c.applicationContext, "click:$someProp", Toast.LENGTH_SHORT).show()
    }
}

Litho中的RecyclerView需要一个Section组件生成列表,如下:

@GroupSectionSpec
object SubRvSectionSpec {
    @OnCreateChildren
    fun onCreateChildren(c: SectionContext, @Prop list: List<Int>): Children {
        val builder = Children.create()
        for (i in list.indices) {
            builder.child(
                SingleComponentSection.create(c)
                    .key(i.toString())
                    .component(
                        SubRvItem.create(c)
                            .imgRes(list[i])
                            .build()
                    )
            )
        }
        return builder.build()
    }
}

编译后,会生成SubRvItem和SubRvSection,Litho使用RecyclerView是通过RecyclerCollectionComponent构建的:

recyclerCollectionComponent(c){
            heightDip(100f)
            recyclerConfiguration(ListRecyclerConfiguration.create().orientation(LinearLayoutManager.HORIZONTAL).reverseLayout(false).build())
            section(SubRvSection.create(SectionContext(c))
                .list(RvItemSpec.getImageArray(bean))
                .build())
            disablePTR(true)
        }.build()

其中disablePTR(true),设置为true,不要下拉刷新。这里就不再试了,最后运行一下嵌套的RecyclerView看下效果。

嵌套的RecyclerView

首先看item的spec:

@LayoutSpec
object RvItemSpec {
    @OnCreateLayout
    fun onCreateLayout(c: ComponentContext, @Prop bean: RvItemBean): Component {
        return column(c){
            paddingDip(YogaEdge.ALL, 16f)
            backgroundColor(Color.WHITE)
            child(text(c){
                marginDip(YogaEdge.TOP, 8f)
                marginDip(YogaEdge.LEFT, 16f)
                marginDip(YogaEdge.RIGHT, 16f)
                marginDip(YogaEdge.BOTTOM, 8f)
                text(bean.title)
                textSizeSp(40f)
            })
            child(text(c){
                marginDip(YogaEdge.LEFT, 16f)
                marginDip(YogaEdge.RIGHT, 16f)
                marginDip(YogaEdge.BOTTOM, 8f)
                text(bean.content)
                textSizeSp(20f)
            })
            child(if (bean.isSubRv) getSubRv(c, bean) else getImgUrlComponent(c, bean))
            clickHandler(ImageViewComponent.onClick(c, bean.clickStr))
        }
    }

    /**
     * 图片组件
     */
    private fun getImgUrlComponent(c: ComponentContext, bean: RvItemBean): GlideImage {
        return GlideImage.create(c)
            .heightDip(200f)
            .imageUrl(bean.imageUrl?:"")
            .build()
    }

    /**
     * 嵌套的子RecyclerView组件
     */
    private fun getSubRv(c: ComponentContext, bean: RvItemBean): Component {
        return recyclerCollectionComponent(c){
            heightDip(100f)
            recyclerConfiguration(ListRecyclerConfiguration.create().orientation(LinearLayoutManager.HORIZONTAL).reverseLayout(false).build())
            section(SubRvSection.create(SectionContext(c))
                .list(RvItemSpec.getImageArray(bean))
                .build())
            disablePTR(true)
        }.build()
    }

    /**
     * 获取子RecyclerView的图片列表,图片都用默认图片,这里只做个数的随机
     */
    private fun getImageArray(bean: RvItemBean): List<Int> {
        val images = ArrayList<Int>()
        for (i in 0 until Random().nextInt(5) + 1) {
            images.add(bean.iconRes)
        }
        return images
    }

    @OnEvent(ClickEvent::class)
    fun onClick(c: ComponentContext, @FromEvent view: View, @Param someProp: String) {
        Toast.makeText(c.applicationContext, "click:$someProp", Toast.LENGTH_SHORT).show()
    }
}

主要是根据bean类的isSubRv字段判断是普通图片组件还是子RecyclerView组件。下面看下SectionSpec,这个是重头戏:

@GroupSectionSpec
object RvSectionSpec {
    private const val TAG = "RvSectionSpec"
    /**
     * 初始化数据
     * StateValue的变量名要与@State注解的变量名保持一致
     */
    @OnCreateInitialState
    fun createInitialState(c: SectionContext, list: StateValue<MutableList<RvItemBean>>, start: StateValue<Int>, count: StateValue<Int>, isFetching: StateValue<Boolean>) {
        start.set(0)
        count.set(15)
        list.set(DataService().getData(0, 15).list)
        isFetching.set(false)
    }

    /**
     * 布局
     */
    @OnCreateChildren
    fun onCreateChildren(c: SectionContext, @State list: MutableList<RvItemBean>): Children {
        val builder = Children.create()
        for (i in list.indices) {
            builder.child(
                SingleComponentSection.create(c)
                    .key(i.toString())
                    .component(
                        RvItem.create(c)
                            .bean(list[i])
                            .build()
                    )
            )
        }
        //上拉加载的进度条
        builder.child(
            SingleComponentSection.create(c)
                .component(ProgressLayout.create(c))
                .build()
        )
        return builder.build()
    }

    /**
     * 创建请求服务
     */
    @OnCreateService
    fun onCreateService(c: SectionContext, @State list: MutableList<RvItemBean>, @State start: Int, @State count: Int): DataService {
        return DataService()
    }

    /**
     * 绑定请求服务
     */
    @OnBindService
    fun onBindService(c: SectionContext, service: DataService) {
        service.registerLoadingEvent(RvSection.onDataLoaded(c))
    }

    /**
     * 解绑请求服务
     */
    @OnUnbindService
    fun onUnbindService(c: SectionContext, service: DataService) {
        service.unregisterLoadingEvent()
    }

    /**
     * 接受到获取数据的消息
     */
    @OnEvent(RvListModel::class)
    fun onDataLoaded(c: SectionContext, @FromEvent list: MutableList<RvItemBean>) {
        //更新数据,看源码可以看到走到了updateData设置数据,
        // 然后调用了SectionTree的updateState,利用CalculateChangeSetRunnable,走到了calculateNewChangeSet,
        // 调用createNewTreeAndApplyStateUpdates,里面会调用nextRoot.createChildren更新视图。这里也体现了Litho的异步measure、layout
        RvSection.updateData(c, list)
        //设置已经获取数据
        RvSection.setFetching(c, false)
        //发送刷新已经完成消息,即隐藏刷新进度条
        SectionLifecycle.dispatchLoadingEvent(c, false, LoadingEvent.LoadingState.SUCCEEDED, null)
    }

    /**
     * 更新数据
     */
    @OnUpdateState
    fun updateData(list: StateValue<MutableList<RvItemBean>>, start: StateValue<Int>, @Param newList: MutableList<RvItemBean>) {
        if (start.get() == 0) {
            list.set(newList)
        } else {
            val listContain =mutableListOf<RvItemBean>()
            listContain.addAll(list.get()!!)
            listContain.addAll(newList)
            list.set(listContain)
        }
    }

    /**
     * 下拉刷新
     */
    @OnRefresh
    fun onRefresh(c: SectionContext, service: DataService, @State list: MutableList<RvItemBean>, @State start: Int, @State count: Int) {
        RvSection.updateStartParam(c, 0)
        service.reFetch(0, 15)
    }

    /**
     * 设置是否刷新,并保存到State中的isFetching属性
     */
    @OnUpdateState
    fun setFetching(isFetching: StateValue<Boolean>, @Param fetch: Boolean) {
        isFetching.set(fetch)
    }

    /**
     * 更新
     */
    @OnUpdateState
    fun updateStartParam(start: StateValue<Int>, @Param newStart: Int) {
        start.set(newStart)
    }

    /**
     * 滑动监听,可以用来判断上拉加载
     */
    @OnViewportChanged
    fun onViewportChanged(c: SectionContext, firstVisiblePosition: Int, lastVisiblePosition: Int, firstFullyVisibleIndex: Int, lastFullyVisibleIndex: Int, totalCount: Int, service: DataService, @State list: MutableList<RvItemBean>, @State start: Int, @State count: Int, @State isFetching: Boolean) {
        Log.e(TAG, "firstVisiblePosition=$firstVisiblePosition;lastVisiblePosition=$lastVisiblePosition;firstFullyVisibleIndex=$firstFullyVisibleIndex;lastFullyVisibleIndex=$lastFullyVisibleIndex;totalCount=$totalCount;list.size()=${list.size};start=$start count=$count isFetching=$isFetching")
        //滑动到最后一个位置的时候
        if (totalCount == list.size && !isFetching) {
            //上拉加载更多的判断
            RvSection.setFetching(c, true)
            RvSection.updateStartParam(c, list.size)
            service.fetch(list.size, count)
        }
    }
}

关键代码加了注释,下面再稍微说一下:

  1. @OnCreateInitialState注解的方法是初始数据的方法,可以看到从DataService获取数据。值得注意的一点:StateValue的变量名要与@State注解的变量名保持一致。
  2. @OnCreateChildren注解不用说了,创建布局,值得注意的是布局最下方追加了上拉加载的进度条。
  3. @OnCreateService,@OnBindService,@OnUnbindService注入管理服务的方法。
  4. @OnRefresh这是下拉刷新的关键代码,可以看出,就是在这里进行请求获取数据的。同时看下DataService的刷新方法reFetch里在请求成功调用了EventHandler.dispatchEvent(),分发消息,更新界面,会走到SectionSpec的onDataLoaded方法。
  5. 可以看到onDataLoaded的注解@OnEvent(RvListModel::class),而RvListModel类同时也有@Event注解,相对应。onDataLoaded方法中的注释已经相当详细了,更新完数据后,会再走到onCreateChildren更新视图。
  6. 上拉加载:主要是由@OnViewportChanged注解的方法实现,这个方法可以进行滑动监听,判断上拉至最后一个item,就加载数据,之后跟刷新比较类似,就是折腾一下数据。
    使用起来就相当简单了:
 /**
     * 下拉刷新上拉加载的RecyclerView复合组件
     * @param context
     * @return
     */
    private fun getRvComponent(context: ComponentContext): Component {
        return RecyclerCollectionComponent.create(context)
            .section(RvSection.create(SectionContext(context)).build())
            .build()
    }

下面看下效果:
demo效果
附上demo地址:LithoDemo

介绍:facebook开源了litho一个高效的声明式UI框架。运行效果:使用说明:dependencies {   // ...   // Litho   compile 'com.facebook.litho:litho-core:0.2.0'   compile 'com.facebook.litho:litho-widget:0.2.0'   provided 'com.facebook.litho:litho-annotations:0.2.0'     annotationProcessor 'com.facebook.litho:litho-processor:0.2.0'      // SoLoader    compile 'com.facebook.soloader:soloader:0.2.0'     // Optional   // For debugging   debugCompile 'com.facebook.litho:litho-stetho:0.2.0'     // For integration with Fresco   debugCompile 'com.facebook.litho:litho-fresco:0.2.0'     // For testing   testCompile 'com.facebook.litho:litho-testing:0.2.0' }测试是否安装成功可以在activity中使用Litho创建一个view来测试是否安装成功。首先,初始化SoLoader。Litho依赖于SoLoader,它帮助加载由布局引擎Yoga提供的本地库。Application类适合做这件事情:[MyApplication.java] public class MyApplication extends Application {     @Override   public void onCreate() {     super.onCreate();          SoLoader.init(this, false);   } }然后把一个Litho文本控件放到一个activity中,显示“Hello World!”:[MyActivity.java] import com.facebook.litho.ComponentContext; import com.facebook.litho.LithoView;   public class MyActivity extends Activity {     @Override   public void onCreate(Bundle savedInstanceState) {     super.onCreate(savedInstanceState);          final ComponentContext c = new ComponentContext(this);       final LithoView lithoView = LithoView.create(       this /* context */,        Text.create(c)             .text("Hello, World!")             .textSizeDip(50)             .build());            setContentView(lithoView);   } }现在运行app应该就能看到屏幕上显示“Hello World!” 了。注:不久前翻译了一篇文章Components for Android: 一个高效的声明式UI框架 ,现在判断就是说的这个库了,那个时候还没开源。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值