preface

My last post was a poor overview of playing Android from 0 to 1, and sure enough, it didn’t go over well. Of course, there is no beautiful page (just the simplest MD document), and since I haven’t written an article for a long time and the National Day holiday has just passed, I feel a little confused. There are many things I want to say but I don’t know how to describe them, so I paste the code. As a result…

Writing an article can’t be so later, good-looking article is not quite able to typeset, or pay attention to content as far as possible. Now writing articles is not only their own notes, write wrong words may mislead others; But on the other hand, if you want to go to the official website without being misled, there may be errors but they are few.

So much complaining, let’s start the body of the day.

The body of the

The Github address and the APK download address are the usual ones.

Apk download address: www.pgyer.com/llj2

Github address: github.com/zhujiang521…

See the title should be clear we want to achieve today’s project home page, first look at the implementation of the good look!

Doesn’t it look easy! Structure is very clear, the top is the title bar, down is the Banner, and then down is the list of articles, a very simple home page. There are several ways to achieve, either directly use RecyclerView directly arranged down, or use a LinearLayout one row down, in fact, there is no better way to achieve, like to use which kind of use what kind of! Here I choose to use the LinearLayout one by one to row down, simple and clear, good!

The TitleBar caption bar

Let’s take it one at a time. TitleBar has a title in the middle and a search and click event in the upper right corner. You can customize TitleBar if you need it.

Here’s how to use it:

    <com.zj.core.util.TitleBar
        android:id="@+id/homeTitleBar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:backImageVisiable="false"
        app:titleName="Home page" />
Copy the code

In the layout, you can specify the title name and whether to show the back button. The home page does not need the back button, so set false. Let’s look at how to set these two properties in code:

    homeTitleBar.setTitle("Title")
    homeTitleBar.setBackImageVisiable(false)
Copy the code

The search text should be displayed in the upper right corner with a click event.

    homeTitleBar.setRightText("Search")
    homeTitleBar.setRightTextOnClickListener {
        // Click the logic to implement the event
    }
Copy the code

If you want to write a layout and include every page, you can do it. It’s just a bit of a hassle. The implementation is the same.

Banner

Banner words in order to simplify and save trouble directly use the tripartite library, before also wrote a simple use of this tripartite library, you can refer to: Android implementation of Banner rotation figure custom picture (non-network picture).

The dependencies of tripartite libraries are as follows:

implementation 'com. Youth. The banner, the banner: 2.1.0'
Copy the code

Write it down to use:

<com.youth.banner.Banner
    android:id="@+id/homeBanner"
    android:layout_width="match_parent"
    android:layout_height="@dimen/dp_180" />
Copy the code

The layout is pretty simple, just write it in. The code is not too hard, just write an adapter and put it in the setup and start.

Write an adapter first:

open class ImageAdapter(private valmContext: Context, mData: List<BannerBean>) : BannerAdapter<BannerBean? , ImageAdapter.BannerViewHolder? >(mData) {override fun onCreateHolder(parent: ViewGroup,viewType: Int): BannerViewHolder {
        val imageView = ImageView(parent.context)
        imageView.layoutParams = ViewGroup.LayoutParams(
            ViewGroup.LayoutParams.MATCH_PARENT,
            ViewGroup.LayoutParams.MATCH_PARENT
        )
        imageView.scaleType = ImageView.ScaleType.CENTER_CROP
        return BannerViewHolder(imageView)
    }

    class BannerViewHolder(view: ImageView) :
        RecyclerView.ViewHolder(view) {
        var imageView: ImageView = view
    }

    override fun onBindView(holder: BannerViewHolder? .data: BannerBean? , position:Int,size: Int) {
        Glide.with(mContext).load(if (data? .filePath ==null) data? .imagePathelse data.filePath).into(holder!! .imageView) } }Copy the code

Twenty lines of code, the core is to Glide load the picture.

It says to put the adapter in. Where do you put it? Of course it is in the Banner:

