Android使用MvvM+kotlin实现简单WanAndroid

  最近在学习mvvm,利用空闲时间写了一个简单版的wanAndroid项目.介绍就不说了,直接上代码.

  1.引入:在App——build.gradle目录引入以下代码即可:

dataBinding{

    enabled true

}

2.配置gradle.properties

android.useAndroidX=true

android.enableJetifier=true

3.WanAndroid项目主要有首页、知识、导航、项目四个模块,采用MvvM+Kotlin方式

主MainActivity代码如下:

/**

* @作者: njb

* @时间: 2020/1/13 12:51

* @描述: 主界面

*/

open class MainActivity : BaseActivity() {

    private val fragmentList: MutableList<Fragment> =

        ArrayList()

    private val strings =

        arrayOf("首页", "知识", "导航", "项目")

    override val layoutId: Int

        get() = R.layout.activity_main

    override fun initView() {

        fragmentList.add(HomeFragment())

        fragmentList.add(KnowledgeFragment())

        fragmentList.add(NavigationFragment())

        fragmentList.add(ProjectFragment())

        viewPager!!.adapter = object : FragmentStateAdapter(this) {

            override fun createFragment(position: Int): Fragment {

                return fragmentList[position]

            }

            override fun getItemCount(): Int {

                return fragmentList.size

            }

        }

        viewPager.offscreenPageLimit = 3

        val tabLayoutMediator = TabLayoutMediator(

            tab_layout,

            viewPager,

            TabConfigurationStrategy { tab: TabLayout.Tab, position: Int ->

                tab.text = strings[position]

            }

        )

        tabLayoutMediator.attach()

        initToolBar()

    }

    private fun initToolBar() {

        setSupportActionBar(toolbar)

        supportActionBar!!.setDisplayHomeAsUpEnabled(false)

        supportActionBar!!.setHomeButtonEnabled(false)

        iv_search.setImageResource(R.drawable.ic_baseline_search_24)

    }

    override fun addListener() {

        iv_search.setOnClickListener(View.OnClickListener {

            startActivity(

                Intent(

                    this@MainActivity,

                    SearchActivity::class.java

                )

            )

        })

    }

}

4.首页Fragment代码:

/**

* @作者: njb

* @时间: 2020/1/10 18:19

* @描述: 首页

*/

class HomeFragment : Fragment(), CompletedListener {

    private var mBinding: ViewDataBinding? = null

    private var artListAdapter: ArtListAdapter? = null

    private var viewModel: HomeViewModel? = null

    private var layoutManager: LinearLayoutManager? = null

    private var page: Int = 1

    private var viewHead: View? = null

    private var banner: Banner? = null

    private var bannerModel: List<BannerModel>? = null

    private var viewModels:BannerViewModel ? = null

    override fun onCreateView(

        inflater: LayoutInflater,

        container: ViewGroup?,

        savedInstanceState: Bundle?

    ): View? {

        mBinding = DataBindingUtil.inflate(inflater, R.layout.fm_home, container, false)

        val view = mBinding!!.root

        viewHead = LayoutInflater.from(context).inflate(R.layout.item_home_banner, null)

        initBanner()

        initView(view)

        initListener(view)

        return mBinding!!.root

    }

    /**

    * 初始化view

    */

    private fun initView(view: View) {

        artListAdapter = ArtListAdapter(null)

        layoutManager = LinearLayoutManager(activity!!)

        layoutManager!!.orientation = LinearLayoutManager.VERTICAL

        view.rv_home.layoutManager = layoutManager

        artListAdapter!!.addHeaderView(viewHead)

        view.rv_home.addItemDecoration(DividerDecoration(ContextCompat.getColor(activity!!,R.color.ce7e7e7),2))

        view.rv_home.adapter = artListAdapter

        viewModel = HomeViewModel(artListAdapter!!, this)

        //显示加载动画

        viewModel!!.getArtCircleList(page)

    }

    private fun initBanner() {

        banner = viewHead!!.findViewById(R.id.home_banner)

        //设置banner样式

        banner!!.setBannerStyle(BannerConfig.CIRCLE_INDICATOR)

        //设置图片加载器

        banner!!.setImageLoader(GlideImageLoader())

        //设置banner动画效果

        banner!!.setBannerAnimation(Transformer.Default)

        //设置自动轮播,默认为true

        banner!!.isAutoPlay(true)

        //设置轮播时间

        banner!!.setDelayTime(3000)

        //设置指示器位置(当banner模式中有指示器时)

        banner!!.setIndicatorGravity(BannerConfig.CENTER)

        //banner设置方法全部调用完毕时最后调用

        banner!!.start()

        viewModels = BannerViewModel(banner!!,this)

        viewModels!!.getBannerList()

    }

    /**

    * 初始化事件

    */

