最近在学习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 =
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? = 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>? = null
private var repository = HomeRepository()
fun getArtCircleList(page:Int) {
observer = object : Observer> {
override fun onComplete() {
completedListener.onCompleted()
}
override fun onSubscribe(d: Disposable) {
}
override fun onNext(t: BaseModel) {
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>
)
}
}
首页请求HomeRespository
/**
*@作者: njb
*@时间: 2020/1/10 18:00
*@描述:
*/
class HomeRepository :BaseRepository(){
fun getArcticList(
page: Int,
observer: Observer>) {
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?):
BaseQuickAdapter(R.layout.item_home,data){
override fun convert(helper: BaseViewHolder, item: ArticleListModel.DatasBean) {
val itemBinding = DataBindingUtil.bind(helper.itemView)!!
itemBinding.homebean = item
itemBinding.executePendingBindings()
}
}
首页布局:
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
android:id="@+id/rf_home"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:id="@+id/rv_home"
android:layout_width="match_parent"
android:layout_height="match_parent" />
首页Banner布局加在文章列表头部,Banner广告布局:
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:id="@+id/home_banner"
android:layout_width="match_parent"
android:layout_height="200dp"
app:layout_constraintTop_toTopOf="parent" />
首页文章列表item布局:
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
name="homebean"
type="fule.com.playandroidkotlin.ui.model.ArticleListModel.DatasBean" />
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp"
tools:ignore="MissingConstraints">
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" />
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" />
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">
android:id="@+id/iv_content"
android:layout_width="100dp"
android:layout_height="80dp"
android:scaleType="centerCrop"
app:home_image="@{homebean.envelopePic}"/>
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" />
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" />
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" />
从下图可以看到数据是和视图绑定到一起的,数据发生变化,视图也会改变.其他三个模块的代码都是和首页一样,创建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我们好处理,但是如果又嵌套一个的话比较麻烦,本博主也是参考网上的方法,找了很多资料才找到这个办法,刚开始一直获取到的第一个数据.
我们这里就以知识模块为例.返回的数据是一个知识体系列表,然后这个体系列表里面又有一个体系文章标题列表
name="knowledge"
type="fule.com.playandroidkotlin.ui.model.KnowledgeModel.DataBean" />
name="knowList"
type="List
name="index"
type="int" />
下图中一个表示知识体系列表,一个表示体系列表下的栏目列表
(3)刚开始在fragment布局中layout设置了宽和高会一直报错
图(1)是错误的写法,会一直报错,这是我作为一个初学者啥也不懂踩的坑
图(2)才是正确的写法
6.实现的效果图如下:
当然,这个小demo还有很多问题需要处理,比如登录注册功能,点赞收藏等等,后面有时间会慢慢补上,欢迎各位大佬前来讨论,指出小弟的问题,如有问题,及时提示,我会及时改正.