valbannerAdapter = ImageAdapter(context!! , viewModel.bannerList) homeBanner.adapter = bannerAdapter// Set to the circular indicator and start
homeBanner.setIndicator(CircleIndicator(context)).start()
Copy the code

That’s about it, but to avoid memory leaks and improve performance, start scrolling when the onResume page is visible and stop scrolling when the onPause page is not:

    override fun onResume(a) {
        super.onResume()
        homeBanner.start()
    }
    
    override fun onPause(a) {
        super.onPause()
        homeBanner.stop()
    }
Copy the code

RecyclerView

Row here to use RecyclerView to show the article, the layout is not posted, too simple, but think about it or need to post, because there needs to be a drop-down refresh and pull up load, here used a tripartite library, we should not be unfamiliar, the following is dependent on:

Implementation 'com. Scwang. Smartrefresh: SmartRefreshLayout: 1.1.2'Copy the code

Here’s how to use the layout:

<com.scwang.smartrefresh.layout.SmartRefreshLayout
    android:id="@+id/homeSmartRefreshLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/homeRecycleView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</com.scwang.smartrefresh.layout.SmartRefreshLayout>
Copy the code

RecyclerView adapter I use the open source library of Hongyang Dashen, the following is dependent:

API 'com. Zhy: base - rvadapter: 3.0.3'Copy the code

Let’s take a look at how pull-down refresh and pull-up load work:

homeSmartRefreshLayout.apply {
    setOnRefreshListener { reLayout ->
        reLayout.finishRefresh(measureTimeMillis {
             page = 1
             getArticleList(true)
        }.toInt())
    }
    setOnLoadMoreListener { reLayout ->
        val time = measureTimeMillis {
             page++
             getArticleList(true)
        }.toInt()
        reLayout.finishLoadMore(if (time > 1000) time else 1000)}}Copy the code

You can also guess how to call the callback from its name.

The adapter code will not be posted, if you are interested, you can go to Github to download the code.

OK, the home page layout is complete, then it’s time to get the data!

To get the data

Banner data

We need to think about whether the Banner data will be updated in real time. As far as I know, the Banner data will be updated once a week at most. At present, most android apps on the market will request the network every time they enter, and then reload, which will undoubtedly increase the network cost, not to mention the pictures. The money that can consume user, although say current flow is not valuable, but also need to save as far as possible!

Therefore, I performed some operations on the Banner data here. First, I went to the local database to check whether there is Banner data. If there is, and the time interval with the last refresh is less than one day (in order to prevent updates, so the tentative day), then I returned the data in the database. If there is no data or the last time to refresh the interval of one day to request network data, request network data is divided into success or failure, if the failure will return failure information, the page to display the corresponding page; If successful, the current time is recorded. If the database detects that there is data and it is the same as the requested data, then the data in the database is returned. Otherwise, the Banner data in the database is deleted, the requested data is inserted into the database, and the data is returned.

It’s a little convoluted here, so let me draw a picture to make it easier to understand:

I have never drawn a similar flow chart before. I used to draw it when I was in college. At ordinary times, I just drew on this book casually. Starting with a pink view of the local database in the middle, it is divided into various ways to obtain data under various conditions.

Ok, so much said and drawn, it’s time to look at the code implementation:

    fun getBanner(application: Application) = fire {
        val spUtils = SPUtils.getInstance()
        val downImageTime by Preference(DOWN_IMAGE_TIME, System.currentTimeMillis())
        val bannerBeanDao = PlayDatabase.getDatabase(application).bannerBeanDao()
        val bannerBeanList = bannerBeanDao.getBannerBeanList()
        if (bannerBeanList.isNotEmpty() && downImageTime > 0 && downImageTime - System.currentTimeMillis() < ONE_DAY) {
            Result.success(bannerBeanList)
        } else {
            val bannerResponse = PlayAndroidNetwork.getBanner()
            if (bannerResponse.errorCode == 0) {
                val bannerList = bannerResponse.data
                spUtils.put(DOWN_IMAGE_TIME, System.currentTimeMillis())
                if (bannerBeanList.isNotEmpty() && bannerBeanList[0].url == bannerList[0].url) {
                    Result.success(bannerBeanList)
                } else {
                    bannerBeanDao.deleteAll()
                    insertBannerList(application, bannerBeanDao, bannerList)
                    Result.success(bannerList)
                }
            } else {
                Result.failure(RuntimeException("response status is ${bannerResponse.errorCode}  msg is ${bannerResponse.errorMsg}"))}}}Copy the code