    private fun initListener(view: View) {

        artListAdapter!!.setOnItemClickListener{

            listener,View,position ->

            Intent(context, WebViewActivity::class.java).run {

                putExtra(AppConstant.WEBVIEW_TITLE_KEY, artListAdapter!!.data[position].title)

                putExtra(AppConstant.WEBVIEW_ID_KEY, artListAdapter!!.data[position].id)

                putExtra(AppConstant.WEBVIEW_URL_KEY, artListAdapter!!.data[position].link)

                startActivity(this, null)

            }

        }

        view.rf_home.setOnRefreshListener {

            page = 1

            viewModel!!.getArtCircleList(page)

            view.rf_home.finishRefresh(200)

        }

        view.rf_home.setOnLoadMoreListener {

            page++

            viewModel!!.getArtCircleList(page)

            view.rf_home.finishLoadMore(200)

        }

    }

    override fun onCompleted() {

    }

}

HomeViewModel

/**

*@作者: njb

*@时间: 2020/1/10 17:55

*@描述:

*/

class HomeViewModel(

    private val adapter: ArtListAdapter,

    private val completedListener: CompletedListener

) {

    private var observer: Observer<BaseModel<ArticleListModel>>? = null

    private var repository = HomeRepository()

    fun getArtCircleList(page:Int) {

        observer = object : Observer<BaseModel<ArticleListModel>> {

            override fun onComplete() {

                completedListener.onCompleted()

            }

            override fun onSubscribe(d: Disposable) {

            }

            override fun onNext(t: BaseModel<ArticleListModel>) {

                if (t.errorCode == 0) {

                    t.data?.let {

                        if(page == 1){

                            adapter.setNewData(it.datas)

                        }else{

                            adapter.addData(it.datas!!)

                        }

                    }

                }

            }

            override fun onError(e: Throwable) {

                completedListener.onCompleted()

            }

        }

        repository.getArcticList(page,

            observer as Observer<BaseModel<ArticleListModel>>

        )

    }

}

首页请求HomeRespository

/**

*@作者: njb

*@时间: 2020/1/10 18:00

*@描述:

*/

class HomeRepository :BaseRepository(){

    fun getArcticList(

        page: Int,

        observer: Observer<BaseModel<ArticleListModel>>) {

        apiServer.articleList(page).subscribeOn(Schedulers.io())

            .observeOn(AndroidSchedulers.mainThread())

            .subscribe(observer)

    }

}

首页文章列表适配器

/**

*@author: njb

*@date:  2020/2/11 0011 16:47

*@desc:  文章列表适配器

*/

class ArtListAdapter (data:List<ArticleListModel.DatasBean>?):

    BaseQuickAdapter<ArticleListModel.DatasBean,BaseViewHolder>(R.layout.item_home,data){

    override fun convert(helper: BaseViewHolder, item: ArticleListModel.DatasBean) {

        val itemBinding = DataBindingUtil.bind<ItemHomeBinding>(helper.itemView)!!

        itemBinding.homebean = item

        itemBinding.executePendingBindings()

    }

}

首页布局:

<?xml version="1.0" encoding="utf-8"?>

<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <LinearLayout

        android:layout_width="match_parent"

        android:layout_height="match_parent"

        android:orientation="vertical">

        <com.ning.mvvmplayandroid.weight.MyRefreshLayout

            android:id="@+id/rf_home"

            android:layout_width="match_parent"

            android:layout_height="match_parent">

            <androidx.recyclerview.widget.RecyclerView

                android:id="@+id/rv_home"

                android:layout_width="match_parent"

                android:layout_height="match_parent" />

        </com.ning.mvvmplayandroid.weight.MyRefreshLayout>

    </LinearLayout>

</layout>

首页Banner布局加在文章列表头部,Banner广告布局:

<?xml version="1.0" encoding="utf-8"?>

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"

    xmlns:app="http://schemas.android.com/apk/res-auto"

    android:layout_width="match_parent"

    android:layout_height="match_parent">

    <com.youth.banner.Banner

        android:id="@+id/home_banner"

        android:layout_width="match_parent"

        android:layout_height="200dp"

        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

首页文章列表item布局:

<?xml version="1.0" encoding="utf-8"?>