InsertBannerList = insertBannerList = insertBannerList = insertBannerList ();

    private suspend fun insertBannerList(
        application: Application,
        bannerBeanDao: BannerBeanDao,
        bannerList: List<BannerBean>) {
        bannerList.forEach {
            val file = Glide.with(application)
                .load(it.imagePath)
                .downloadOnly(SIZE_ORIGINAL, SIZE_ORIGINAL)
                .get()
            it.filePath = file.absolutePath
            bannerBeanDao.insert(it)
        }
    }
Copy the code

The code is very simple, but the important thing is this idea. Now it’s time to look at the data in the article list.

Article list data

In fact, the data acquisition of the article is similar to that of the Banner. The logic is basically the same. It reads files from the database, and then determines whether to refresh. The difference between the article list data and the Banner is that the article list needs to be requested twice, and you need to determine the current page. If it is page 0, you need to add the top article to the first one, and if it is not page 0, you only need to add the following article.

The implementation here is actually lazy, but not exactly lazy… Why do you say that? Because I only cache the first page of the article list data, but actually not lazy, because the article list data is variable, may update frequently, cache too many pages of data later need to update, or delete all the data and reinsert, not worth the loss. Cache before the first page of the data is that the user has already opened the project, the data are normally displayed, if suddenly no network, open the application again not white page or display no network, shows the cached data is more elegant, if tensile load on the user dropdown refresh or remind users currently there is no network.

Since the data of the Banner is drawn in a graph, the data of the article list should also be drawn in a graph!

After determining whether it is the first page, we need to judge the top article and the list article in sequence. According to whether the database data is consistent with the network request data, we need to determine whether to update the database data, and then return the data to the ViewModel.

Go ahead and take a look at the code. It’s a bit too much and I’ll just show you the general logic. If you want to see the complete code, you can download it directly from Github:

    fun getArticleList(application: Application, query: QueryHomeArticle) = fire {
        coroutineScope {
            val res = arrayListOf<Article>()
            if (query.page == 1) {
                val spUtils = SPUtils.getInstance()
                val downArticleTime by Preference(DOWN_ARTICLE_TIME, System.currentTimeMillis())
                val articleListDao = PlayDatabase.getDatabase(application).browseHistoryDao()
                val articleListTop = articleListDao.getTopArticleList(HOME_TOP)
                val downTopArticleTime by Preference(
                    DOWN_TOP_ARTICLE_TIME,
                    System.currentTimeMillis()
                )
                if (articleListTop.isNotEmpty() && downTopArticleTime > 0&& downTopArticleTime - System.currentTimeMillis() < FOUR_HOUR && ! query.isRefresh ) { res.addAll(articleListTop) }else {
                    val topArticleListDeferred =
                        async { PlayAndroidNetwork.getTopArticleList() }
                    val topArticleList = topArticleListDeferred.await()
                    if (topArticleList.errorCode == 0) {
                        if (articleListTop.isNotEmpty() && articleListTop[0].link == topArticleList.data[0].link && ! query.isRefresh) { res.addAll(articleListTop) }else {
                            res.addAll(topArticleList.data)
                            topArticleList.data.forEach {
                                it.localType = HOME_TOP
                            }
                            spUtils.put(DOWN_TOP_ARTICLE_TIME, System.currentTimeMillis())
                            articleListDao.deleteAll(HOME_TOP)
                            articleListDao.insertList(topArticleList.data)}}}}else {
                val articleListDeferred =
                    async { PlayAndroidNetwork.getArticleList(query.page - 1)}val articleList = articleListDeferred.await()
                if (articleList.errorCode == 0) {
                    res.addAll(articleList.data.datas)
                    Result.success(res)
                } else {
                    Result.failure(
                        RuntimeException(
                            "response status is ${articleList.errorCode}" + "  msg is ${articleList.errorMsg}")}}}}Copy the code

As you can see, I have only shown the data cache for the top articles, but the same principle applies to the list of articles.

Here are the constants used by these modules:

const val ONE_DAY = 1000 * 60 * 60 * 24
const val FOUR_HOUR = 1000 * 60 * 60 * 4
const val DOWN_IMAGE_TIME = "DownImageTime"
const val DOWN_TOP_ARTICLE_TIME = "DownTopArticleTime"
const val DOWN_ARTICLE_TIME = "DownArticleTime"
const val DOWN_PROJECT_ARTICLE_TIME = "DownProjectArticleTime"
const val DOWN_OFFICIAL_ARTICLE_TIME = "DownOfficialArticleTime"
Copy the code

ViewModel

ViewModel code is relatively simple, I directly put on, and then the following simple description of it:

class HomePageViewModel(application: Application) : AndroidViewModel(application) {

    private val pageLiveData = MutableLiveData<QueryHomeArticle>()

    private val refreshLiveData = MutableLiveData<Boolean> ()val bannerList = ArrayList<BannerBean>()

    val articleList = ArrayList<Article>()

    val articleLiveData = Transformations.switchMap(pageLiveData) { query ->
        HomeRepository.getArticleList(application, query)
    }

    val bannerLiveData = Transformations.switchMap(refreshLiveData) { isRefresh ->
        HomeRepository.getBanner(application,isRefresh)
    }

    fun getBanner(isRefresh: Boolean) {
        refreshLiveData.value = isRefresh
    }

    fun getArticleList(page: Int, isRefresh: Boolean) {
        pageLiveData.value = QueryHomeArticle(page, isRefresh)
    }

}

data class QueryHomeArticle(var page: Int.var isRefresh: Boolean)
Copy the code

AndroidViewModel as in the previous article, AndroidViewModel is used because you need a database in Repository.

The logic in the ViewModel is clear: define two ArrayLists to hold data, use LiveData to observe changes to the data, and two GET methods to mobilize method execution to change the data.

Horizontal and vertical screen adaptation

At this point the whole logic should make sense, and the code should be almost written, so let’s run it!

After running it, it was found that portrait display was normal, but when landscape display, the page was completely unavailable! The Banner basically took up all the space and the article list was unusable!

In the res directory, create a layout-land folder and put the layout in it. The name of the layout is the same as the name of the layout.

Here you can rearrange the layout of the landscape control position as required. Here I’ve split the screen in half, with the left side showing the Banner and the right side showing the article list. Let’s take a quick look at the layout:

<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal"> < com. Youth. The banner, the banner of the android: id = "@ + id/homeBanner" android: layout_width = "0 dp" android: layout_weight = "1.5" android:layout_height="match_parent" /> <com.scwang.smartrefresh.layout.SmartRefreshLayout android:id="@+id/homeSmartRefreshLayout" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="2"> <androidx.recyclerview.widget.RecyclerView android:id="@+id/homeRecycleView" android:layout_width="match_parent" android:layout_height="match_parent" /> </com.scwang.smartrefresh.layout.SmartRefreshLayout> </LinearLayout>Copy the code

After the completion of the following style, is not more beautiful than just, and easier to operate.

That’s about it, and I’ll come back to the next article.

conclusion

Every time I feel that there is not much to write, I write more, and every time I think about what I want to write, I don’t know how to write by hand. This article briefly walks through the simple implementation logic of an application home page, and brings you a simple implementation of vertical and horizontal screens.

Write to write to pass midnight 12 o ‘clock, for a long time did not write so late, is his programmer when too incompetent, also for a long time did not work hard to learn, for a long time did not take the initiative to learn, basic has been in a passive learning situation, can not be so, their own to refueling.

Efforts, mutual encouragement.