<layout xmlns:tools="http://schemas.android.com/tools"

    xmlns:android="http://schemas.android.com/apk/res/android"

    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>

        <variable

            name="homebean"

            type="fule.com.playandroidkotlin.ui.model.ArticleListModel.DatasBean" />

    </data>

    <androidx.constraintlayout.widget.ConstraintLayout

        android:layout_width="match_parent"

        android:layout_height="wrap_content"

        android:padding="10dp"

        tools:ignore="MissingConstraints">

        <TextView

            android:id="@+id/tv_username"

            android:layout_width="wrap_content"

            android:layout_height="wrap_content"

            app:layout_constraintLeft_toLeftOf="parent"

            android:layout_marginRight="20dp"

            android:text="@{homebean.author}"

            android:textColor="@color/colorPrimary"

            android:textSize="12sp" />

        <TextView

            android:id="@+id/tv_time"

            android:layout_width="wrap_content"

            android:layout_height="wrap_content"

            app:layout_constraintEnd_toEndOf="parent"

            android:text="@{String.valueOf(homebean.niceDate)}"

            android:textColor="@color/colorPrimary"

            android:textSize="12sp" />

        <androidx.cardview.widget.CardView

            android:id="@+id/cv_content"

            android:layout_width="100dp"

            android:layout_height="80dp"

            app:layout_constraintTop_toBottomOf="@+id/tv_username"

            android:layout_marginTop="10dp"

            android:elevation="0dp"

            app:cardCornerRadius="4dp"

            app:cardElevation="0dp">

            <ImageView

                android:id="@+id/iv_content"

                android:layout_width="100dp"

                android:layout_height="80dp"

                android:scaleType="centerCrop"

                app:home_image="@{homebean.envelopePic}"/>

        </androidx.cardview.widget.CardView>

        <TextView

            android:id="@+id/tv_content"

            android:layout_width="0dp"

            android:layout_height="wrap_content"

            android:layout_marginStart="12dp"

            android:layout_marginTop="10dp"

            android:ellipsize="end"

            android:gravity="top|start"

            android:lineSpacingExtra="2dp"

            android:maxLines="2"

            android:text="@{homebean.title}"

            android:textColor="@color/black"

            android:textSize="14sp"

            app:layout_constraintLeft_toRightOf="@+id/cv_content"

            app:layout_constraintRight_toRightOf="parent"

            app:layout_constraintTop_toBottomOf="@+id/tv_username" />

        <TextView

            android:id="@+id/tv_type"

            android:layout_width="wrap_content"

            android:layout_height="wrap_content"

            android:layout_marginStart="10dp"

            android:layout_marginTop="10dp"

            android:layout_toRightOf="@+id/cv_content"

            android:gravity="center"

            android:padding="4dp"

            android:text="@{homebean.chapterName}"

            android:background="@drawable/shape_article_type_bg"

            android:textColor="@color/white"

            android:textSize="12sp"

            app:layout_constraintLeft_toRightOf="@+id/cv_content"

            app:layout_constraintTop_toBottomOf="@+id/tv_content" />

        <ImageView

            android:layout_width="wrap_content"

            android:layout_height="wrap_content"

            app:layout_constraintTop_toBottomOf="@+id/tv_content"

            app:layout_constraintEnd_toEndOf="parent"

            android:layout_marginTop="10dp"

            app:srcCompat="@drawable/ic_favorite_black_24dp" />

    </androidx.constraintlayout.widget.ConstraintLayout>

</layout>

 从下图可以看到数据是和视图绑定到一起的,数据发生变化,视图也会改变.其他三个模块的代码都是和首页一样,创建view,绑定视图,请求数据,数据请求成功后直接显示.这里就不写重复赘述了。

5.遇到的问题:

(1)databinding绑定图片和一般的用法不一样,一般的直接设置背景图片就可以,拿到数据后直接用glide显示,这里是和视图绑定的,所以我们得新建一个图片显示的工具类本文暂定ImageUtil,方法必须和布局中的名称一致,否则会报错.

我们在ImageUitl中新建一个显示首页文章列表图片的方法:

​​​​​​

@BindingAdapter("image_view")

public static void setImageView(ImageView imageView, String urlString) {

    Glide.with(imageView.getContext()).load(urlString).into(imageView);

}


(2)当实体类有一个list我们好处理,但是如果又嵌套一个的话比较麻烦,本博主也是参考网上的方法,找了很多资料才找到这个办法,刚开始一直获取到的第一个数据.

我们这里就以知识模块为例.返回的数据是一个知识体系列表,然后这个体系列表里面又有一个体系文章标题列表

​<data>

    <variable

        name="knowledge"

        type="fule.com.playandroidkotlin.ui.model.KnowledgeModel.DataBean" />

    <!导入一个list!>

    <import type="java.util.List" />

    <variable

        name="knowList"

        type="List&lt;fule.com.playandroidkotlin.ui.model.ChildrenModel>" />

    <!表示list的位置!>

    <variable

        name="index"

        type="int" />

</data>

下图中一个表示知识体系列表,一个表示体系列表下的栏目列表 

(3)刚开始在fragment布局中layout设置了宽和高会一直报错

图(1)是错误的写法,会一直报错,这是我作为一个初学者啥也不懂踩的坑

图(2)才是正确的写法

6.实现的效果图如下:

7.最后,项目的完整源码地址:https://gitee.com/jackning_admin/MvvmPlayAndroid

当然,这个小demo还有很多问题需要处理,比如登录注册功能,点赞收藏等等,后面有时间会慢慢补上,欢迎各位大佬前来讨论,指出小弟的问题,如有问题,及时提示,我会及时改正.

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349